diff --git a/BUILDOPTIONS.txt b/BUILDOPTIONS.txt new file mode 100644 index 00000000..bcbff4c2 --- /dev/null +++ b/BUILDOPTIONS.txt @@ -0,0 +1,260 @@ +muscled should compile fine with nothing more than a "cd muscle/server ; make" +but if you feel like hacking around, here is a list of some +compile-time constants that you can define in the CXXFLAGS variable +of your Makefile to alter muscle's behaviour: + +-DMUSCLE_ENABLE_SSL + Set this to enable built-in support for SSL connections via OpenSSL. + (e.g. ReflectServer::SetSSLPrivateKey()) + +-DMUSCLE_USE_CPLUSPLUS11 + Set this to enable C++11-specific features such as move-constructors. + +-DMUSCLE_AVOID_IPV6 + Set this to indicate that Muscle should be compiled without IPv6 + support. The main difference with this flag is that muscle_ip_address + will be defined a typedef'd alias for a uint32 (rather than a C++ class + that holds 128 bits of data). + +-DMUSCLE_SINGLE_THREAD_ONLY + Makes the Mutex class's methods compile down to no-ops. Specify this if + you are able to guarantee that your program will never access MUSCLE + code from more than one thread. + +-DMUSCLE_USE_EPOLL + Causes the SocketMultiplexer class to use the epoll() Linux system + call instead of select(). This method is less portable, but + avoids the FD_SETSIZE limitation that select() introduces. + Note that this flag is mutually exclusive with -DMUSCLE_USE_KQUEUE and + -DMUSCLE_USE_POLL. + +-DMUSCLE_USE_POLL + Causes the SocketMultiplexer class to use the poll() system + call instead of select(). This method is slightly less portable, but + avoids the FD_SETSIZE limitation that select() introduces. + Note that this flag is mutually exclusive with -DMUSCLE_USE_KQUEUE + and -DMUSCLE_USE_EPOLL. + +-DMUSCLE_USE_KQUEUE + Causes the SocketMultiplexer class to use the kqueue() and kevent() + system calls instead of select(). This method is less portable, but + avoids the FD_SETSIZE limitation that select() introduces. + Note that this flag is mutually exclusive with -DMUSCLE_USE_POLL and + -DMUSCLE_USE_EPOLL. + +-DMUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS=(#micros) + If specified, MUSCLE's AddNewConnectSession() calls + will force an asynchronous connection to fail after this + many microseconds have elapsed. If not defined, the + default behavior is to let the operating system determine + when the asynchronous connection should time out and fail. + +-DMUSCLE_CATCH_SIGNALS_BY_DEFAULT + If specified, ReflectServer will by default set up a signal + handler to catch signals (e.g. Control-C), and gracefully + exit its event loop when they are detected. Without this + flag, MUSCLE signal-handling routines will only be set up + if you explicitly call SetSignalHandlingEnabled(true) + somewhere in your code. + +-DMUSCLE_USE_LIBRT + If specified, GetRunTime64() and Snooze64() will use librt's + high-resolution timing functions instead of the low-resolution + ones supplied by older operating systems. Note that if you + specify this flag, you should link with librt as well (-lrt). + +-DMUSCLE_AVOID_MULTICAST_API + Set this to omit the multicast API calls in NetworkUtilityFunctions.h. + This might be useful to do if compiling on a platform where multicast + APIs aren't supported. + +-DMUSCLE_ENABLE_KEEPALIVE_API + Set this to make the TCP keepalive API calls in NetworkUtilityFunctions.h + available for use. (it's disabled by default to make sure that the + keepalive functions won't break the build on platforms that don't + support keepalive). Note that muscled itself won't use keepalive even + if this constant is specified; the functions are a convenience for + other MUSCLE-based applications to use if they wish. + +-DMUSCLE_64_BIT_PLATFORM + Set this to indicate that compilation is being done on a 64-bit platform. + This flag will be set automatically in support/MuscleSupport.h if defines + indicating a known 64-bit platform are detected; if not, you can set it + yourself in the Makefile if necessary. + +-DMUSCLE_USE_LLSEEK + Force the FileDescriptorDataIO class to use the non-standard _llseek() command + when compiled under Linux. This should be done automatically in most cases + where it is necessary, but you can force it also. + +-DMUSCLE_PREFER_QT_OVER_WIN32 + Tell the Muscle Thread/Mutex/etc classes to prefer to use Qt Threading APIs over Win32 calls + when both are available. (By default, Win32 calls are preferred when running under Windows) + +-DMUSCLE_ENABLE_MEMORY_PARANOIA=N + Put N overwrite-guards before and after each malloc() buffer, watch them for memory corruption + +-DMUSCLE_NO_EXCEPTIONS + Tells muscle that exceptions won't be used. + +-DMUSCLE_ENABLE_MEMORY_TRACKING + Enables system memory usage tracking (wrappers for new and delete that allow muscled to + put an upper bound on the amount of memory it dynamically allocates, etc) + +-DMUSCLE_AVOID_ASSERTIONS + makes MASSERT statements into no-ops + +-DMUSCLE_AVOID_SIGNAL_HANDLING + Disables the built-in support for catching signals and doing an orderly shutdown of + the ReflectServer event loop in response. + +-DMUSCLE_AVOID_INLINE_ASSEMBLY + tells muscle to use boring old C/C++ code and avoid using any clever assembly-language code + +-DMUSCLE_ENABLE_ZLIB_ENCODING + enables support for zlib compression of Messages + +-DMUSCLE_TRACE_CHECKPOINTS=N + enable TCHECKPOINT tracing of last N checkpoints + +-DMUSCLE_DISABLE_MESSAGE_FIELD_POOLS + turn off Object pooling for Message field objects; helpful for debugging + +-DMUSCLE_MAX_OUTPUT_CHUNK=N + tell muscled not to send() more than N bytes of output per call + +-DMUSCLE_INLINE_LOGGING + turn Log(), LogTime(), etc into simple printf() passthroughs + +-DMUSCLE_DISABLE_LOGGING + turn Log(), LogTime(), etc into no-ops + +-DMUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS + Use Mutexes to simulate atomic inc/dec operations; useful if no other method is available + +-DMUSCLE_MUTEX_POOL_SIZE=N + If -DMUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS is defined, then this can be defined to set the size of the Mutex pool to use. Defaults to 256. + +-DMUSCLE_POWERPC_TIMEBASE_HZ=N + Use mftb/mftbu for GetRunTime64() calls. N is the frequency at which the register is incremented + +-DMUSCLE_USE_PTHREADS + Use pthreads for thread operations + +-DMUSCLE_DEFAULT_TCP_STALL_TIMEOUT=N + Number of microseconds to wait for a client to read TCP data before + giving up and closing his connection (defaults to 20 minutes' worth) + +-DMUSCLE_FD_SETSIZE=N + Redefine the fd_setsize to another value (useful under Windows, where the default setsize is a measly 64) + +-DMUSCLE_AVOID_NEWNOTHROW + Turns newnothrow into a synonym for "new", instead of "new (nothrow)" + +-DMUSCLE_AVOID_FORKPTY + Tells the ChildProcessDataIO class not to compile in calls to forkpty(); instead it will use fork() only + +-DMUSCLE_HASHTABLE_DEFAULT_CAPACITY=X + Number of value slots to initially pre-allocate in a Hashtable, by default (defaults to 7) + Note that the pre-allocation is done the first time an object is Put() into the Hashtable. + A new, empty Hashtable will have no pre-allocated slots. + +-DSMALL_QUEUE_SIZE=N + Number of value slots to initially pre-allocate in a Queue, by default. (defaults to 3) + +-DSMALL_MUSCLE_STRING_LENGTH=N + strings <= this length will be stored inline in the String object to avoid a malloc()... default is 7 + +-DMUSCLE_USE_QUERYPERFORMANCECOUNTER + Tells MUSCLE's GetRunTime64() to use the higher-resolution + QueryPerformanceCounter() API instead of timeGetTime() when running under Windows. + Specifying this flag increases GetRunTime64()'s accuracy, but QueryPerformanceCounter() + is known not to work on some hardware. + +-DMUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME + Compiles MUSCLE's and LogTime() function as a macro that includes the + source code location in the call. By enabling this it is possible to include + the source of a message with the messge itself, should your code choose to do so. + +-DMUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS=200 + If defined, the Hashtable::EnsureSize() method will do some paranoia + checking every time it resizes the Hashtable, to see if the Hashtable's + average lookup-count (as calculated by CountAverageLookupComparisons()) + is greater than 2.00f (or whatever the preprocessor-define's value is, + divided by 100). If it is, a log message, debug info, and a stack trace + will be printed. Only enable this compiler flag when doing debugging/ + development/optimization (i.e. when you want to check to see if you + have any hash functions that aren't performing well), since it will + significantly slow down your program when it is enabled. + +-DMUSCLE_ENABLE_DEADLOCK_FINDER + If specified, calls to Mutex::Lock() and Mutex::Unlock() will + print trace information to stdout that can later be used by + the deadlockfinder program (in the tests folder) to detect + potential deadlocks in the code caused by inconsistent lock + acquisition ordering. + +-DMUSCLE_DEFAULT_RUNTIME_DISABLE_DEADLOCK_FINDER + If this is specified in addition to -DMUSCLE_ENABLE_DEADLOCK_FINDER, + then deadlock-detection will be compiled into the code but the + printouts will be disabled by default. To enable them at runtime, + set the global variable _enableDeadlockFinderPrints to true. + +-DMUSCLE_POOL_SLAB_SIZE + This can be set to a number indicating the number of bytes that should be + allocated in each "slab" of data malloc()'d by the ObjectPool class. If left + unset, slabs of approximately 8 kilobytes will be used. Large slabs mean + fewer memory allocations, but potentially more memory wasted if all the objects + in the slabs aren't needed. This value should be specified in bytes. + +-DMUSCLE_AVOID_BITSTUFFING + If set, this flag will cause the RefCount and ByteBuffer classes + to use a separate boolean state value, rather than stuffing that bit + into its held pointer. This flag might be necessary on systems + that don't word-align their object pointers (if such systems exist) + +-DMUSCLE_AVOID_CHECK_THREAD_STACK_USAGE + If set, calls to the CHECK_THREAD_STACK_USAGE macro will be + converted into no-ops. + +-DMUSCLE_AVOID_OBJECT_COUNTING + If defined, the CountedObject<> class will compile down to a no-op. + +-DMUSCLE_AVOID_THREAD_LOCAL_STORAGE + If defined, the MUSCLE code will try to avoid using the ThreadLocalStorage + class where possible (in particular, it will use Mutexes inside the + ZLibUtilityFunctions.cpp file rather than ThreadLocalStorage objects -- + this might be useful on systems where ThreadLocalStorage isn't implemented) + +-DMUSCLE_AVOID_MINIMIZED_HASHTABLES + If defined, the MUSCLE Hashtable class will not used variable-sized + indexing in its HashtableEntries. Variable-sized indexing saves memory + when tables have less than 65,535 slots in them, but increases the + number of "if" statesments in common Hashtable codepaths. Whether or + not it increases or decreases performance will depend on the architecture + of the host computer (e.g. on cache size, CPU speed, etc). + +-DMUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + As of v5.90, the Hashtable class includes logic to ensure that + HashtableIterators are thread safe, even if multiple threads are + iterating over the same Hashtable at the same time (as long the + Hashtable is not being modified, at least). This extra safety + does impose some overhead, though -- about 16 bytes of RAM per + Hashtable object, and a small amount of CPU overhead imposed + by using an AtomicCounter. If you want to avoid that overhead + and you're confident that you will always supply the + HTIT_FLAG_NOREGISTER argument whenever you are doing + concurrent iterations over a Hashtable (or Message), you can + supply this flag on the command line to avoid the overhead. + +-DMUSCLE_FAKE_SHARED_MEMORY + If defined, the SharedMemory class will allocate a non-shared memory + buffer (using muscleAlloc()) rather than actual shared memory. Handy + for debugging if you suspect that shared-memory is causing a problem. + +-DMUSCLE_COUNT_STRING_COPY_OPERATIONS + If this flag is defined, the String class will tally the number + of times that String objects are moved and the number of times + they are copied. This is handy for verifying that the C++11 + move semantics are being used as expected. + diff --git a/HISTORY.txt b/HISTORY.txt new file mode 100644 index 00000000..4445eb96 --- /dev/null +++ b/HISTORY.txt @@ -0,0 +1,5390 @@ + +This file contains the version history/change log for this software. + +key: - new feature + * bug fixed + o other + +6.05 Released 6/27/2014 + - Upgraded the captive zlib implementation to v1.2.8. + o Moved public repository from FreeCode to GitHub, because + FreeCode no longer accepts release announcements. + o Renamed LICENSE.txt to LICENSE, since that's how GitHub + likes the license file to be named. + * Merged in Mika Lindqvist's patch to avoid some truncation + warnings for strlen() calls under 64-bit Visual C++. + +6.04 Released 6/11/2014 + - Added a public GetParametersConst() method to the + StorageReflectSession, for easier debugging. + * Fixed a bug that prevented the PR_COMMAND_REMOVEPARAMETERS + handler from removing parameters that had escaped wildcard + characters in their names. + +6.03 Released 6/2/2014 + - Added a convenience method muscleArrayIndexIsValid() that + returns true iff the specified numeric index is a valid index + into the specified array. + - Added a CloseCurrentLogFile() function that can be used to + force the file-logger to close any log file that it currently + has open. + - Added a SetSendDestinations() method to the UDPSocketDataIO + class, for convenience. + - The Launch*() and System*() methods in the ChildProcessDataIO + class now take an optional directory-path argument so that you + can specify the current working directory of the child process. + - Added move-constructors and move-assignment operators to the + Ref and ConstRef classes (available only when + MUSCLE_USE_CPLUSPLUS11 is defined, of course). + - Added a Reset() method to the PointerAndBool class. + - Added a SwapContents() method to the PointerAndBool class. + - Added RemoveHeadMulti() and RemoveTailMulti() convenience + methods to the Queue class. + o Queue::GetArrayPointer() now makes sure to set (retLength) + to 0 when it returns NULL. + * Under Windows, GetConstSocketRefFromPool() now automatically + sets the don't-inherit flag on any sockets passed to it. This + avoids problems caused by child processes unintentionally holding + sockets connections open in the parent process. + * Fixed a bug in Queue::EnsureSizeAux() that would cause a + bogus extra item to be added to the end of the Queue if the + (setNumItems) argument was set to true. + * The MacOS/X implementation of DetectNetworkConfigChangesSession + would fail to notify its calling code about a changed network + configuration if the network change did not include any + IP address changes. Now it does provide a notification + for that scenario as well. + +6.02 Released 4/2/2014 + - Added optional (addHeaderBytes) and (addFooterBytes) arguments + to the DeflateByteBuffer() utility functions. + - Added an implementation of GetByteBufferFromPool() that takes + a DataIO as an argument, for easy reading in of files, etc. + - Added a NybbleizeData() implementation that takes a pointer + and length argument rather than a ByteBuffer object. + - Added an AreKeySetsEqual() convenience method to the Hashtable + class. This method checks for equal key-sets while ignoring + the values in the two Hashtables. + - Added WithoutPrefixIgnoreCase() and WithoutSuffixIgnoreCase() + convenience methods to the String class. + - Added some missing convenience overloads for HexBytesToString(). + - CanWildcardStringMatchMultipleValues() now ignores dashes in the + string, since they only have meaning when enclosed in brackets -- + and if there are brackets in the string, those will be sufficient + to cause CanWildcardStringMatchMultipleValues() to return true. + o Renamed CanRegexStringMatchMultipleValues() to + CanWildcardStringMatchMultipleValues(), as it is really examining + the simplified wild-card syntax and not the official regex + syntax. + o Several locations that were calling HasRegexTokens() to decide + if it was worth doing pattern-matching with a key-string now + call CanWildcardStringMatchMultipleValues() instead. + o DataNode::Reset() now deletes any metadata structures the + DataNode may have accumulated, rather than simply clearing them. + This saves RAM, and also ensures that recycled DataNodes will + act exactly the same as newly created ones. + * The MemMem() function did not return correct results in all + cases. Rewrote it to work correctly. + +6.01 Released 1/7/2014 + - Added a PrependWord() convenience method to the String class. + - Added WithReplacements() convenience methods to the String class. + - Added a SetExplicitDelayMicros() method to the + DetectNetworkConfigChangesSession class. + - Added a IsCopperDetected() method to the NetworkInterfaceInfo + class, so that code can tell whether or not a Ethernet jack + has a cable plugged in to it. + - Added a "quietsend" argument to hexterm. + o The NetworkInterfacesChanged() virtual method + in the DetectNetworkConfigChangesSession class + has been changed to take an argument that calls out + which network interfaces in particular have changed. + This functionality is currently only implemented under + Linux, MacOS/X and Windows. For other OS's the + argument will always be an empty list. + * Fixed a bug in the Linux implementation of + DetectNetworkConfigChangesSession that could cause + a segmentation fault if recvmsg() returned an error + (e.g. due to a signal being received). + +6.00 Released 10/14/2013 + - Rewrote the SSLSocketDataIO class to work better + with non-blocking I/O (in conjunction with the + new SSLSocketAdapterGateway class). + - Added implementations of SSLSocketDataIO::SetPrivateKey() + and SSLSocketDataIO::SetCertificate() that take a + ByteBuffer as an argument. + - Added an SSLSocketAdapterGateway class that is used + to manage OpenSSL's internal state machine when using + an SSLSocketDataIO class with your gateway. + - Added SetSSLPrivateKey() and GetSSLPrivateKey() methods + to the ReflectServer class, for easier enabling of SSL + authentication on all incoming TCP connections. These + methods are available iff MUSCLE_ENABLE_SSL is defined. + - Added SetSSLPublicKeyCertificate() and GetSSLPublicKeyCertificate() + methods to the ReflectServer class, for easier enabling + of SSL authentication on outgoing TCP connections. These + methods are available iff MUSCLE_ENABLE_SSL is defined. + - Added SetSSLPrivateKey() and SetSSLPublicKeyCertificate() + methods to the MessageTransceiverThread class, for easier + enabling of SSL functionality when using threaded I/O. + - Added an ssl_data folder with some info on generating + OpenSSL public/private keypairs, and an example keypair + for use in testing OpenSSL. + - When MUSCLE_ENABLE_SSL is defined, muscled now accepts + an optional 'privatekey=filename' argument. When specified, + SSL mode will be enabled and muscled will only accept incoming + TCP connections that present public keys that match this + private key/certificate. + - When MUSCLE_ENABLE_SSL is defined, portablereflectclient + and qt_example will now accept an optional + 'publickey=filename' argument. When specified, SSL mode + will be enabled and these clients will connect to muscled + using OpenSSL and present this file as their credentials. + - Added an "Animate" checkbox to the qt_example demo. + Checking it causes the window to move its indicator around + automatically. This is fun and also useful if you want to + test a scenario where multiple clients are generating traffic + simultaneously. + - Made the qt_example demo prettier. + - Renamed the C++11-helper macros in Hashtable.h and Queue.h + to make them less likely to collide with other packages' macros. + * Fixed some minor errors in the SSLSocketDataIO class. + o Renamed SSLSocketDataIO::SetKey() to SetPrivateKey(). + o Renamed SSLSocketDataIO::SetCertificate() to + SetPublicKeyCertificate(). + o AbstractMessageIOGateway::SetDataIO() is now a virtual + method. + +5.92 Released 9/20/2013 + - Improved support for C++11 move-semantics in the + Queue and Hashtable classes (enabled only when + -DMUSCLE_USE_CPLUSPLUS11 is defined) + - Added some instrumentation to the String class so + I can watch how many times a String object is copied, moved, + etc (enabled only when -DMUSCLE_COUNT_STRING_COPY_OPERATIONS + is defined) + - Added a PrintAndClearStringCopyCounts() function which + will print out the String-operation data collected above. + - Added some SFINAE magic to muscleSwap() so that it will + swap by calling SwapContents() when possible, rather than + by copying to a temporary object. + - Added an initializer-list constructor and overload of + AddTailMulti() to the Queue class (available only when + -DMUSCLE_USE_CPLUSPLUS11 is defined, of course) + o Renamed the Queue and array overloads of Queue::AddTail() + to AddTailMulti(), to avoid conflicts with the new C++11 + template parsing support. + o Renamed the Queue and array overloads of Queue::AddHead() + to AddHeadMulti(), to avoid conflicts with the new C++11 + template parsing support. + o Replaced MCRASH_IMPL macro with a call to assert(false). + * A little more Android-compatibility tweakage. + * Many of the programs in the tests folder weren't compiling + under C++11. Fixed. + * Fixed several potential bugs that were detected by clang's + static analysis tool. + +5.91 Released 9/2/2013 + - Added EnsureCanPut() convenience method to the Hashtable class. + - Added EnsureCanAdd() convenience method to the Queue class. + o Changed DoMutexAtomicIncrement() to be an inline function to + make calling it more efficient. + * Changed QMessageTransceiverThread and QAcceptSocketsThread to + call QCoreApplication::postEvent() rather than + QApplication::postEvent(), to allow for non-GUI Qt apps. + * Updated the Beginner's Guide document to reflect MUSCLE's + improved UDP support. + * Merged in some Android compatibility changes provided by + Jean-François Mullet. + * Use of the MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS compile + flag would cause MUSCLE to crash on startup due to an + order-of-operations issue. This has been fixed now. + * The MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS compile flag + was previously only used if no other implementation of + AtomicCounter was available. Now the flag has higher + precedence, so setting the flag means that Mutexes will + be used, even if another (more efficient) mechanism is + available. + +5.90 Released 8/6/2013 + - Added a GetPacketMaximumSize() method to the DataIO class + to allow gateway code to more intelligently handle UDP-style + packetized communication. + - MessageIOGateway now works usefully in conjunction with + UDPSocketDataIO. + - Added CreateObjectFromArchiveMessage() templated functions + to Message.h, to serve as a restoring-side counterpart to + GetArchiveMessageFromPool(), etc. + - AtomicCounter::AtomicIncrement() now returns a boolean + (true iff the new counter value is equal to one). + - Modified the HashtableIterator class so that read-only + Hashtable iterations are now thread-safe even if the + HTIT_FLAG_NOREGISTER flag isn't specified. + - Added a muscle_thread_id class to SetupSystem.h, to + properly represent a thread ID in an + implementation-neutral fashion. + - Added a "deadlock" program to the tests folder. This + program deliberately risks creating a deadlock, as a way + to exercise/demonstrate the deadlockfinder test. + - Added support for a -DMUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + command line flag, for those who would rather avoid the + overhead of automatic thread safety and promise to supply + HTIT_FLAG_NOREGISTER arguments by hand where necessary. + - Added an optional LRU lookup cache to the GetHostByName() + function, so that it can return more quickly when the same + hostnames are getting resolved over and over again. + - Added a SetHostNameCacheSettings() function that enables + and adjusts the LRU lookup cache in GetHostByName(). + - Added support for "dnscache" and "dnscachesize" command + line arguments in HandleStandardDaemonArgs(), to allow + command-line specification of the LRU lookup cache's behavior. + o Modified the Hashtable class so that the _iterHead, _iterTail, + and _freeHead member values are now uint32s rather than + pointers, to reduce memory usage. + o Removed the ThreadLocalStorage::SetFreeHeldObjectsOnExit() + method, and added a boolean argument to its constructor + instead, since pthreads don't allow you to change that + setting after pthread_key_create() has been called. + o Moved GetCurrentThreadID() into the muscle_thread_id class + as a static member function, and changed it to return + a muscle_thread_id object rather than unsigned long. + o Changed the default hostname for sessions without a known + IP address from "" to "_unknown_", as the angle + brackets in the former string have a special meaning as of + the 5.84 release, and that could interfere with node-path + matching in unintended ways. + o The CalculateChecksum() methods in Message.cpp have been + modified to be more robust in detecting data transposition + differences. + o Removed the MUSCLE_USE_QT_FOR_ATOMIC_OPERATIONS support + from AtomicCounter.h, since Qt's QAtomicInt class doesn't + support the functionality that the AtomicIncrement() + method's new return value requires. + o Removed MessageIOGateway::FlattenMessage() and + MessageIOGateway::UnflattenMessage(). Added in their + place: MessageIOGateway::FlattenHeaderAndMessage() + and MessageIOGateway::UnflattenHeaderAndMessage(). + These new methods deal with both the header bytes and + the Message body at the same time. + o Added a udpproxy.vcproj file to the tests folder, to + help compile udpproxy under Windows. + o Message:Flatten() now iterates over the fields in the + Message once, instead of twice. + o GetCurrentThreadID() is now an inline function, since + it may now get called often by HashtableIterator. + o Modified the deadlockfinder code to use Queues instead + of Hashtables, since muscle_thread_id can't be used + as a Hashtable key type anymore. + * Fixed testudp.cpp to properly use a MessageIOGateway + for its UDP communication. + * Tweaked the ifdefs in FilePathInfo.cpp a bit more so that + statInfo.st_birthtimespec won't be accessed when using + MacOS/X SDKs that don't provide it. + * MessageDataIOGateway no longer tries to Unflatten a + Message from a zlib-deflated data buffer that it was + unable re-inflate. + * Fixed a bug in SendDataUDP() that could cause SendDataUDP() + to incorrectly return an error when sending to a multicast + address using non-blocking mode, and the output buffer was + full. + +5.85 Released 7/2/2013 + - Added LogTime(MUSCLE_LOG_DEBUG) calls to all the error paths + in MessageIOGateway::DoInputImplementation() and + Message::Unflatten(), so that it's easier to determine when + TCP connections are being aborted due to data corruption. + - Added a PreviousOperationHadTransientFailure() function, + which returns true iff errno is EINTR or ENOBUFS. + - Specifying spamspersecond=-1 will now cause hexterm to + send spam data as fast as possible. + o SocketMultiplexer.h's MUSCLE_USE_POLL implementation was + supplying POLLERR to WSAPoll() but WSAPoll() doesn't support + POLLERR so WSAPoll() would return an error when this occurred. + Worked around the problem by filtering out POLLERR when + compiling under Windows. + * Fixed a bug where send() returning ENOBUFS could cause the + socket connection to be terminated, even though ENOBUFS is + not a fatal condition. + * SocketMultiplexer.cpp would not compile when MUSCLE_USE_POLL + was defined. Fixed. + * The ZLibCodec::Deflate() method would fail to compress all + the data in a very large buffer (e.g. over 42MB). Fixed. + +5.84 Released 4/20/2013 + - The StringMatcher class's numeric-range syntax has been + extended so that you can now specify multiple ranges. + For example, "<19-21,25,30-50>" would match strings + "19", "20", "21", "25", "30", "31", [...], and "50". + - Added GetCurrentTime64ForRunTime64() and GetRunTime64ForCurrenTime64() + conversion functions to TimeUtilityFunctions.h. + - Added a GetDescendant() utility method to the DataNode class. + - Added C++11 move-constructors and move-assignment-operators + to the Hashtable, Queue, String, Message, and ByteBuffer classes. + For backwards compatibility with older compilers, this code + will only be compiled if -DMUSCLE_USE_CPLUSPLUS11 is specified + on the compile line. + - SharedMemory class will now nerf itself into a non-shared-memory + class if -DMUSCLE_FAKE_SHARED_MEMORY is specified. + - Added a testfilepathinfo test to the tests folder. + o Updated all copyright notice headers to read 2000-2013 Meyer Sound. + * Added spaces between macro tokens (e.g. UINT32_FORMAT_SPEC) and + string constants (e.g. "Hello") to make C++11 compilers happy. + * ByteBuffer.cpp had a syntax error that would prevent it from + compiling on big-endian hosts. Fixed. + * MacOS/X only: Replaced deprecated Carbon function calls with + Mach equivalents, to avoid deprecation warnings under 10.8.x. + +5.83 Released 11/11/2012 + - Added convenience versions of InflateByteBuffer() and + DeflateByteBuffer() that take a ByteBufferRef as an argument. + o Removed some obsolete/unused methods (EnsureBufferSize() and + FreeLargeBuffer()) from the AbstractMessageIOGateway class. + o Fixed some typos in the comments in the delphi subfolder. + * The Hashtable class no longer generates warnings when compiled + under MSVC with -DMUSCLE_AVOID_MINIMIZED_HASHTABLES defined. + * Fixed a bug in IPAddressAndPort::ToString() that caused IPv4 + address strings to be formatted ambiguously when (preferIPv4Style) + was set to false. + +5.82 Released 10/1/2012 + - Rewrote the Hashtable class so that it now saves memory by + internally using smaller per-slot index values (e.g. uint8 or + uint16, rather than uint32) in Hashtables with fewer than 65,535 + slots in their HashtableEntry-array. + - Added support for compiling with -DMUSCLE_AVOID_MINIMIZED_HASHTABLES, + which will disable the above optimization (since sometimes you'd + rather use more memory and have faster code execution) + * Fixed ZLibUtilityFunctions.cpp so that it will again build when + -DMUSCLE_SINGLE_THREAD_ONLY is defined. + +5.81 Released 9/13/2012 + - If compiled with -DMUSCLE_ENABLE_DEADLOCK_FINDER, + HandleStandardDaemonArgs() will now look for a "deadlockfinder" + argument that can be used to force deadlock-prints on or off. + - Rewrote the ZLibUtilityFunctions.cpp calls to use + ThreadLocalStorage rather than shared ZLibCodec objects + serialized by Mutexes. This change helps avoids any possibility + of deadlocks, and also improves performance because now multiple + threads can inflate and/or deflate data in parallel. For systems + without a ThreadLocalStorage implementation, the old + implementation can still be used as a fallback, by compiling with + -DMUSCLE_AVOID_THREAD_LOCAL_STORAGE. + - The pthreads implementation of the ThreadLocalStorage now uses + the destructor-callback feature of pthread_create_key() to ensure + that created thread-local-data objects are destroyed in a timely + manner (i.e. when their host thread exits, rather than only when + the ThreadLocalStorage object destructor runs) + o ParseHexBytes() no longer treats commas as token-separator + characters, that way they can be escaped (by prefixing them + with a slash) and entered as ASCII literals. + +5.80 Release 8/30/2012 + - Added a new "micromessage" folder that contains a C API + with functions for constructing and parsing Message data + directly to/from its flattened form, with no intermediate + representations, flattening/unflattening, or dynamic memory + allocation necessary. This API would be appropriate for + embedded or other severely constrained environments. + - Added "testmicro", "microchatclient", and "microreflectclient" + programs to the tests folder, to serve as tests and examples + for the new MicroMessage and MicroMessageGateway APIs. + - Added a new constructor to the FilePathInfo class that + allows explicit/manual setting of the file info parameters. + - Added MoveName*() methods to the Message class, so that + the iteration order of the fields in the Message can + be easily modified. + o Made String::Flatten(), String::Unflattened(), and + String::FlattenedSize() into inline methods. + * IsMulticastIPAddress() now returns true for IPv6-mapped + IPv4 multicast addresses (e.g. ::ffff:239.255.1.2) + * Fixed a bug in MutexGuard class declaration, private + copy-constructor had wrong argument type. + * Fixed some inaccurate comments in ZipUtilityFunctions.h + * Fixed the "chatclient" test; it was forgetting to read + from stdin. + +5.72 Released 6/27/2012 + - Added HexBytesToAnnotatedString() functions to the + MiscUtilityFunctions API. These are similar to + PrintHexBytes(), but they output to a String rather + than to a file. + - ParseHumanReadableTimeIntervalString now correctly parses + non-integral values (e.g. "3.5 minutes") + * Fixed a constant-overflow error in testbytebuffer.cpp + * Fixed the gcc implementation of MuscleX86SwapInt16(), + which would fail to compile under gcc 4.7 on 64 bit targets. + * Usage of the uintptr typedef no longer causes warnings under + MSVC when the /Wp64 compiler flag is specified. + * Cleaned up some other miscellaneous MSVC warnings. + +5.71 Released 6/11/2012 + - Added a WasSignalCaught() function to SignalHandlerSession.h. + This method will return true iff any SignalHandlerSession ever + caught a signal in the current process. + - Added global SetMainReflectServerCatchSignals() and + GetMainReflectServerCatchSignals() functions to + SignalHandlerSession.h to allow easy programmatic enabling + and disabling of the standard signal handling routines. + - Added WithPrefix(), WithSuffix(), WithoutPrefix(), and + WithoutSuffix() convenience methods to the String class. + - Added TruncateChars() and TruncateToLength() convenience + methods to the String class. + - Added an StartsWithNumber() convenience method to the String class. + - Added Append*(), Read*(), and Write*() methods to the ByteBuffer + class for easier endian-aware adding and retrieiving of various + primitive datatypes from a ByteBuffer's data array. + - Added a SetDataFlags() and IsEndianSwapEnabled() methods to + the ByteBuffer class to help the user specify what data + endian-ness logic the ByteBuffer should apply in its Append*(), + Read*(), and Write*() method calls. + - Added a testbytebuffer.cpp test to the tests folder to test + the new data adding/retrieval routines. + - Added a PointerAndBool class that stores a pointer and a boolean + using no more space than that of a pointer by itself (unless + -DMUSCLE_AVOID_BITSTUFFING is enabled). + o Renamed MUSCLE_AVOID_REFCOUNT_BITSTUFFING to + MUSCLE_AVOID_BITSTUFFING, since bit-stuffing is no longer + specific to the RefCount class. + +5.70 Released 5/8/2012 + - NodePathMatcher::DoTraversal() now returns the number of + DataNodes that the traversal visited. + * Fixed a bug in Queue::Clear() that would cause it only to reset + the first item in the queue rather than all of the items. + * Range-checking in DenybblizeData was incorrect (it would spuriously + reject characters 'G' through 'P'). Fixed. + +5.69 Released 4/18/2012 + - Made Queue::Clear() a bit more efficient. + - Exposed the DefaultConsoleLogger and DefaultFileLogger + classes in LogCallback.h so they can be re-used elsewhere. + - Added an Indent() convenience method to the String class. + - The matching logic in AndOrQueryFilter and NandNotQueryFilter + now has additional short-circuit logic for better efficiency. + - StorageReflectSession::RestoreNodeTreeFromMessage() now + has an optional (quiet) argument, which can be used to prevent + db-subscription-updates from being triggered when the subtree + is installed into the database. + o Rewrote the String::Arg() methods to take C-native argument + types rather than fixed-size types (e.g. int instead of int32) + so that explicit typecasting is no longer required to avoid + ambiguous-argument-type compile errors that previously could + occur under certain compilers. + o Tweaked the .pro files in the qtsupport folders to make them + compatible with Qt 5.0. + o DenybbleizeData() now returns B_ERROR when passed a string + with an invalid (odd) length, rather than causing an + assertion failure. + * Inet_NtoA() now formats IPv4-mapped IPv6 addresses in the + IPv4 style, if (preferIPv4Style) is set to true. + * Merged in more Haiku-compatibility fixes provided by Fredrik. + +5.68 Released 3/23/2012 + - Added an optional (retPort) argument to GetPeerIPAddress(). + - Added optional "spamspersecond" and "spamsize" arguments to + hexterm, so that you can have hexterm automatically generate + random output at a specified rate for performance testing. + - Added a "quietreceive" argument to hexterm so that if you + don't want to see a printout of the bytes being received, + you don't have to. + - Added GetDistanceTo() and GetSquaredDistanceTo() methods + to the Point class. + o Privileged-address matching is now done using IPv4-style + address strings for IPv4 addresses (even when compiled with + IPv6 support enabled), per Lior's request. + * Merged in some Haiku-compatibility fixes provided by + Fredrik Modeen. + +5.67 Released 2/3/2012 + - Added a AreOutgoingMessagesIndependent() method to the + MessageIOGateway class, to allow better control over how + outgoing Messages are deflated when zlib-encoding is enabled. + - Removed some unnecessary data members from certain Message + field-object classes, to cut down on memory usage. + - Added a Queue::GetItemAtUnchecked() method. + - Added a RemoveName() to the Python Message class. + o Cleaned up the Python code to avoid pychecker warnings + o Removed Queue::GetItemPointer(). Use Queue::GetItemAt() instead. + * BitChord::ToHexString() was broken -- fixed. + * Queue::GetItemAt() would return a garbage pointer if passed + an invalid index. Fixed to return NULL (as documented) instead. + * Fixed a problem in QSignalHandler and QDataIODevice that could + occasionally cause a CPU-spin on shutdown under MacOSX/Lion. + +5.66 Released 1/5/2012 + - Added a CountedRawDataMessageIOGateway class to + RawDataMessageIOGateway.{cpp,h}. This class is the same as + its RawDataMessageIOGateway superclass, except that it is + instrumented to keep a running count of the number of raw + data bytes currently held in its output-queue. + - Added a CountedMessageIOGateway class to MessageIOGateway.{cpp,h}. + This is similar to the CountedRawDataMessageIOGateway class + described above, except it tracks Message sizes. + - Added a PopNextOutgoingMessage() virtual method to the + MessageIOGateway class, so that subclasses can override its + behavior if necessary. + - Added EventLoopCycleBegins() and EventLoopCycleEnds() + virtual method hooks to the ReflectServer class. + o AbstractMessageIOGateway::AddOutgoingMessage() is now virtual. + o Trying to use -DMUSCLE_USE_POLL under Windows without declaring + an appropriate value for -DWIN32_WINNT now gives an informative + compile-error message rather than the inscrutible errors that + would appear previously. + * Fixed minor bug in kqueue that could cause obsolete entries + to remain in the _bits Hashtable. + * Fixed a bug in the RawDataMessageIOGateway class that would cause + it to emit Messages with a what-code of 0 (rather than + PR_COMMAND_RAW_DATA) when in immediate-forward mode. + +5.65 Released 12/22/2011 + - Added experimental support for kqueue under FreeBSD/MacOSX, + enabled by defining -DMUSCLE_USE_KQUEUE on the compile line. + - Added experimental support for epoll under Linux + enabled by defining -DMUSCLE_USE_EPOLL on the compile line. + - Updated testsocketmultiplexer.cpp to make it more useful for + gathering performance statistics for the various SocketMultiplexer + implementations. + +5.64 Released 12/13/2011 + - Added a SocketMultiplexer class (util/SocketMultiplexer.{cpp,h}) + that acts as an API-agnostic wrapper around select() or poll(), + depending on whether MUSCLE_USE_POLL is defined or not. + - MuscleSupport.h now declares uintptr and ptrdiff types that + are guranteed to be the same size as a native pointer. + - Added a PseudoFlattenable base class to Flatten.h, for subclasses + that want to act like Flattenable objects but don't want to incur + the memory overhead of actually having virtual methods. + o Modified all MUSCLE code to use a SocketMultiplexer object + rather than calling select() or poll() directly. + o Added a testsocketmultiplexer.cpp test to the tests folder. + * RefCount.h now uses uintptr instead of (unsigned long) when doing + its pointer-bit-stuffing magic, to avoid pointer-truncation warnings + from MSVC. + * Message::FindFlat() now does the right thing when an object + is added to the Message as a FlatCountableRef and then + retrieved from the Message by value. + +5.63 Released 12/1/2011 + - Added a MUSCLE_USE_POLL build option which, if defined, + will cause ReflectServer::RunEventLoop() to base its + event loop around poll() rather than select(). The benefit + of this is that with poll() the FD_SETSIZE limit imposed + by select() can be be avoided. + - Made a number of previously protected methods in the + AbstractReflectSession class public. + - StorageReflectSession::PrintSessionsInfo() and + StorageReflectSession::PrintFactoriesInfo() now print + additional information about the state of the sessions/factories. + - The Thread constructor now takes an optional boolean argument, + so that you can now create a Thread that doesn't allocate a + socket-pair for inter-thread messaging purposes. This is useful + when you want to create a lot of Threads but don't need to + use socket-based inter-thread signalling and don't want to + allocate a lot of sockets. + - Added a FastClear() method to the Queue class (for when + you care more about performance than about having item + destructors get called right away). + - Added a RemoveDuplicateItems() method to the Queue class. + o Retired the MUSCLE_ENABLE_MULTICAST_API compiler flag, and added + MUSCLE_AVOID_MULTICAST_API in its place. (Which is another way + of saying that the MUSCLE Multicast API calls are now enabled + by default) + +5.62 Released 11/2/2011 + - Added a CountedObject class that various other MUSCLE classes + now subclass from. Subclassing from the CountedObject class + allows the application to efficiently keep track of the number + of objects allocated of that type, for debugging/monitoring + purposes. + - Added a PrintCountedObjectInfo() function that prints to stdout + a report of how many objects of each counted type are currently + allocated. + - Added a GetCountedObjectInfo() function that returns a Hashtable + containing all of the current CountedObject counts. + - Added support for a -DMUSCLE_AVOID_OBJECT_COUNTING compiler + definition that will turn the object-counting code into a no-op + if you want it disabled. + - The Python Message.PrintToStream() method now recursively prints + sub-Messages by default. It has arguments that can be used to + limit the depth of the recursion, if desired. + - Added XINT64_FORMAT_SPEC macros to MuscleSupport.h. + - MCRASH now calls __builtin_trap() under gcc/g++, rather than + writing to an invalid pointer. + - Added a GetGlobalObjectForType() function, which is similar + to GetDefaultObjectForType() except that the returned reference + is not read-only. + o Merged in Jean-François Mullet's patches to allow MUSCLE to be + compiled cleanly under Android. + * Fixed the code so that it now compiles cleanly under clang++ 2.1. + * String::AppendWord() no longer appends a separator string if + the word to be appended is blank. + * Added some missing string constant declarations to + storage_reflect_constants.py + * Fixed a bug an exception-string-formation line of message.py + * Fixed a bug in StorageReflectSession::NodeChanged() that would + cause incorrect PR_RESULT_UPDATE updates to be generated for + subscribers using QueryFilter objects, if the matches-filter + status of the node was changed by the node update. + +5.61 Released 9/28/2011 + - Added a GetCurrentStackUsage() method to the Thread class. + - Added a CheckThreadStackUsage() function and CHECK_THREAD_STACK_USAGE + Macro to Thread.{cpp,h} to make it easier to determine if and + when a Thread is allocating more stack space than it was allowed + to allocate. + - Added a ThreadPool class to the system folder. + - Added EINTR-proof wrapper functions to NetworkUtilityFunctions.h + for recv(), recvfrom(), send(), sendto(), read(), and write(). + MUSCLE code now calls the wrapper functions (e.g. recv_ignore_eintr) + so that incoming signals won't cause spurious errors to be returned. + - Added an Arg(const void *) method to the String class, so that + Arg() can be used to print out pointer values. + o Replaced String::AddWord() with String::AppendWord(), which is + easier to use and takes an optional separator-string argument. + * The Point and Rect implementations of String::Arg() were broken; fixed. + +5.60 Released 9/19/2011 + - Added a qt_advanced_example directory to the qtsupport folder + to demonstrate more advanced usage of the QMessageTransceiverThread + class. See the README.TXT and the diagram PNG in that directory + for details. + - Added a TamperEvidentValue class (in support/TamperEvidentValue.h) + - ThreadWorkerSession and ThreadWorkerSessionFactory objects now + have (and use) a SendMessageToSupervisorSession() method for + sending messages to their supervisor. This is more efficient + and less error-prone that calling BroadcastMessgeToAllSessions(), + and relying on non-supervisor recipients to ignore the Messages + that weren't meant for them, as was done previously. + - Added SetForwardAllIncomingMessagesToSupervisor() and + IsForwardAllIncomingMessagesToSupervisor() methods to the + ThreadWorkerSession, ThreadWorkerSessionFactory, and + MessageTransceiverThread classes. These methods govern how + Messages incoming from remote peers should be handled. + o Retired the MUSCLE_USE_IPV6 compiler option, since enabled + IPv6 support is now the default. If you want to disable IPv6 + support, you'll need to specify -DMUSCLE_AVOID_IPV6 instead. + o ThreadSupervisorSession::SendMessageToWorkers() is now + protected rather than private. + o MessageTransceiverThread::GetNextEventFromInternalThread + now passes PR_RESULT_* Messages to the caller, so that it's + possible to use a MessageTransceiverThread as if it it were + a client to the local MUSCLE thread, if desired. + o QMessageTransceiverThread now calls SetDoLogging(false) + inside CreateReflectServer() rather than afterwards, so + that subclasses can easily re-enable logging if they want to. + * Gave the README.TXT file a badly needed rewrite/update. + * Tweaked the doxygen.dox file to yield better-looking + DOxygen HTML documentation. + * genDocs.sh now automatically extracts the current + MUSCLE version number from MuscleSupport.h, so it's + no longer necessary to manually update the muscle.dox + file for each MUSCLE release. + +5.57 Released 9/16/2011 + - Added the following new compile-time constant + definitions to NetworkUtilityFunctions.h: + MUSCLE_EXPECTED_MTU_SIZE_BYTES + MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES + MUSCLE_IP_HEADER_SIZE_BYTES + MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET + These constants (particularly the last one) are + useful for deciding how many bytes of data can + be added to a UDP packet without risking packet + fragmentation. + - testudp now prints out the values of the above + constants when it is run. + - Added a SetCount() method to the NestCount class. + - HandleStandardDaemonArgs() now accepts "realtime_rr" + or "realtime_fifo" as alternatives to the pre-existing + "realtime" keyword, for those who like to specify + their scheduling algorithm explicitly. + * The PacketTunnelIOGateway constructor now uses + MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET as + its default maxTransferUnit value, rather than hard-coding + a magic value of 1500 (which was too large anyway) + * Fixed a bug that could cause Snooze64(MUSCLE_TIME_NEVER) + to return immediately, rather than sleeping forever as + intended. + * GetHumanReadableTimeIntervalString(MUSCLE_TIME_NEVER) + now returns "forever", rather than "584,942 years". + * Fixed warning in DemandConstructedObject == operator. + * Fixed warning in ZLibCodec destructor. + +5.56 Released 9/4/2011 + - Added a StorageReflectSession::PrintFactoriesInfo() + method to go with the existing PrintSessionsInfo() method. + - Added InsertItemAtSortedPosition() methods to the Queue + class, for convenience in doing insertion-sorting. + - Added SetSuggestedStackSize() and GetSuggestedStackSize() + methods to the Thread class. + - Added a IsSymLink() method to the FilePathInfo class. + - Added a qt_muscled folder to the qtsupport sub-directory. + The .pro file in this folder builds a version of muscled that + runs in a Qt GUI window. + - Added a "Clone Window" button to the qt_example application, + to make quick demonstrations of multiple-client scenarios + easier. + - The qt_example application is now more colorful -- each + client gets assigned his own (random) color. + o Added checks for (sizeof(float)==4) and (sizeof(double)==8) + to the SanitySetupSystem constructor. + o PrintHexBytes() and LogHexBytes() now take a + ConstByteBufferRef rather than a ByteBufferRef, since + they don't ever need to modify the ByteBuffer they receive. + o DataNode objects now store their ordered-child index as + a Queue of DataNodeRefs rather than a Queue of String pointers. + This avoids any possibility of dangling pointers in the index. + o DataNode::GetIndex() now returns a (Queue *) + rather than a (Queue *). + o DataNode::ReorderChild() now takes a DataNodeRef rather + than a reference to a DataNode object. + o Added util/FilePathInfo.cpp, and Moved the non-trivial + FilePathInfo method bodies into it. + o Made FileDescriptorDataIO.cpp compile (as a no-op) + under Windows, to avoid project-file confusion. + * CPULoadMeter::GetCPULoad() now uses uint64s to store tick + counts rather than uint32s, to avoid potential overflows. + * SysLog.cpp now works around the lack of a declaration + for TzSpecifiedLocalTimeToSystemTime() function when compiling + under Windows with the Mingw compiler. + * Made several other tweaks for Mingw32 compatibility. + * Fixed the qt_muscled.pro to work under Windows. + * Fixed a bug in the qt_example application that would cause + a qt_example client to disconnect and reconnect whenever + the server-name field lost focus. + +5.55 Released 7/28/2011 + - Added a "plain" keyword to hexterm's arguments; useful + if you don't want any headers in hexterm's output + - Made it possible to embed hexterm as part of a larger program. + - Added Arg() methods to the String class that take a bool, + a Point, or a Rect as an argument. + - Added a ToMixedCase() method to the String class. + - Added AddWord() methods to the String class. + - Added HeadWithDefault() and TailWithDefault() methods to the + Queue class that take a default-argument parameter. + - Added a PrintSessionsInfo() method to the StorageReflectSession + class, for quick discovery of what sessions are present and + how much RAM they are each using. + - The ObjectPool class now takes the maximum slab size as + a template argument, and has an assertion-check to make + sure that the resulting objects-per-slab count is small + enough to be tracked by a uint16 (i.e. is less than 65536) + * Fixed a crashing bug in hexterm's ASCII mode. + +5.54 Released 6/24/2011 + - Added a GetDefaultObjectForType() templated function to + MuscleSupport.h. This function returns a reference to + a default-constructed static object of the specified type. + - Added a description of the Hashtable class's feature set + to the Hashtable DOxygen documentation. + - Added a const [] operator to the Hashtable class. The + [] operator behaves the same as the GetWithDefault() method. + - Expanded and enhanced the DOxygen per-class documentation + of various classes. + o Reduced sizeof(Queue), sizeof(Hashtable), and + sizeof(ObjectPool) by removing their default-object member + items and adding calls to GetDefaultObjectForType() instead. + o Replaced the various get-default-object convenience methods + (GetEmptyString(), GetEmptyMessage(), etc) with inline + call-throughs to GetDefaultObjectForType(). + +5.53 Released 6/16/2011 + - Added TarFileWriter.{cpp,h} to the zlib folder. This class + allows for quick inline writing of .tar files to disk. + - Added a MUSCLE_MAY_ALIAS macro to MuscleSupport.h, as + an easier way to invoke gcc's __attribute__((__may_alias__)). + This macro expands to empty under other compilers. + - Added a version of the muscleCopyIn() function that returns + the copied-in value, for convenience. + - Added a DemandConstructedObject template (in + util/DemandConstructedObject.h) to allow for member variables + whose constructors aren't called until the object is actually + needed for something. + - Added a ClearAndFlush() method to the String class, for + forcing a String object to free its allocated data. + o Reduced sizeof(String), by by employing a union to reuse + the bytes used for referencing small and large char arrays. + o Reduced sizeof(Ref) by storing the boolean (doRefCount) + value in the least significant bit of the item pointer. + (this works because the item pointers are guaranteed + to be word-aligned -- if for some reason that guarantee + isn't valid for a given environment, the old method can + be reinstated by adding -DMUSCLE_AVOID_REFCOUNT_BITSTUFFING + to your compiler arguments) + o Reduced sizeof(DataNode) by making the subscribers table + demand-allocated, rather than a member variable. + o Removed the MUSCLE_CPU_REQUIRES_DATA_ALIGNMENT define, + because it is no longer used anywhere. + * Rewrote the HashtableIterator class to no longer use + type-punning trickery, so that g++ versions 4.4.0 and higher + no longer complain about strict aliasing being broken. + +5.52 Released 6/8/2011 + - Added a Tuple::Contains() convenience method. + - Added a ExecuteSynchronousMessageSend() method to the + MessageIOGateway class. This method is similar to + ExecuteSynchronousMessageRPCCall(), except that this method will + return to the caller as soon as the Message is sent, without + waiting for the server to return a reply Message. + - Added CreateSynchronousPingMessage() and IsSynchronousPongMessage() + hook methods to the MessageIOGateway class, so that subclasses can + specify alternative ping/pong Messages if they wish to. + - Added a IsCharInLocalArray() method to the String class. + - Added a IsItemLocatedInThisContainer() method to the Queue class. + - Added a GetNumUnusedItemSlots() method to the Queue class. + - Added a + operator and += operators for the ByteBuffer class. + - Made the + operators for the String class more efficient. + o Converted the ExecuteSynchronousMessageRPCCall() functions into a method + of the MessageIOGateway class, so that its behavior can be used in conjunction + with a MessageIOGateway subclass to customize their behavior, if necessary. + * Some of the methods in the Queue class could access freed memory + if passed in references to held data items, and a reallocation occurred. Fixed. + * Some of the methods in the String class could access freed memory + if passed in references to their own string, and a reallocation occurred. Fixed. + * Some of the methods in the ByteBuffer class could access freed memory + if passed in a pointer to their own bytes, and a reallocation occurred. Fixed. + * Fixed a bug in StorageReflectSession::NodePathMatcher::CheckChildForTraversal() + that could cause the traversal callback to be called with an invalid DataNode, + likely causing the callback to crash. This bug was a regression introduced + in the 5.51 release. + * CalculateChecksumForFloat() and CalculateChecksumForDouble() would return + different checksum values for 0.0 and -0.0, even though the == operator + considers them equivalent. Fixed. + +5.51 Released 5/23/2011 + - Added convenience methods to the Queue class: HeadWithDefault(), + TailWithDefault(), RemoveHeadWithDefault(), RemoveTailWithDefault(), + and RemoveItemAtWithDefault(). These methods are all safe to call + even when the Queue is empty. + - Added a SLIPFramedDataMessageIOGateway class to the iogateway + folder. As the name suggests, it is used to do SLIP encoding + and decoding on outgoing and incoming raw data, respectively. + - Added an AppendByte() method to the ByteBuffer class, for convenience. + - The WARN_OUT_OF_MEMORY macro now prints the number of bytes that + the failed memory allocation attempted to allocate. + - Added SetExpendable() and IsExpendable() methods to the + AbstractReflectSession class, to help influence which sessions + will be thrown under the bus in a low-memory situation. + o The QueryFilter::Matches() methods have been modified to take a + (ConstMessageRef &) argument instead of (const Message &), so that + it is now possible to create a QueryFilter subclass that modifies + the data it returns. (Not that any of the QueryFilter subclasses + included in the MUSCLE distribution currently do this) + o Removed the optional MemoryAllocator pointer argument from the + ReflectServer constructor. Now ReflectServer simply accesses the + installed MemoryAllocator object directly (via + GetCPlusPlusGlobalMemoryAllocator()) when necessary. + * When MUSCLE_USE_IPV6 is defined, SharedFilterSessionFactory now + compares ip_address objects using ip_address::EqualsIgnoreInterfaceIndex() + instead of the == operator. This avoids spurious mismatches between IPv4 + addresses that have their interface index set, and those that don't. + * UnparseArgs(const Message &) wasn't escaping quote marks embedded + in the strings in the Message. Fixed. + * UnparseArgs(const Message &) wasn't properly handling multiple values + filed under the same field name. Fixed. + +5.50 Released 4/18/2011 + - Added a qt_muscled_browser subfolder to the qtsupport folder. qt_muscled_browser + is a GUI MUSCLE database browser, based on code contributed by Jean-François Mullet. + - Added GetFirstKeyWithDefault(), GetLastKeyWithDefault(), GetFirstValueWithDefault(), + and GetLastValueWithDefault() convenience methods to the Hashtable class. + - Added a RemoveWithDefault() convenience method to the Hashtable class. + - Added a CountAverageLookupComparisons() method to the Hashtable class, + to help determine the hashing performance of various hash functions. + - Added a -DMUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS preprocessor define, + which will cause the Hashtable reallocator to calculate the hashtable's + average lookup comparisons value after every hashtable resize, and + complain if the average lookup count is too high. + - Added 3-argument constructors to the AndOrQueryFilter and NandNotQueryFilter classes. + - Added regex/FilePathExpander.{cpp,h}. These files contain a single function, + ExpandFilePathWildCards(), which expands wildcarded file paths into a list of + non-wildcarded paths, similar to shell expansion. + - Added test/testmatchfiles.cpp, a unit test for the ExpandFilePathWildCards() function. + o Moved the boolean "isAnd" argument to the front of the arguments list for the + AndOrQueryFilter constructors. + o Removed the -DMUSCLE_COLLECT_HASHTABLE_COLLISION_STATISTICS preprocessor + define, since the new -DMUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS replaces it. + o PortableReflectClient now includes a string showing the current time when you + 's'et a node, so it's easier to see the node change in other clients. + * FilePathInfo::SetFilePath(NULL) would crash. Fixed. + * ChildProcessDataIO::System() and ChildProcessDataIO::LaunchChildProcess() + weren't NULL-terminating their argv arrays. Fixed. + +5.43 Released 3/29/2011 + - Added GetSocketBlockingEnabled() function to NetworkUtilityFunctions.{cpp,h} + - Added GetSocketNaglesAlgorithmEnabled() function to NetworkUtilityFunctions.{cpp,h} + - Added GetSocketSendBufferSize() function to NetworkUtilityFunctions.{cpp,h} + - Added GetSocketReceiveBufferSize() function to NetworkUtilityFunctions.{cpp,h} + - Added GetUDPSocketBroadcastEnabled() function to NetworkUtilityFunctions.{cpp,h} + - hexterm now batches its stdin input together to send in fewer buffers, if possible. + - Added a libmuscle.a target to the Makefile in the server folder. + - Directory::MakeDirectory() now takes an optional third argument to specify + whether the pre-existence of the requested directory should be considered an error. + - Added a qt_example sub-directory to the qtsupport folder. This directory + contains buildable code for a sample Qt-based GUI MUSCLE client. + * FilePathInfo now handles file paths ending in a slash properly, even if the + filesystem object located at that path is not a directory. + * Fixed hexterm so that it no longer exits prematurely when reading stdin from a file. + * ChildProcessDataIO::System(const Queue &) wasn't declared static. Fixed. + +5.42 Released 2/26/2011 + - Added SetEnabled() and IsEnabled() methods to the + DetectNetworkConfigChangesSession class. + o Removed the ability to set String objects using an argument of type + 'char', because having it was preventing the compiler from catching + errors where an integer is assigned to a String. Use s = String(&c, 1) + instead. + * The SharedMemory wasn't interpreting the return value of shmat() + calls correctly. Fixed. + * Fixed the access privileges of the Hashtable classes so that they now + compile under g++ 4.4.x. + +5.41 Released 2/2/2011 + - Added a ParseBool() function to MiscUtilityFunctions.{cpp,h} + - Added convenience implementations of PrintHexBytes() and LogHexBytes() + that accept ByteBuffers and ByteBufferRefs. + - PrintHexBytes() and LogHexBytes() can now gracefully accept NULL + pointers without crashing (they will just print out "NULL buffer" + instead of any hex bytes) + - printsourcelocations now filters out commented-out calls to LogTime(). + - Hashtable.h now declares a Void class that can be used as a + placeholder for Hashtables that need only keys, not values. + - Added a PutWithDefault() method to the Hashtable class. + - Added a CanRegexStringMatchMultipleValues() function to StringMatcher.cpp. + - Added CaseInsensitiveNumericAwareStringCompareFunctor and + NumericAwareStringCompareFunctor classes to String.h + - printsourcelocations now sorts its output by key before emitting it. + o Added \r and \n to the list of characters that StringTokenizer + will treat as word-separators by default. + * PrintHexBytes() and LogHexBytes() were incorrectly writing some + of their decorative output to stdout, instead of to the specified + FILE stream. Fixed. + * Added some #ifdefs to hexterm.cpp and some logic to the tests Makefile + so that when hexterm is built as part of a Meyer software release, + it will show the standard Meyer software version string. + * ReflectServer::DisconnectSession() now calls ShutdownIOFor() on the + disconnected session, even if the session is to become a lame duck. + That way TCP connections are guaranteed to be disconnected quickly. + * PulseNode::GetCycleStartTime() now returns the correct value even + for PulseNodes that are children of other PulseNodes. + * Hashtable now uses == operator on user-values, not the != operator. + * The Hashtable class's Get(key), GetFirstValue(), and GetLastValue() + methods were not properly enforcing const-correctness. Fixed. + +5.40 Released 12/20/2010 + - Added various time-units-conversion functions to TimeUtilityFunctions.h + - Added a few time-related constants to TimeUtilityFunctions.h + - Added a DetectNetworkConfigChangesSession class, which can be used + to install a handler that is called whenever the host computer's + networking configuration has changed. + - Added testnetconfigdetect.cpp to the tests folder, as a simple + test/example of using the DetectNetworkConfigChangesSession class. + - Added a TelnetPlainTextMessageIOGateway class, that works similarly + to PlainTextMessageIOGateway, except that it filters out any + telnet-protocol control codes that get sent from the client. + o Made it a no-op to call AbstractReflectSession::EndSession() + on a session that isn't attached to a server. (Previously it + would trigger an assertion failure, but an unattached session + is already effectively ended so there's no point in crying over it) + o Code cleanup: Magic-number time-unit conversions in several places + now use the new time-conversion functions instead, for clarity. + o Simplified the SharedUsageLimitProxyMemoryAllocator. It no longer + maintains a running sum of the total memory usage of the daemon + set, but rather recalculates the current sum when needed. + * The StringMatcher class's copy constructor wasn't initializing + its _bits field to zero, which could cause undefined behavior. Fixed. + * StringMatcher::SetPattern() didn't clear the negate-bit when + setting a non-simple (aka full regex) pattern. Fixed. + +5.35 Released 12/5/2010 + - Added a MoveToPosition(key, idx) method to the Hashtable class. + - Added a InsertOrderedChildNode() utility method to the + StorageReflectSession class. + - hexterm now includes a version number in its LogUsage() text. + - ChildProcessDataIO::Launch*() can now be told to only capture + output to stderr, or stdout + - Added a GetTotalDataSize() method to the ObjectPool class. + - Added a GetNumAllocatedItemSlots() method to the ObjectPool class. + o Changed QueryFilterRef arguments to ConstQueryFilterRef where appropriate. + o Replaced the (usePty) boolean in ChildProcessDataIO::Launch*() + method with a launchBits bit-chord, so that multiple options + can be specified. + o Renamed Hashtable::GetNumAllocatedSlots() to GetNumAllocatedItemSlots(). + * GetSystemMemoryUsagePercentage() was not being calculated + accurately under MacOS/X. Fixed. + * ZLibUtilityFunctions.cpp now installs a cleanup-callback when + necessary, so that any privately allocated ZLibCodec objects + will be deleted as part of the shutdown sequence and thus won't + show up in valgrind as possibly-leaked-memory anymore. + * ByteBuffer::SetBuffer() now deallocates the current buffer if + the new data size is less than half of the current data size, + to avoid wasting memory. + +5.34 Released 10/19/2010 + - Added an optional (maxAsyncConnectPeriod) argument to the + AddNewConnectSession() and AddNewDormantConnectSession() + methods of the AbstractReflectSession and MessageTransceiverThread + classes. + - Added SetMaxAsyncConnectPeriod() and GetMaxAsyncConnectPeriod() + methods to the AbstractReflectSession class. + - Added an optional MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS + constant to the set of compiler -D flags that the muscle code + looks for during compilation. + - Added Strcasecmp() and Strncasecmp() wrapper functions to + MiscUtilities.h, to hide Windows' inconsistent function names. + - muscled and the programs in the tests folder are now compiled + with the -DMUSCLE_USE_IPV6 flag enabled. + - Added support for "disablestdout" and "disablestderr" startup + arguments, that cause the program to close its STDOUT_FILENO + and STDERR_FILENO file descriptors, respectively (and thus + suppress its output from then on) + - DebugTimer can now accept a negative logLevel parameter, which + will force it to output using printf() instead of LogTime(). + - Added a GetSystemMemoryUsagePercentage() function to + MiscUtilityFunctions.{cpp,h} + +5.33 Released 8/31/2010 + - Added an optional (argIdx) argument to ParseConnectArg() + and ParsePortArg() in MiscUtilityFunctions.{cpp,h} + - Added a udpproxy program to the tests folder. This can + be used to forward UDP packets to and from a further + destination. + - Added a NumericAwareStrcasecmp() function to String.{cpp,h}. + - Added NumericAwareCompareTo() and NumericAwareCompareToIgnoreCase() + methods to the String class. + - Added SetMask() and GetMask() methods to the NumericQueryFilter + class, to allow bitfield masking all numeric QueryFilters. + - Added a printtypecode program to the test folder, for quickly + getting the ASCII representation of a 32-bit type code. + o Changed String::CompareToIgnoreCase() to call strcasecmp() + rather than calling ToUpper() on both strings. + o Removed the OP_CONTAINS_BITS and OP_DOESNT_CONTAIN_BITS operators + from the NumericQueryFilter class, as that functionality can + now be handled more generally via the SetMask() call instead. + * Inet_PtoN now sets the ai_flags value to AI_NUMERICHOST under + Windows, so that non-numeric address strings (e.g. "") are no + longer possibly misinterpreted as valid IP address strings. + * ParseArgs() now un-escapes any escaped quote marks. + * The micromuscle Java classes were still packaged under + com.lcs.*. Changed them to com.meyer.* + +5.32 Released 7/22/2010 + - Added a GetTimeStampForHumanReadableTimeValues() function + to SysLog.{cpp,h}. This function does the inverse operation + of GetHumanReadableTimeValues(). + - Added a HumanReadableTimeValues::ToString() method, + for convenience. + - Added SetFSyncOnClose() and IsFSyncOnClose() methods to the + FileDescriptorDataIO class. + - Added a SortFieldNames() method to the Message class. + - Added FindMatchingNode() and FindMatchingNodes() convenience + methods to the StorageReflectSession class. + - Added a serialproxy utility to the tests folder. This program + exports a local serial port as a TCP port. + - Message::FieldsAreSubsetOf() is now a public method. + o Replaced GetServerUptime() with GetServerStartTime(), so + that the returned value can remain meaningful even if stored + for a period of time. + * Threads in a Java com.meyer.muscle.thread.ThreadPool object + now exit cleanly when they are interrupted (e.g. by an + applet shutdown). Previously they would just ignore the + InterruptedException and stick around, gumming things up. + * The interface-0-send-detect code no longer prints an error + if the destination address is (invalidIP), on the assumption + that the UDP socket was previously connected using + SetUDPSocketTarget() in that case. + * ReceiveDataUDP() now returns correct source-address/port + information even when receiving IPv4 UDP packets while + compiled with the MUSCLE_USE_IPV6 flag defined. + +5.31 Released 6/7/2010 + - Added LaunchIndepentChildProcess() static methods to the + ChildProcessDataIO class, for quick fire-and-forget style + launching of child processes. + - Added an optional maxWaitTimeMicros argument to the + ChildProcessDataIO::System() methods. + - Added a QDataIODevice adaptor class to the qtsupport folder. + - Made the MUSCLE GetCurrentThreadID() function publicly + available (declared in system/SetupSystem.h). + - Added a MUSCLE_ENABLE_DEADLOCK_FINDER flag. + - The deadlock-finder no longer requires manual insertion + of PLOCK and PUNLOCK macros everywhere; instead, that + functionality is conditionally compiled into the + Mutex class itself. + - Added code to deadlockfinder.cpp to detect inconsistent + sequences and print error messages about them, instead of + just printing out all sequences and leaving it up to the + user to detect any problems. + o The deadlock-finder generation code now stores up its output + in RAM until the process is exiting, and dumps it to stdout + at that time. That way race conditions in accessing stdout + are avoided, so the output won't be garbled. + o LockLog() and UnlockLog() now lock/unlock a separate lock + that is dedicated to the log callbacks only, instead of + calling through to the global muscle lock. + o InflateBuffer() and DeflateBuffer() now use their own + Mutex also, for reasons similar to those listed above. + * The Windows implementation of ChildProcessDataIO now marks + its master/slave notify sockets as non-inheritable, so that + subsequent child process won't hold them open unexpectedly. + +5.30 Released 5/7/2010 + - Added proper copy/assignment/equality operators to the + StringMatcher class, and added a HashCode() method to + it so that it can be a key in a Hashtable. + - Added OP_CONTAINS_BITS and OP_DOESNT_CONTAIN_BITS operators + to the NumericDataQueryFilter family of classes, so that + you can do some bit-chord logic in a QueryFilter. + - Added an Area() method to the Rect class. + - Added optional checksum calculation and printing to hexterm. + - Added a GetDefaultObject() method to the ObjectPool class + that gives read-only access to the ObjectPool's persistent + default-constructed object. + - Added a GetEmptyByteBuffer() function to ByteBuffer.{cpp,h} + o GetEmptyMessageRef() now returns a ConstMessageRef instead + of a MessageRef, to ensure that the returned Message is + not (easily) modified. + o GetEmptyByteBufferRef() now returns a ConstByteBufferRef + instead of a MessageRef, to ensure that the returned + ByteBuffer is not (easily) modified. + o Simplified the ObjectPool class to make it less error-prone + and easier to use. The function callbacks are gone; instead, + recycled objects are reset to their default state simply by + using their assignment operator to set them equal to a + default-constructed object. + o IsRegexToken() no longer considers ':' to be a regex token. + This change is because ':' is often used in otherwise non-regex + strings, and is never the only regex character in a regex string. + * Fixed some compiler warnings under MSVC 2008 + * Fixed a bug that would cause subscriptions not to be properly + unsubscribed in some cases. + +5.23 Released 4/22/2010 + - The Parse*() functions in MiscUtilityFunctions.{cpp,h} now take + an optional (caseSensitive) boolean argument. If left false + (the default), parsing will be case sensitive, as it was in + previous versions. If set to true, then arguments will be left + in their original case and not forced to lower case. + - Added information to the leaked-ObjectPool-Ref assertion failure + error message, so that now the type and the address of the leaked + reference(s) are printed out as part of the assertion failure. + - Rewrote the ObjectNode class to use uint16 indices instead of + pointers, to reduce memory usage (especially on 64-bit systems) + - Added a Clear() convenience method to the Socket class. + - Added Append() and Prepend() methods to the String class that + take a char as an argument. + o Changed the (recurse) argument in Message::PrintToStream(), + Message::ToString(), and Message::AddToString() to a + (maxRecurseLevel) uint32 argument instead, so that user-specified + maximum recursion depths can be supported. + o Re-organized the GlobalSocketCallbacks so that they all happen + immediately after the socket is created. + o Changed the "Couldn't disable V6-only mode" message to MUSCLE_LOG_DEBUG. + o Made the Message(uint32) constructor explicit. + * Tweaked the code so that hexterm once again compiles under Windows. + * Tweaked NetworkUtilityFunction.cpp to build again even when + MUSCLE_ENABLE_MULTICAST_API is not defined. + * MUSCLE sockets now have IPV6_V6ONLY explicitly disabled when they + are created, so that IPv4-mapped sockets will work even under + operating systems that disable them by default (read: Windows 7) + * The MCRASH macro no longer calls FatalAppExit() under windows; + instead it just writes to a NULL pointer. That way if there is + a crash handler installer, it will run. + +5.22 Released 3/3/2010 + - Added SetSocketKeepAliveBehavior() and GetSocketKeepAliveBehavior() + functions to NetworkUtilityFunctions.{cpp,h}. Note that these functions + are only available if MUSCLE_ENABLE_KEEPALIVE_API is defined. + - Added SetGlobalSocketCallback() and GetGlobalSocketCallback() to + NetworkUtilityFunctions.{cpp,h}, to allow all TCP sockets in the + process to be set up in a similar way, if desired. + - Added a portablereflectclient.vcproj file to the tests folder, so that + portablereflectclient can be compiled under Microsoft Visual Studio. + - The AtomicCounter class now uses the OSAtomicCounter.h atomic counter + API when compiled under MacOS/X. + - Added an optional (maxReplaceCount) arg to the String::Replace() methods. + - Added a ServerComponent::IsFullyAttachedToServer() method, which returns + true iff AttachedToServer() has already returned success, and + AboutToDetachFromServer() hasn't been called yet. + o Removed MUSCLE_CUSTOM_ATOMIC_TYPE support from the AtomicCounter class, + since it wasn't useful and was cluttering up the code. + o Rewrote the CalculateChecksum() and CalculateChecksumFor*() methods + to call through to CalculateHashCode(), since the existing checksum + algorithms were not very good at avoiding checksum collisions. + * Updated win32client.vcproj and win32client.cpp to build properly again. + * Fixed a couple of typos in AsyncDataIO.cpp. + * GetFileLogName() is no longer included in the MUSCLE_INLINE_LOGGING + headers in SysLog.h, since its inclusion caused chicken-and-egg problems. + * Added inline versions of PrintStackTrace(), GetStackTrace(), + GetLogLevelName(), and GetLogLevelKeyword(). + * Made all of the inline log functions into static inlines. + +5.21 Released 1/21/2010 + - Added UnparseFile() functions to MiscUtilityFunctions.{cpp,h}. + These convert a ParseFile()-style Message back into a text file. + - Added printsourcelocations.cpp to the tests folder. This program + will scan a source code directory tree and print out the 4-letter + source location codes corresponding to all calls to LogTime(), + along with their human-readable location and the line of source code. + * The source code location alphabet had the numeral '5' in it, which + was not supposed to be there (it looks too much like 'S'). Removed it. + * Fixed a bug where UnparseArg("arg= val") wouldn't parse correctly. + * Fixed a potential race condition in ObjectPool::Drain(). + * Some dependencies were missing from the VC++ project files; added them. + * Converted RefCount::CheckedGetItemPointer() from a method into + a stand-alone inline function, because calling methods on a NULL + pointer is specified as causing undefined behavior in C++, and + therefore having CheckedGetItemPointer() as a method is unsafe. + * Removed unnecessary files from the vc++ folder, updated vc++/README.txt + +5.20 Released 12/29/2009 + - PrintStackTrace() is now implemented under MSVC/Win32, using + Jochen Kalmbach's StackWalker class. + - The debugcrashes command line argument is now supported under + MSVC/Win32; with it specified, crashes will cause PrintStackTrace() + to be called, and therefore a stack trace to be printed to stdout. + - Added a GetTotalDataSize() method to the Hashtable and Queue classes, + so that it's possible to query how much RAM the container is using. + - The Message::*Flat() methods are now templated so that the object + you pass in to them doesn't have to be a subclass of Flattenable... + it just have to have necessary methods declared. This lets you + save memory on small flattenable objects since they no longer + need a vtable pointer. + - Added a GetFlattenedByteBufferFromPool() method to ByteBuffer.h. + - The HashtableEntry class now stores references to other + HashtableEntries as uint32 slot indices rather than pointers, so + that its memory footprint doesn't increase on 64-bit systems. + - Added a GetRawArrayPointer() method to the Queue class to + allow direct/low-level access to the Queue's items array. + - UnparseArgs(const Queue &) now takes optional startIdx and + afterEndIdx arguments. + o Rewrote/simplified the HashtableIterator class: Removed all of + the HasMore*(), GetNext*(), and PeekNext*() methods. The only + methods remaining are GetKey(), GetValue(), HasData(), + operator++(), and operator--(). + o Rewrote/simplified the MessageFieldNameIterator class: Removed + the HasMoreFieldNames(), GetNextFieldName() and PeekNextFieldName() + methods. The only methods remaining are HasData(), GetFieldName(), + operator++, and operator--. + o The String, Point and Rect classes no longer derive from + Flattenable, and no longer have any virtual methods. + o Removed the (initialSize) constructor argument from the Queue + and PathMatcherQueue classes, since it was rarely used and added + 4 bytes to the size of the Queue class. + * debugcrashes is also now enabled under OS/X. + * Rewrote the POSIX implementation of ChildProcessDataIO so that + the marshalling of the child process's arguments array is done + in the parent process rather than between fork() and execve(); + that avoids potential problems with doing dynamic memory allocation + during that awkward phase of the child's development. + * Added a work-around in SendDataUDP() for a misfeature of MacOS/X + (and possibly other IP stacks) where sendto() will send on the + default interface even when the destination address clearly + specifies another interface. + * Replaced calls to gmtime() with gmtime_r(), for thread safety. + +5.11 Released 11/23/2009 + - Added GetHumanReadableTimeIntervalString() to SysLog.h. + - Added GetEmptyByteBufferRef() to ByteBuffer.{cpp,h}. + - Added a DataNode::GetChild() method that returns the result as + a return value rather than as a by-reference parameter. + - ParseHumanReadableTimeIntervalString() can now correctly parse + multiple-clause time interval strings of the type generated by + GetHumanReadableTimeIntervalString(). + - Added a GetPulseParent() method to the PulseNode class. + * Removed the MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF bit from the + DEFAULT_MUSCLE_ROUTING_FLAGS_BIT_CHORD constant, since including + this bit made the sessions' default routing behavior different + from what it was (and is) documented to be. + * BatchOperator now calls BatchEnds() from within the batch context + instead of after the batch context has ended, for consistency + with the semantics of BatchBegins(). + o Rewrote the Windows implementation of the Mutex class to use + critical sections rather than locking a Windows Mutex directly, + for better performance. + o Renamed the Python files in the python folder from e.g. CamelCase.py + to e.g. lower_underbar_case.py, per the Python Style Guide. + o Made CalculateHashCode() and CalculateHashCode64() non-inline + functions, since they are rather large to be inlined. + * Rewrote the Xenomai implementation of GetRunTime64() to call + rt_timer_tsc() instead of rt_timer_read(), so that the value + returned is nanoseconds-since-boot, not nanoseconds-since-1970. + * The "realtime" command line argument support now memsets() the + sched_param struct to zero before filling it out, just in case + it has other members besides sched_priority. + * Fixed a bug in the Hashtable class where copying one Hashtable + to another could cause multiple identical entries to appear + in the target table's iteration list. + +5.10 Released 10/12/2009 + - Added a HashCode() method to the Tuple, Point, and Rect classes, + so that they can now be used as keys in a Hashtable. + - Added a Hashtable::Intersect() convenience method. + - Added SetRoutingFlag() and IsRoutingFlagSet() methods to the + DumbReflectSession class. These flags allow subclasses of + DumbReflectSession to change default/unrecognized routing + behavior to be more appropriate for their needs, if necessary. + - Added two new parameters that can be set via + PR_COMMAND_SETPARAMETERS and got via PR_COMMAND_GETPARAMETERS. + They are PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS and + PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY and they control the + similarly named bits in the DumbReflectSession class. + - Added a (reconnectViaTCP) argument to the AbstractReflectSession:: + SetAsyncConnectDestination() method, so you can specify whether + Reconnect() should connect to the supplied address via a standard + TCP connection, or not. + - The file log name (as set by the "logfile" command line argument + or SetFileLogName()) is now interpreted by + HumanReadableTimeValues::ExpandTokens(), so it can use any of + the standard tokens and they will be expanded to their values + at the time the log file is created. + - File logging now supports maximum log file sizes (via the + "maxlogfilesize=n" keyword and via the new SetMaxLogFileSize() + and GetMaxLogFileSize() functions). When a log file becomes + larger than the specified size, it will be closed and a new + log file opened. + - Log files closed because they are too large can be optionally + gzip-compressed (if the "compresslogfiles" keyword is specified, + or via the new SetFileLogCompressionEnabled() function) + - Added an optional "maxnumlogfiles" argument (and associated + SetMaxNumLogFiles() and GetMaxNumLogFiles() functions) that + allow you to specify that old log files should start to be + deleted after a certain number of log files have been created. + - Added an optional "oldlogfilespattern" argument (and associated + SetOldLogFilesPattern() function) that lets you specify the + pattern of file-paths that can be safely assumed to be old log + files from previous instances of the current application, and + deleted if necessary. + - Added a SetFile() method to the FileDataIO class. + - Added AppendBytes() methods to the ByteBuffer class. + - Added InflateByteBuffer() and DeflateByteBuffer() functions + to ZLibUtilityFunctions.{cpp,h}. + - Added raw-pointer versions of the Inflate(), Deflate(), and + GetInflatedSize() methods of the ZLibCodec class. + - Set EXTRACT_ALL to yes in the muscle.dox file, so that the + non-member functions will show up in the DOyxgen documentation. + - Added Doxygen groups around the non-class APIs, so that they + show up as Modules in the Doxygen HTML. + - Added a brief Doxygen \mainpage introduction blurb. + o Changed the MUSCLE_VERSION macro in support/MuscleSupport.h + from hexadecimal to decimal, so it can be more readable. + o FileDataIO ctor's argument now defaults to NULL. + o Log() and LogTime() no longer inhibit re-entrant calls during + log callbacks. This allows the callbacks to do logging themselves, + although they do need to be careful to avoid infinite recursions. + o GetNumAvailableBytes(), GetMaxNumBytes(), and GetNumUsedBytes() + now return uint64's instead of uint32's, so that they can + deal with more than 4 gigabytes of RAM allocation. + o DumbReflectSession::SetReflectToSelf() and GetReflectToSelf() + are now deprecated inline synonyms for + SetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF) and + GetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF), respectively. + o ParseArgs() no longer strips leading dashes off of the arguments + it parses, because doing that made it impossible to pass negative + numbers as command line arguments. + o Added a PrintStackTrace() comment to SysLog.cpp for easy + copy-and-paste to third party code when necessary. + o Moved the HumanReadableTimeValues class and its related + functions out of MiscUtilityFunctions.{cpp,h} and into + SysLog.{cpp,h}, so that programs that use the system log + but not the other functions in MiscUtilityFunctions.cpp do + not need to link in MiscUtilityFunctions.cpp. + o Moved Atoll() and Atoull() from MiscUtilityFunctions.{cpp,h} + to SetupSystem.cpp and MuscleSupport.h, for the same reasons. + * Fixed some printf() formatting warnings that occurred when the + C code (MiniMessage, etc) was compiled on a 64-bit OS. + * Changed a constant in SharedUsageLimitProxyMemoryAllocator + from MUSCLE_NO_LIMIT to ((size_t)-1) so that it would work + properly under 64-bit OS's. + * NetworkInterfaceInfo::ToString() was broken. Fixed. + +5.00 Released 9/16/2009 + - Added a MUSCLE_VERSION define that is the numeric counterpart + to the MUSCLE_VERSION_STRING define. This makes it easier to + write code that will build against multiple MUSCLE versions, if + necessary. + - Split DataIO::GetSelectSocket() into two methods, GetReadSelectSocket() + and GetWriteSelectSocket(). This way a DataIO can use two different + file handles for event-catching if it wants to. + - Split AbstractReflectSession::GetSessionSelectSocket() into + GetSessionReadSelectSocket() and GetSessionWriteSelectSocket(). + - Reimplemented the ARRAYITEMS macro in C++ land to be a templated + function instead. This has the advantage of making it a compile-time + error if you try to call ARRAYITEMS() with a pointer argument. + - muscleCompare() now depends only on the less-than operator. + Before it depended on less-than and greater-than working as expected. + - Removed the DECLARE_HASHTABLE_KEY_CLASS* macros, since they are no + longer needed. Appropriate HashFunctors are now selected automatically + through SFINAE magic; all the user has to do is add a + "uint32 HashCode() const" method into his key class and it will be used. + - HashFunctor classes now also have a AreKeysEqual() method for determining + whether two keys are equivalent or not in a type-appropriate manner. + - Added support for a MUSCLE_COLLECT_HASHTABLE_COLLISION_STATISTICS #ifdef + which can be used to test the efficacy of different hash functions. + - Added CalculateHashCode() and CalculateHashCode64() functions to + MuscleSupport.h. These functions return hash codes for any array of + bytes using Austin Appleby's MurmurHash2_Aligned algorithms. + - The Hashtable class no longer chooses its array sizes based on prime + numbers; instead it now relies on a better hash function (MurmurHash2.0) + that will hash values well no matter what the array size is. + - Optimized the String class's reallocation strategy to conserve memory + better when dealing with very long strings. + - Added a GetNumAllocatedBytes() method to the String class. + - Added IsValid() and IsNull() convenience methods to the ConstRef class. + - Added a version of DataNode::GetNodePath() that returns a String. + - Added ToCaseInsensitive(const String &) to regex/StringMatcher.h + - Added a PutOrRemove() method to the Hashtable class that takes + a pointer to the value (NULL pointer means remove). + - Added a GetAncestorNode() convenience method to the DataNode class. + - Added ++ and -- operators to the String class. (they add a space + and remove the last character in the string, respectively) + - ParseHumanReadableTimeIntervalString() now recognized special strings + "forever", "never", and "infinite" and will return MUSCLE_TIME_NEVER in + response to any of those keywords. + - ParseFile() can now accept its file data in the form of a String + as well as a (FILE *). + - Added BroadcastToAllSessionsOfType() templated methods + to the AbstractReflectSession and ReflectSessionFactory classes. + - Added GetWithDefault() methods to the Queue class. + - Added a GetDataIO() convenience method to the AbstractReflectSession class. + - Added a GNII_INCLUDE_NONLOOPBACK_INTERFACES, + GNII_INCLUDE_ENABLED_INTERFACES, and GNII_INCLUDE_DISABLED_INTERFACES + flags GetNetworkInterfaceInfos() function's flag argument options. + - Merged in Monni's patch to make the B_SWAP_*() macros call through to + MSVC's _byteswap_*() functions on versions of MSVC that support that. + - Added a Contains(const ItemType &) convenience method to the Queue class. + - AbstractMessageIOGateway now derives from AbstractGatewayMessageReceiver, + so that you can link the input of one gateway directly to the output + of another if you want to. + - Added a SetFilterForEntry() method to the PathMatcher class so that + existing PathMatcherEntry objects can have their filters updated without + throwing the PathMatcher class's filter-count out of whack. + - Added ToString() methods to the PathMatcherEntry and StringMatcherQueue + classes, to better facilitate debugging. + o Removed CStringHashFunc() and CStringHashFunc64() functions; any code + that was previously using those should call CalculateHashCode(s,strlen(s)) + instead. + o Split the Hashtable class auto-sort functionality out into subclasses. + The Hashtable class no longer supports auto-sort; instead you now + instantiate an OrderedKeysHashtable or an OrderedValuesHashtable + if you want a table that keeps its data sorted. + o All Hashtable sorting is now done via CompareFunctors instead of + via function pointers. + o Rewrote the Queue::Sort() method to used a templatized CompareFunctor + object instead of a function pointer. This allows the compiler to + inline the comparisons for better performance, and also frees the + programmer from having to manually specify a comparison function in + most cases. + o Remove the Compare*() callback functions from MuscleSupport.h because + they are no longer needed (use functors instead) + o Made PulseNode::InvalidatePulseTime() public instead of protected. + o Made the object-capturing constructors in the RefCount and ConstRefCount + classes explicit, so that object-capture can't happen without the + programmer's explicit knowledge. + o ExecuteSynchronousMessageRPCCall() now returned an empty Message + if it connects successfully but receives no reply. That way the + caller can differentiate that case from the could-not-connect case. + o Removed UNLESS() macro, since it was never used and was bad style. + o FindFirstSessionOfType() and FindSessionsOfType() template methods + now take as their template argument, not + o Removed some characters from the source-location-code alphabet to + reduce the risk of inadvertent swearing in the lock file. + * Rewrote the Windows implemetation of StdinDataIO (again) so that closing + a StdinDataIO object no longer tries to shut down the Stdin I/O thread. + That appears impossible to do reliably under the awesome Windows API, + so now the Stdin I/O thread is a singleton that always runs (safely + minding its own business) until stdin closes. + * Fixed a typo in MessageTransceiverThread.py + * ExecuteSynchronousMessageRPCCall() now includes the TCP-connect time + in its calculations regarding the timeout period. + * ParseHumanReadableTimeInterval() now defaults to seconds when + no units are specified (previously it would return zero in that case). + * String::operator-(const char) had a bug where it would read one + byte too many as part of its memmove() call. Fixed. + * Atoll() and Atoull() weren't handling invalid strings + (with preceding non-digit characters) properly. Fixed. + * Added a _M_AMD64 case ot the LITTLE_ENDIAN byte ordering detection, + per Mika Lindqvist's suggestion. + * GetNetworkInterfaceInfos() no longer returns results for interfaces + that are not currently up/enabled. + * SharedFilterSessionFactory::CreateSession() now assumes that any + connection coming in over the Loopback device is a local connection, + even if the source IP address isn't recognized as a local address + (a situation that can happen on a Mac when an interface has been + disabled) + * Fixed a bug in AbstractMessageIOGateway::ExecuteSynchronousMessaging() + that would cause it to sometimes return B_ERROR even when it had, in + fact, successfully completed. + * In MUSCLE_USE_IPV6 mode, IsIPv4Address() no longer returns true for + IPv4 addresses ::1 and ::, as these are commonly used IPv6 addresses. + * Fixed two bugs that could cause StorageReflectSession do do subscription + updating incorrectly when using QueryFilters in the subscriptions. + +4.63 Released 7/17/2009 + - When compiled with MUSCLE_USE_IPV6 enabled, MUSCLE now does automatic + transparent remapping of IPv4-compatible IPv6 addresses into IPv4-mapped + IPv6 addresses. This allows MUSCLE servers to be 100% compatible with + both IPv4 and IPv6 clients (on platforms that support dual stacks, of + course) without any additional effort on the programemr's part. + - Added SetAutomaticIPv4AddressMappingEnabled() and + GetAutomaticIPv4AddressMappingEnabled() calls to + NetworkUtilityFunctions.{cpp,h}. + - Added CAdd*() and CPrepend*() convenience methods to the Message + API, to the common "add value to Message unless it's the default value" + idiom quicker and easier to express. + - Added Contains() convenience methods to the String class. + - Added an optional (optRetTotal) argument to + SharedUsageLimitProxyMemoryAllocator::GetCurrentMemoryUsage(). + - Added a ParseHumanReadableTimeIntervalString() convenience function + to MiscUtilityFunctions.cpp. + - Added an static Exists() method to the Directory class. + - Added a PutOrRemove() convenience method to the Hashtable class. + - Added an IsValidIP() function to NetworkUtilityFunctions.{cpp,h}. + - GetNextEventFromInternalThread() now has an optLocation parameter that + can be used to find out the IP address and port that a session connected + to (or what accepted from). + - The SessionConnected() signals of the QMessageTransceiverThread and + QMessageTransceiverHandler classes now include an IPAddressAndPort object + indicating what the session connected to. + - The SessionAccepted() signal of the QMessageTransceiverThread class now + includes an IPAddressAndPort object indicating where the session was accepted from. + - Added SetAsyncConnectDestination() and GetAsyncConnectDestination() methods + to the AbstractReflectSession class. + - Added ToString() methods to the SegmentedStringMatcher and + StringMatcher classes. + - SegmentedStringMatcher no longer bothers to allocate a StringMatcher + object for clauses that are represented by "*". + o Renamed DataNode::CountChildren() to DataNode::GetNumChildren(). + o Renamed DataNode::SetMaxKnownChildID() to DataNode::SetMaxKnownChildIDHint(). + o Renamed Message::CountNames() to Message::GetNumNames(). + o Rewrote the static internal function AdjustValue() in + SharedUsageLimitAllocator.cpp to give more informative error output. + o Removed Message::GetConstPointer() since you can use GetPointer() + to do the same thing (by assigning a const pointer to the result). + o Documented some methods that were previously not documented. + * Fixed a bug where SharedUsageLimitAllocator::ResetDaemonCounter() + could push the cumulative memory counter into negative territory. + * Inet_NtoA was allowing the IPv6-specific "@3" interface-index + suffix into IPv4-style address strings. Fixed. + * SetupSystem.cpp's Muscle_GetCurrentThread() wasn't working properly + on 64-bit OS's. Fixed. + +4.62 Released 6/17/2009 + - Added a ThreadLocalStorage class to the muscle/system + sub-folder. This class makes it easy for each thread + to access its own local copy of a global object, without + any need for Mutex locking/unlocking overhead. + - Added a Thread::GetCurrentThread() static method, so that + any Thread can access its Thread object conveniently. + - Added additional fields to the PR_RESULT_PARAMETERS Message: + PR_NAME_SERVER_CURRENTTIMEUTC (server's GetCurrentTime(UTC)) + PR_NAME_SERVER_CURRENTTIMELOCAL (server's GetCurrentTime(LOCAL)) + PR_NAME_SERVER_RUNTIME (server's GetRunTime64()) + - Added a AddApplicationSpecificParametersToParametersResultMessage() + method to the StorageReflectSession class. It allows a subclass + to add fields to the PR_RESULT_PARAMETERS Message before it + goes back to the client. + - Added an IsIPv4Address() function to NetworkUtilityFunctions.{cpp,h} + - Added GetSendDestinations() methods to the UDPSocketDataIO object. + With these methods you can have the UDPSocketDataIO object send the + same UDP packet to multiple destinations whenever Write() is called. + - Added a GetDefaultItem() method to the Queue class. + - Added GetDefaultKey() and GetDefaultValue() methods to the + Hashtable class. + - If TARGET_PLATFORM_XENOMAI is defined, GetRunTime64() now + uses Xenomai's rt_timer_read() function to determine its result. + - GetNetworkInterfaceInfos() and GetNetworkInterfaceAddresses() now + accept a bit-chord of GNII_INCLUDE_* bits as their second argument, + rather than a simple boolean. This allows the caller to express + in a bit more detail which sorts of interfaces he is interested in. + - Added a convenience constructor to the AndOrQueryFilter class. + o Added testthread.cpp back in to the test/Makefile. + o Modified testthread.cpp to test the ThreadLocalStorage class also. + o Reorganized the ObjectPool.h class implementation. + o Simplified the Qt implementation of the Thread class. + * system/Mutex.h now #includes support/MuscleSupport.h, + so that the Win32 build environment is detected properly + even when system/Mutex.h is the first #include. + +4.61 Released 5/22/2009 + - Added GetTotalNumSignalsReceived() and + GetNumSignalsReceivedOfType() methods to the + SignalMultiplexer class. + - Added an IsCurrentThreadMainThread() function to + SetupSystem.{cpp,h}. + - Added support for a "catchsignals" keyword to + HandleStandardDaemonArgs(). This keyword, if + specified, will cause the main thread's + ReflectSession() (if any) to add a signal handler + session to itself. + - Added support for a MUSCLE_AVOID_SIGNAL_HANDLING compiler + flag that can be defined by applications that don't + want to compile in signal handling support. + - Added a SignalChildProcess() method to the + ChildProcessDataIO class. + - ChildProcessDataIO::WaitForChildProcessToExit() + now takes an optional timeout value, and returns + true if the child exited or false if it timed out. + - In the ChildProcessDataIO class, I replaced + SetKillChildOnClose() and SetWaitForChildOnClose() with + a single SetChildProcessShutdownBehavior() method which + provides for more flexibility, making it possible to + do a "soft shutdown with a hard kill after a timeout". + o Removed the "catchsignals" support from muscledMain()'s + setup code, since that support is now part of + HandleStandardDaemonArgs() instead. + +4.60 Released 5/15/2009 + - The Message class now derives from Cloneable. + - The Message::FindInt*() methods class now accept both + signed and unsigned value arguments, so that dangerous + C-style casting is no longer necessary when retrieving + unsigned integer values. + - Added a set of Message::Get*() methods that are similar to + Message::Find*() except that they return the found value + instead of a status code. (if the requested value is not found, + they return a user-provided default value instead) + - The Message::Find*() methods now take their value parameters + by reference instead of by pointer. + (e.g. msg.FindInt8("foo", x) instead of msg.FindInt8("foo", &x). + The old by-pointer style is still supported, but is deprecated. + - Added a Queue::LastIndexOf() method that does a reverse + search in a Queue, optionally within a specified index range. + - Added a Flattenable::UnflattenFromByteBuffer() method + that takes a ConstByteBufferRef argument, per Mika's request. + - Added SetLowBits(), SetHighBits() to the IPv6 ip_address class. + - When MUSCLE_USE_IPV6 is defined, the ip_address class now + includes an interface-address field. Inet_AtoN() and Inet_NtoA() + now append/expect this field at the end of the string if the + interface is non-zero (e.g. "fe80::1@3") + - Upgraded the multicast API to properly support IPv6 multicast. + In particular, when MUSCLE_USE_IPV6 is defined, + AddSocketToMulticastGroup() and RemoveSocketFromMulticastGroup() + no longer take an interface IP address (instead they use the + interface index included in the groupAddress argument). + Also SetSocketMulticastSendInterfaceAddress() and + GetSocketMulticastSendInterfaceAddress() are replaced by + SetSocketMulticastSendInterfaceIndex() and + GetSocketMulticastSendInterfaceIndex(). This is necessary + because IPv6 doesn't identify interfaces by IP address, + rather it identifies them with integers. + - Added a broadcastIP_IPv4 constant to NetworkUtilityFunctions.h + to allow IPv4 braodcasts even in IPv6 mode. + - Added a localhostIP_IPv4 constant to NetworkUtilityFunctions.h + to allow references to the IPv4 localhost device in IPv6 mode. + - Added a GetHashCodeForIPAddress() function to + NetworkUtilityFunctions.h, to avoid #ifdefs in application code. + - Added a IsMulticastIPAddress(const ip_address &) function + to NetworkUtilityFunctions.{cpp,h} that returns true iff the + specified address is a multicast addess. + - Added a IsStandardLoopbackDeviceAddress(const ip_address &) + function to NetworkUtilityFunctions.h, since under IPv6 + localhostIP has several names and thus doing a literal + numeric comparison to (localhostIP) can be error-prone. + - Added a GetConnectString(const String &, uint16) convenience + function to MiscUtilityFunctions.{cpp,h}, to generate strings + like "localhost:9999" or "[ff05::1]:9999" correctly. + - Added an ExecuteSynchronousMessageRPCCall() function to + MessageIOGateway.{cpp,h}. This function connects to a + server via TCP, sends a Message, received a Message, + and returns the received Message, so that you can "call" + a server, RPC-style, as if it was a local function. + - Added a GetLocalHostName() function to + NetworkUtilifyFunctions.{cpp,h}. + - Added a HashCode64() method to the String class, and a + CStringHashFunc64() function that it calls. + - Added a ToString() method to the NetworkInterfaceInfo class. + - Improved the parsing of IPAddressAndPort and ParseConnectArg() + so that they now properly handle IPv6 hostname-and-port + strings that don't contain brackets, when possible. + - Added a version of ParseConnectArg() that takes a direct + String (instead of a Message and field name) + - Rewrite the Win32 implementation of GetNetworkInterfaceInfos() + to use GetAdaptersAddresses() instead of GetIpAddrTable(), + so that it can detect IPv6 addresses as well as IPv4. + - Added an optional (preferIPv4Style) argument to Inet_NtoA() + so that if you prefer, IPv4 addresses can be returned in the + classic style ("192.168.1.1") instead of new-style ("::192.168.1.1") + - Added support for the MUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME + compiler flag, which if specified will cause every call to + LogTime() to include location info (source file name and line + number) in the log information. This can be useful for + tracking down exactly where a particular log message came from. + - When MUSCLE_INCLUDE_LOCATION_IN_LOGTIME is defined, the + standard log-line preamble now includes the string returned + by GetStandardLogLinePreamble(). + - Added GenerateSourceCodeLocationKey(), + SourceCodeLocationKeyToString(), and + SourceCodeLocationKeyFromString() functions to Syslog.{cpp,h} + - Added a utility called "findsourcelocations" to the tests folder. + This utility will find possible locations for source code keys + in the specified directory hierarchy. + - Added a HashCode() method to the NetworkInterfaceInfo class. + - The Directory class is now a subclass of RefCountable. + - Added a GetPath() method to the Directory class. + - Added FileExists(), RenameFile(), CopyFile(), and DeleteFile() + utility functions to util/MiscUtilityFunctions.{cpp,h}. + - Added an MEXIT(ret,msg) macro to MuscleSupport.h. It's + the same as MCRASH, but it doesn't crash, it merely ends + the process (by calling ExitWithoutCleanup(ret)). + - Added a Reset() method to the StringMatcher class. + - Added a WasConnected() method to AbstractReflectSession, + so that subclasses can find out if the session ever was + connected to its remote peer. + - ExpandLocalhostAddress() now caches its first result from + GetNetworkInterfaceInfos() so that it no longer has to + call GetNetworkInterfaceInfos() every time it is called. + - Added a GetServerSessionID() method to the ReflectServer + class, and a PR_NAME_SERVER_SESSION_ID field to the + standard parameters set, so that clients can access a + 64-bit value that is unique to the current server instance. + - Added a SignalMultiplexer class that deals with POSIX + signals or Windows Console signalling in a unified manner. + - Added a QSignalHandler class that can emit a Qt signal + when a POSIX/Windows signal is received. + - Added a SignalHandlerSession class that you can add + to your ReflectServer if you want signals to result + in a graceful shutdown (or other custom behavior) + - The Python MessageTransceiverThread class constructor + now takes a boolean argument which indicates whether or + not it should use IPv6 networking. + - Added a GetHumanReadableProgramNameFromArgv0(const char *) + convenience function to MiscUtilityFunctions.{cpp,h}. + - Added a Win32AllocateStdioConsole() function that allocates + a console window for stdio to use under Windows. + - Added CleanupDNSLabel() and CleanupDNSPath() functions to + MiscUtilityFunctions.{cpp,h}. These are handy for removing + errors from user-entered DNS hostnames (e.g. "www.foo.com"). + - Rewrote String::LastIndexOf(char) and the String -= + operators to be more efficient. + o The arguments to the Log() method in the LogCallback API + are now consolidated into a single LogCallbackArgs object, + for efficiency and cleaner code. + o The GetStandardLogLinePreamble() function now takes a + single LogCallbackArgs argument instead of separate args also. + o Removed support for the MUSCLE_AVOID_NAMESPACES, + BEGIN_NAMESPACE, END_NAMESPACE, and USING_NAMESPACE + macros, since they aren't necessary and use of macros to + control namespaces can confuse Qt's moc utility. + o Rewrote portablereflectclient to use StdinDataIO instead + of accessing stdin directly. This allows it to work + correctly under Windows, and simplifies the code. + o Queue::IndexOf() was defined as returning the last + matching item, which was inconsistent. Replaced it with + a new IndexOf() implementation that does a forward search, + optionally within a specified index range. + o Suppressed a warning in zip.c + o Removed the explicit signal handling from the ReflectServer + class (SetSignalHandlingEnabled() and WasSignalCaught() are + gone now). This functionality is now handled by the + SignalHandlerSession class instead. + * The Win32 "console" keyword wasn't redirecting stdin. Fixed. + * Fixed a syntax error in the EnsureRefIsPrivate() method. + * Updated the VC++ project files so they build again. + * ParseConnectArg() now handles the "[ipv6::addr]:port" + syntax properly when MUSCLE_USE_IPV6 is defined. + * Fixed a bug in the PulseChild class that would sometimes + prevent the GetPulseTime() method of grandchild PulseChild + nodes from getting called after InvalidatePulseTime() was + called on them. + * AbstractReflectClient::Reconnect() now sets the _wasConnected + flag to false. + * Fixed a bug in MessageTransceiverThread::AddNewWorkerConnectSession() + that was causing disconnect notifications not to be sent for + connect-sessions whose connections failed synchronously. + * IntCompareFunc() was taking an int8 argument by mistake. Fixed. + +4.51 Released 3/19/2009 + - Added GetByteBufferFromPool() functions that accept a Flattenable + object as an argument, for convenience. + - Added a Message::GetPointerToNormalizedFieldData() method, which + is useful if you need efficient direct (array-style) access to + the contents of a data field in a Message object. + - Added NumericAwareStringCompareFunc() functions to String.{cpp,h}. + These routines are based on Martin Pool's number-aware string + compare code, and compare strings in such a way that numbers in + the strings are sorted properly. + - Added a SegmentedStringMatcher class to the regex folder, for + easy multi-level regex matching in e.g. file paths. + - Added GetStringMatcherFromPool() convenience functions to + regex/StringMatcher.{cpp,h}. + - Added a ParseHexBytes() convenience function to + MiscUtilityFunctions.{cpp,h} + - Added a PrintToStream() method to the ByteBuffer class. + - Added Bryan Varner's UDPClient class to the Java archive. + This class facilitates sending small MUSCLE Messages over UDP + in Java programs. Thanks Bryan! + - Added Bryan Varner's DatagramPacketTransceiver class to the + Java archive. This class allow Java applications + to send large MUSCLE Messages over UDP in Java programs, + by breaking them up into multiple chunks that are sent in + successive UDP packets and then re-assembled by the receiver. + This class is compatible with MUSCLE's C++ PacketTunnelIOGateway + class. Thanks again, Bryan! + - Added LaunchChildProcess() and System() methods to the + ChildProcessDataIO class that takes a Queue as an + argument, for convenience. + - Added implementations of ParseArg(), ParseArgs(), ParseFile(), + and UnparseArgs() that take a Queue to hold the parsed + arguments rather than a Message. This is useful in cases where + argument ordering must be preserved 100%. + - Added a Directory::MakeDirectoryForFile() convenience method. + It creates a directory for the specified file to be created in. + - Added Monni's 64-bit x86 byte-swap routine to MuscleX86SwapInt64(). + o RemoveEscapeChars() and EscapeRegexTokens() now take a const + String and return a String. This makes them easier to use + in nested function calls. + o Repackaged the com.lcs.* Java hierarchy as com.meyer.*, to reflect + my company's name change. + o Removed the Visual Cafe project from the archive, as it is obsolete + and I am no longer using Visual Cafe and thus cannot easily update it. + * Fixed template problems with gcc4 in BThread.h and AThread.h + * The Windows implementation of StdinDataIO now freopens stdin + as "nul", so that no other code will try to access stdin. This + was done to keep third party libraries (read: Python 2.6) from + trying to muck with stdin while the StdinDataIO thread was using + and getting hung up. + +4.50 Released 1/28/2009 + - NullDataIO's constructor now takes an optional ConstSocketRef + argument, to let you specify the value that its GetSelectSocket() + method should return. + - Added a GetMessageFromPool(const ByteBuffer &) inline + convenience function. + - Added another AddNewSession() convenience method to the + MessageTransceiverThread class. + - In the Message class, MoveName(), CopyName(), and ShareName() + all now take an optional target-field-name parameter, in case + you want to have a different field name in the target Message. + - Added an EnsureFieldIsPrivate(const String & fieldName) method + to the Message class. This method is useful to avoid side effects + when modifying possibly-shared Message fields' contents. + - Added an "cleanup callbacks queue" to the CompleteSetupSystem + class, so that you can specify actions that should be taken as + part of the CompleteSetupSystem destructor's cleanup steps. + - Added a static GetCurrentCompleteSetupSystem() method to the + CompleteSetupSystem class, to make it easier for code to find + the current CompleteSetupSystem object. + - Added MUSCLE_CATCH_SIGNALS_BY_DEFAULT #ifdefs... if this + is set, ReflectServer will enable Control-C detection (and + graceful shutdowns) by default. + - GetOSName() now accepts an optional string value to return in + the case where it can't determine the local OS name. + - Added a MUSCLE_UNIQUE_NAME macro to MuscleSupport.h which will + evaluate to a unique identifier based on the line number that the + macro is invoked on. + - Added a DECLARE_ANONYMOUS_STACK_OBJECT macro to MuscleSupport.h + to allow easy declaration of anonymous objects on the stack. + - Added util/BatchOperator.h, which contains the BatchOperator + and BatchOperatorGuard utility classes that help automate the + amortization of setup/shutdown routines across a nested call + tree, for more efficient batch processing. + - Added a DECLARE_BATCHGUARD() macro for each declaration of + batched areas of code. + - Added a DECLARE_MUTEXGUARD(m) macro to support declaring an + anonymous MutexGuard on the stack in a simple and foolproof way. + - hexterm now automatically enables the UDP broadcast flag on its + UDP socket when the specified UDP address is a broadcast address. + - Added a Cloneable interface in util/Cloneable.h, and updated + ConstRefCount::EnsureRefIsPrivate() to use it when appopriate. + - Added a Clone() method to the ConstRefCount class, for easy + copying of referenced items. + - Redesigned hexterm to be Win32 compatible. + - Added a hexterm.vcproj file to the tests folder, to compile + hexterm under Win32/VC++. + - Added a "child=" option to hexterm so it can spawn and communicate + with a child process if you want it to. + - Added a "ascii" keyword argument to hexterm so you can send + and receive data in ASCII format rather than hex, if you prefer. + - Added LogHexBytes() functions to MiscUtilityFunctions.h. These + are the same as PrintHexBytes() except that they call through + to Log() instead of directly to fprintf(). + - Added an optional (portRequired) argument to ParseConnectArg(). + - Added SetLogFileName()/GetLogFileName() to util/SysLog.{cpp,h}. + - Added a standard "logfile" keyword so you can specify the name + and/or location of a log file to write the log to. + - Added a WaitForChildProcessToExit() method to the + ChildProcessDataIO class. + - Added ChildProcessDataIO::System() static convenience methods. + - Added a Directory::DeleteDirectory() static convenience method. + o Rolled in Mika Lindqvist's Haiku-compatibility patches. + o Renamed the standard "log" and "display" keywords to "filelevel" + and "displaylevel", respectively. + o Removed the OutOfMemoryCallback and FunctionOutOfMemoryCallback + classes and replaced them with more general-purpose + GenericCallback and FunctionGenericCallback classes. + o Tweaked a call to FD_ISSET() in system/Thread.cpp in order to + avoid a compiler warning under g++ 4.3.1 + o Tweaked the _PLOCKimp and _PUNLOCKimp debug functions to compile + properly in 64-bit environments. + o Updated the Message.h Doxygen documentation with some more + descriptive parameter names. + o hexterm no longer disables multicast-to-self when sending + multicast UDP packets. + o Removed DebugTimer.cpp; that code has moved to SetupSystem.cpp. + o Updated all copyright notice headers to read 2000-2009 Meyer Sound. + * DebugTimer now uses GetRunTime64() if MUSCLE_USE_LIBRT is defined. + * Fixed some #ifdef problems involving the stat64() call in + FilePathInfo.h. FilePathInfo.h should now compile under both + 32-bit and 64-bit environments. + * JeffK added a macosx.mak file to the muscle/zlib/zlib folder, + to support creation of universal binaries that include zlib. + * MuscleSupport.h now checks a much more exhaustive list of + Intel-compatible processor-type macros before deciding that + inline x86 assembly code is not an option. In particular, + compiling with an __i686__ target now does the right thing. + * Fixed a potential infinite recursion in + Thread::WaitForNextMessageAux(). + * Tweaked the SharedMemory.h include directives to compile + more reliably under Win32. + * Updated the #ifdefs in MuscleSupport.h to define int64's + and uint64's properly under 64-bit Ubuntu Linux. + * Rewrote the Win32 implementation of StdinDataIO to be simpler + and more reliable. + * The Win32 implementation of the INT64_FORMAT_SPEC macros + was incorrect. Fixed it to do the right thing. + * Made ChildProcessDataIO::LaunchChildProcess() const-correct. + * ChildProcessDataIO::LaunchChildProcess() now specifies the + application name to launch, when possible, for better security. + (See Microsoft's CreateProcess() man page for details) + +4.41 Released 11/17/2008 + - Added an implementation of PrintHexBytes() that takes a + Queue as an argument. + - Added GetArchiveMessageFromPool() functions to Message.{cpp,h}. + These templated convenience functions let you convert any object + (with a SaveToArchive() method) into a MessageRef with a single command. + - Added FindArchiveMessage() and FindArchiveMessageWithDefault() + template methods to the Message class, for convenient one-step + restoring of archived objects. + - Added AddArchiveMessage(), PrependArchiveMessage(), and + ReplaceArchiveMessage() template methods to the Message class, + for further convenience in archiving objects. + - GetHumanReadableTimeValues() now writes into a + HumanReadableTimeValues object instead of into a series + of by-reference int parameters. The HumanReadableTimeValues + object now also includes day-of-week and microsecond fields. + - Added an ExpandTokens() method to the HumanReadableTimeValues + class that makes it easy to generate time/date stamps of your + preferred format using various printf()-style field specifiers. + * ProxySessionFactory::IsReadyToAcceptSessions() wasn't passing + the call onto the slave session like it was supposed to. Fixed. + * The calls to open() inside SpawnDaemonProcess() now supply a + mode argument to the open() call. + * AcceptSocketsThread.cpp wasn't compiling due to a const/non-const + issue. Fixed. + * Fixed several bugs in the admin utility. Thanks to Monni for + pointing them out and supplying a patch. + +4.40 Released 10/16/2008 + - The Ref class now subclasses from a ConstRef class, which is the + same as Ref except that it only allows read-only access to the + held RefCountable object. + - Added a CastAwayConstFromRef() template function to RefCount.h, + so you can easily convert a ConstRef to a Ref if you really need to. + - Renamed the SocketRef class to ConstSocketRef, because it now derives + from ConstRef instead of Ref. + - Added a DECLARE_REFTYPES macro to RefCount.h, which you can + use to declare the standard BlahRef and ConstBlahRef typedefs + without having to specify the typedefs manually every time. + - Added implementations of the Connect() and ConnectAsync() functions + that take an IPAddressAndPort object as an argument. + - Added *_FORMAT_SPEC_NOPERCENT macros, for times when I need to + specify format specifications without the percent sign included. + - Added an AsyncDataIO class that can be used to transparently forward + I/O operations to a separate thread, to avoid blocking in your + main thread. This can be useful for DataIO classes that don't + support non-blocking I/O (e.g. FileDataIO) + - Added a GetStackTrace() function to SysLog.{cpp,h} that returns + the current stack trace as a String. + - Added GetLightweightCopyOfMessage() convenience functions to + Message.{cpp,h}. + - Added convenience methods FindFirstSessionOfType() and + FindSessionsOfType() to the ServerComponent class. These + methods are templated to let you quickly find and collect + one or more atatched session objects of the specified C++ class. + o Renamed GenericRef to RefCountableRef, for consistency. + o Renamed PolicyRef to AbstractSessionIOPolicyRef, for consistency. + o Renamed Ref::SetFromGeneric() and Ref::GetGeneric() to + Ref::SetFromRefCountableRef() and Ref::GetRefCountableRef(), + respectively, for consistency. + o testrefcount now runs a 10-second test of reference-counts in a + heavily multithreaded environment, to ensure that they are thread safe. + o Improved performance of Tuple::ShiftValuesLeft() and + Tuple::ShiftValuesRight(). + * Removed re-definitions of 'true' and 'false' when compiling in C++ mode. + * Added some missing command codes to StorageReflectConstants.py. + * Message.py didn't handle B_INT32_TYPE fields properly when running on + a 64-bit host. Fixed. + * AddNewConnectSession() and Reconnect() now handle synchronous connect + failures the same way as asynchronous connect failures, so that the + calling code doesn't have to worry about two different failure modes. + * Fixed a valgrind hit in CreateAcceptingSocket() on 64-bit platforms. + * Fixed a buffer overflow bug in Message::AddToString(). + * Updated the header comments to fix various doxygen warnings. + +4.30 Released 9/18/2008 + *** WARNING - THIS RELEASE CHANGES THE PulseNode API IN a NON *** + *** BACKWARDS COMPATIBLE WAY. BE SURE TO READ THE ENTRY ABOUT *** + *** PulseNode BELOW AND UPDATE YOUR CODE TO MATCH THE NEW API. *** + - ChildProcessDataIO class now has SetChildProcessInheritFileDescriptors() + and GetChildProcessInheritFileDescriptors() methods to control whether + child processes spawned should inherit the parent's file descriptors or + not. Note that these methods have no effect under Windows. + - When receiving UDP packets, hexterm now prints out the source of the + UDP packets it received (in addition to the data). + - Added a IsNormalized() method to the Queue class. + - Added a IsMessageDeflated() method to ZLibUtilityFunctions.h. + - Added Message::BecomeLightWeightCopyOf(const Message &), for making + lightweight copies of Messages, with shared fields. + - Added Message::ShareName() (like CopyName() except the data isn't + copied, rather only a reference is shared) + - Added Queue::AddHeadAndGet() and Queue::AddTailAndGet() methods, + for when you want to add an item to a Queue and then write to the + added object directly afterwards. + - Added an optional (retPort) argument to + MessageTransceiverThread::PutAcceptFactory() so that you can now + use dynamic port assignment with it. + o PulseNode::GetPulseTime() and PulseNode::Pulse() now take a single + (const PulseArgs &) argument instead of two uint64s. This makes + implementing the PulseNode interface less verbose, and calling + the Pulse callbacks more efficient. However, this breaks compatibility + with earlier code that expects (uint64, uint64) arguments for those + methods, so be sure to update your code when upgrading to this + version of MUSCLE. + o Fixed some compiler warnings under gcc (thanks to Monni for pointing + these out) + o Trying to use MUSCLE's logging system when there is not a SetupSystem + object on the stack (e.g. before or after main()) is no longer a fatal error. + Instead, muscle will degrade to output only to the built-in log services + (stdout and/or output-to-file) when called under these conditions. + +4.28 Released 8/26/2008 + - Added a RefCount::IsDeeplyEqualTo() method for more in-depth + comparison-by-value of the referenced items. (The regular RefCount::== + operator only compares the pointers, not the items themselves) + - Added a Directory class (util/Directory.{cpp,h}) to support directory + scanning in a cross-platform-compatible fashion. + - Added a FilePathInfo class (util/FilePathInfo.h) to support + stat() functionality in a cross-platform-compatible manner. + - ReflectServer::SetSignalHandlingEnabled() now enables/disables + handling of console signal events (Ctrl-C, etc) under Win32. + - Added implementations of GetMessageFromPool() that take a + pointer and a byte-count and try to return a Message that has + been unflattened from the specified byte array. + - ChildProcessDataIO::LaunchChildProcess() now takes an optional + boolean (usePty) argument that allows you to specify at runtime + whether you want to use fork() or forkpty() to launch the child + process. + - Added a MUSCLE_AVOID_FORKPTY compiler flag that tells the + ChildProcessDataIO class to avoid compiling in calls forkpty(). + o Made the AbstractReflectSession::EndSession() method virtual. + o The Win32 build of MUSCLE now #includes winsock2.h instead of winsock.h. + * The Win32 implementation of StdinDataIO wasn't handling a closed + stdin connection correctly. Fixed. + * For the Win32 build, added Microsoft's recommended work-around for + the WSAECONNRESET problem with UDP sockets, as described at + http://support.microsoft.com/kb/263823/en-us + * Fixed the Hashtable and Queue classes to again compile under + Visual C++ 6.0 (thanks to Mika Lindqvist for the patch) + +4.27 Released 7/24/2008 + - minichatclient.c now advertises the client's host OS on the server. + - PrintStackTrace() is now implemented under MacOS/X (Leopard or newer). + - PrintStackTrace() and all implementations of PrintToStream() + now take an optional (FILE *) argument, so that they can now + print to places other than stdout if you prefer. + - The testchildprocess test program now has you specify the number + of child process instances to run. That makes it easier to test + what happens when many child processes are launched in parallel. + o Merged in some more of Mika Lindqvist's Haiku compatibility tweaks. + o Hashtable::GetByDefault() with one parameter now returns its result + by reference instead of by value. + o The Hashtable and Queue classes now keep default instances of + their user types, to avoid having to construct temporary default + objects when resetting objects to their default state. + * PrintStackTrace() now prints appropriate error messages if + it fails, instead of just failing siliently. + * Fixed a syntax error in Hashtable::Remove(const Hashtable &) + * Hashtable::Clear() now handles re-entrancy (from the templatized + classes' assignment operators) correctly. + +4.26 Released 6/5/2008 + - Added a CheckedGetItemPointer() method to the Ref class. + This method is the same as GetItemPointer(), but with + an additional check so that it will safely return NULL + if the "this" pointer is NULL. + - Added a "assumeDefaultValue" argument to the NumericQueryFilter, + StringQueryFilter, and RawDataQueryFilter classes. This + argument lets you specify that in the event that a Message + doesn't contain the data item the QueryFilter wants to + compare against, a default value should be compared against + instead. + - Added a HashCode() method to the Ref class, for consistency. + - ChildProcessDataIO::GetChildProcessID() is now implemented + under Windows as well. + - Added two new conditional defines to MuscleSupport.h: + MUSCLE_USING_OLD_MICROSOFT_COMPILER, which is defined when + compiling under VC++6 or earlier, and + MUSCLE_USING_NEW_MICROSOFT_COMPILER, which is defined + when compiling under VC++.net(2003) or later. These make + managing Microsoft's bug circus a bit a easier. + * Fixed the Ref class to compile under MSVC2005 again. + * Fixed the setsockopt()/getsockopt() calls in NetworkUtilityFunctions.cpp + so that they again compile under Windows. + * Merged in Mika Lindqvist's patches so that muscle compiles + properly under Haiku. + +4.25 Released 5/14/2008 + - Added a DECLARE_HASHTABLE_KEY_CLASS macro that expands + to the boilerplate template code necessary to use the + specified class as a key in a Hashtable. + - Added a DECLARE_HASHTABLE_KEY_CLASS_IN_NAMESPACE macro + which is the same as DECLARE_HASHTABLE_KEY_CLASS except + it works from within namespaces other than muscle. + - Added PutIfNotAlreadyPresent() convenience methods to the + Hashtable class. + - Added CopyFrom() method to the Queue and Hashtable classes. + These work the same as the assignment operator, except that + they return a status_t result code. + - Added a SetFilter() method to the PathMatcherEntry class. + o Cleaned up the setsockopt() and getsockopt() calls inside + NetworkUtilityFunctions.cpp to be more portable. + * ExplandLocalhostAddress() is now more careful not to expand + (localhostIP) back into (localhostIP), even if that is the + first IP address listed in the interfaces list. + * Fixed a bug in the QueryFilter subscriptions feature -- + after replacing a subscription's existing QueryFilter, + the old QueryFilter would continue to be used instead + of the new one. + +4.24 Released 4/28/2008 + - If MUSCLE_USE_LIBRT and _POSIX_MONOTONIC_CLOCK are defined, + GetRunTime64() and Snooze64() will now use librt's high-resolution + functions (clock_gettime() and clock_nanosleep()) instead of + the older vanilla POSIX functions. This can provides higher + resolution timing on platforms that support librt. + - Added a new ExecuteSynchronousMessaging() virtual method + to the AbstractMessageIOGateway class, and some associated + hook/callback methods. This method lets you easily do + synchronous/RPC-style "function calls" across the gateway's + socket, passing in one or more Message objects as the + "arguments", and receiving one or more Messages as "results". + - MessageIOGateway now overrides ExecuteSynchronousMessaging(), + IsStillWaitingForSynchronizedReply(), and + SynchronousMessageReceivedFromGateway() so that when you + call ExecuteSynchronousMessaging() on it you will get + proper RPC function-call semantics. + - Added an IsReadyToAcceptSessions() virtual method to the + AbstractReflectSessionFactory class. Subclasses can override + this method to return false if they don't want to accept + any more connections for a while. + - Added an IsConnected() method to the AbstractReflectSession + class, to make it easier to check if a given session is + currently connected to anything or not. + - Added an XorDataIO class to the dataio folder. + XorDataIO XOR's all the data going through it, before + forwarding the method call to its held child DataIO object. + - Added a MutexGuard class that you can put on the stack + to automatically lock/unlock a Mutex via its ctor/dtor. + - Added versions of ReadZipFile() and WriteZipFile() that + take a DataIO reference instead of a file name. That way + you can read zip files over the network, through custom + filters, and so on, if you want to. + zlib/ZipFileUtilityFunctions.{cpp,h}. + - ReadZipFile() now has an optional second argument called + (loadData). Setting it false will cause ReadZipFile to + only read in the file names and uncompressed file lengths, + but not actually read or uncompress the file data. This + is useful if you just want to quickly check the zip file's + contents without actually unpacking everything. + - Updated testzip.cpp to accept an optional "namesonly" + command line argument, which will set (loadData) to + false in its ReadZipFile() call, as described above. + - Added a GetNetworkInterfaceAddresses() function, as an + easier-to-use alternative to GetNetworkInterfaceInfos(). + o Made AbstractReflectSession::IsConnectingAsync() public. + o Moved Snooze64() from NetworkUtilityFunctions.cpp to + SetupSystem.cpp, and moved its function declaration from + NetworkUtilityFunctions.h to TimeUtilityFunctions.h. + * Joel Lucsy reported a bug in FlattenToDataIO() that would cause + the last four bytes of the flattened buffer not to be written + to the DataIO object. Fixed. + * Updated zlib's included VC++ project files to reference zconf.h + in its new location (zlib/win32/zconf.h) so that they now work + again. + +4.23 Released 3/26/2008 + - Added an UnparseArgs() function to MiscUtilityFunctions.{cpp,h}. + This function takes a parsed Message and turns it back into a String. + - Added an InsertItemsAt() method to the Queue class. + - Added an (includeLocalhost) boolean argument to GetNetworkInterfaceInfos(). + - Made ServerComponent::SetOwner() and ServerComponent::GetOwner() + public, as it is necessary for classes other than ReflectServer + to access that value in order to properly support the facade + pattern within ReflectSessionFactory objects. + - Added a ProxySessionFactory class to support facade-style + ReflectSessionFactories better. + - Added an optional ITraversalPruner argument to both + StorageReflectSession::SaveNodeTreeToMessage() and + StorageReflectSession::RestoreNodeTreeFromMessage(). This + argument lets the caller specify a callback argument that + can direct the traversal based on custom logic, if necessary. + o Replaced StorageReflectSession::CloneDataNodeSubtree()'s rather clunky + MessageReplaceFunc and void-pointer filtering mechanism with the nicer + and more powerful ITraversalPruner filtering mechanism. + * Fixed a bug in FilterSessionFactory and SharedFilterSessionFactory + where an assertion failure would occur if the "slave" factory attempted + to call GetSessions(), etc. + * SharedFilterSessionFactory's IP-address checking was broken. Fixed. + * AbstractReflectSession::GetPort() was broken and would always + return 0. Fixed. + +v4.22 Released 3/4/2008 + - Added an IsDaemonProcess() function that returns true iff + the current process was created via SpawnDaemonProcess() + or BecomeDaemonProcess(). + - Added a AssembleBatchMessage() convenience function to + MiscUtilityFunctions.{cpp,h}. It's useful for creating a + PR_COMMAND_BATCH Message from zero or more other Messages. + - The NestCount class will now cause an assertion failure + if Decrement() is called when it is already at zero... + that should never happen in correct code. + - Added an optional (numColumns) argument to the + PrintHexBytes() utility function. + - Added a templatized bit-chord class, support/BitChord.h. + - Added ReadData() and WriteData() functions to + NetworkUtilityFunctions.{cpp,h}. These are the same + as SendData() and ReceiveData(), except that they call + read() and write() instead of send() and recv(). + - Replaced the PrintHexBytes() implementation with the + superior one from hexterm.cpp. + - Added a GetWithDefault() convenience method to the + Hashtable class. + o Moved the Win32 version of zconf.h from zlib/zlib to + zlib/zlib/Win32, so that it wouldn't show up in SVN + as modified whenever the configure script modified it. + o Changed the default value of MUSCLE_POOL_SLAB_SIZE to + 4 kilobytes, to reduce memory overhead somewhat. This + value coincides nicely with the most common memory page size. + * The POSIX implementation of RS232DataIO was broken, because + it was trying to use SendData() and ReceiveData() on the + serial port's (non-sock) fd. Changed it to call ReadData() + and WriteData() instead. + * Rewrote the Unix implementation of ChildProcessDataIO to + call forkpty() instead of doing everything by hand. This + makes the implementation much simpler and more reliable, + although of course it will only work on systems that have + forkpty() available. + * ChildProcessDataIO now closes any forked file handles in the + child process before calling exec(). + * Made the PacketTunnelIOGateway parsing more flexible; + duplicate packets no longer break a logical stream. + * Removed several files from the zlib/zlib folder that are + meant to be generated by the configure script. + +v4.21 Released 1/22/2008 + - Optimized Hashtable::GetOrPut(), CopyToTable(), MoveToTable(), + Put(), and Remove() to be more efficient. + o Updated the muscle.dox DOxygen file to reflect changes to + newer versions of DOxygen + * Hashtable::Put() and Hashtable::Remove() now do the right + thing if they are passed (*this) as an argument. + * Fixed a nasty bug in the ObjectPool destructor that would + cause it to go into an infinite loop, calling delete on + the same array over and over again. + * Filled in and fixed up the DOxygen comments to the point where + DOxygen no longer gives any warnings when generating the autodocs. + o Updated all copyright notice headers to read 2000-2008 Meyer Sound. + +v4.20 Released 12/31/2007 + - Added an optional set of multicast networking API calls + to util/NetworkUtilityFunctions.h. Define the constant + MUSCLE_ENABLE_MULTICAST_API to make them available. + - hexterm will now automatically use multicast UDP if you + specify a multicast UDP address as an argument. For example, + ./hexterm udp=239.255.1.2:4001 + - Added SetSourceExclusionID() and GetSourceExclusionID() + methods to the PacketTunnelIOGateway class, to enable more + efficient filtering of looped-back broadcast/multicast packets + that you sent out and don't want to see back again. + - Added a ParseHexBytes() function to MiscUtilityFunctions.{cpp,h} + - Under Linux, HandleStandardDaemonArgs() now takes a new argument + "debugcrashes" which will cause a signal handler to be installed + that will catch SIGSEGV, SIGBUS, SIGILL, SIGABRT, and SIGFPE, + and print a stack trace to stdout before exiting. + - Added RemoveFirst() and RemoveLast() convenience methods + to the Hashtable class (handy when using the Hashtable as + a keyed FIFO or LRU) + o Moved the PrintHexBytes() function into system/SetupSystem.cpp + so that you don't have to link in MiscUtilityFunctions.cpp + just to use it. + * Tweaked FinalizeAsyncConnect() to work properly under BeOS. + * Improved the BeOS/BONE detection -- Makefile support no + longer required. + +v4.11 Released 12/11/2007 + - GetHumanReadableTimeString() and ParseHumanReadableTimeString() + now handle the uint64 value MUSCLE_TIME_NEVER as a special case, + by translating it into the string "(never)" (and back). + - Added a CalculateChecksumForUint64(), CalculateChecksumForFloat(), + and CalculateChecksumForDouble() convenience functions to + MuscleSupport.h + - Added a FindFirstMatchingNode() method to the DataNode class, + which can be used to efficiently look up a descendant node + based on a relative or absolute path, with or without wildcard + matching characters. + - Added a GetRootNode() convenience method to the DataNode class + which returns the root node of the DataNode tree. + - Added SetAllowMiscIncomingData() and GetAllowMiscIncomingData() + methods to the PacketTunnelIOGateway class, so that a UDP socket + controlled by a PacketTunnelIOGateway can optional receive + individual (non-packetized) arbitrary UDP packets as well as + the packetized kind. This mode is disabled by default. + - Added a Normalize() method to the Queue class that can be used + to ensure that the Queue's contents are layed out contiguously + in memory (like a C array). + - Added a AddNewDormantConnectSession() method to the ReflectServer + and ServerComponent classes. This method is the same as + AddNewConnectSession() except that the added session will not + start a TCP connection immediately; instead it will hang out + and wait for you to call Reconnect() on it. + - Added a PrintHexBytes() convenience/debugging function to + MiscUtilityFunctions.h + o Removed the (countFieldOrder) argument from the + Message::CalculateChecksum() method. + o Changed instances of ((uint32)-1) literals to the more proper + MUSCLE_NO_LIMIT constant in a number of places. + o Rewrote StorageReflectSession::GetDataNode() as a simple + inline call-through to FindFirstMatchingNode(), which means + that GetDataNode() now supports wildcard matching. + o GetHumanReadableTimeValues() now returns B_ERROR if you pass + it MUSCLE_TIME_NEVER as a time value (since there is no good + numeric way to represent infinity). + o DataNode::PrintToStream() and Message::PrintToStream() + no longer print this-pointer values in their debug output, + because doing so makes it harder to diff state dumps + from different processes. + o AbstractReflectSession::Reconnect() and SetAutoReconnectDelay() + can now be used in conjunction with sessions that were not + added with AddNewConnectSession() also. In this context, they + will "reconnect" the session by destroying its gateway and + DataIO objects and creating new ones by calling + CreateDefaultSocket() and CreateDataIO(). + * system/AcceptSocketsThread.h was missing a necessary #include + line. Thanks to Mika Lindqvist for reporting this. + * Fixed a bug in DataNode::InsertOrderedChild() that could cause + the inserted child's node name not to be unique in some + circumstances. + * DataNode::PutChild() now takes a (const DataNodeRef &) instead + of a (DataNodeRef &). + * Removed the index-order multiplication from most implementations + of CalculateChecksum(), since including an index-multiplier + in the checksum makes it inefficient to update a running + checksum tally when inserting or removing items in the list. + * The Thread class now closes the internal thread's side of + the inter-thread socket connection when the internal thread + hook function exits. That way the main thread can be notified + that the child thread has gone away (the main thread's socket + will select() ready-for-read because of the socket close) + +v4.10 Released 10/30/2007 + - Added Bryan Varner's MicroMUSCLE port to the archive, in + the new java_j2me folder. MicroMUSCLE is a fork of the + MUSCLE Java code that is compatible with the J2ME edition + of Java being used on modern cell phones, etc. Unlike + the standard MUSCLE Java API, this version doesn't require + Java 1.4.x APIs to be supported. + - Added a GetNumberOfProcessors() function to the SystemInfo.h + API. You can call this to find out how many CPU cores the + computer you are running on has. + - Made LockLog() and UnlockLog() part of the public MUSCLE + syslog API, in case you want to use the log mutex in your + own critical section. + - Added a CalculateChecksum() utility function to MuscleSupport.h + - Added CalculateChecksum() functions to the String, ByteBuffer, + Point, Rect, Message, and DataNode classes. These methods + compute a quick checksum on the object's contents which can + then be used for sanity-checking later on. + - Added a PrintToStream() method to the DataNode class for + quick recursive dumping of database subtrees to stdout. + - Message::PrintToStream() and Message::ToString() now include + the checksum value of the Message. + - Added IsInBatch() and IsOutermost() methods to the NestCount + class, for convenience and code clarity. (Before they were + only present in the NestCountGuard class) + - Added a KillChildProcess() method to the ChildProcessDataIO + class, for times when you need the child process dead right + away but you want to keep the socket to it open (so that it + will error out as if the child process died of its own accord). + - Added IsDescendantOf() and IsAncestorOf() convenience methods + to the DataNode class. + - Added a StringCompareFunc() override to String.h that takes + (const String *)'s as arguments. Useful when you want to + save space in a Hashtable of string-keyed, ref-counted + objects, by changing the key-type to a pointer to a string + that is held in the referenced value-objects. + - Added a Message::FindString() method override that sets a + pointer-to-a-String, so that you can access the underlying + String object without having to copy it. + - Added a ServerComponent::GetSession() override that takes + a uint32 for the session ID argument. + - Added convenience versions of StringMatcher::Match() + and HasRegexTokens() that take a String argument instead + of a (const char *). + - Added a GetEmptyMessageRef() convenience function to + Message.h. It's like GetEmptyMessage() except it returns + a (const MessageRef &) instead of a (const Message &). + - The StdinDataIO class now works as expected under Windows. + In particular, it implements some backstage trickery so that + you can use its GetSelectSocket() return value in select() + even though Windows doesn't support doing that. + o Removed GetBlankMessage() from the StorageReflectSession + API. Use GetEmptyMessageRef() instead. + o DataNode::GetData() now returns a (const MessageRef &) + instead of a MessageRef. + o The GetSessions() method now returns a Hashtable instead + of a HashtableIterator. + o The GetNumSessions() method has been removed; use + GetSessions().GetNumItems() instead. + o The GetFactories() method now returns a Hashtable instead + of a HashtableIterator. + o The GetNumFactories() method has been removed; use + GetFactories().GetNumItems() instead. + o Tweaked some of the code to avoid new warnings in gcc 4.1.x + o The SetDataNode(), FindMatchingSessions(), CloneDataNodeSubtree(), + NotifySubscribersThatIndexNodeChanged(), NodeIndexChanged(), + GetNewDataNode(), and JettisonOutgoingSubtrees() methods + in the StorageReflectSession class not take String arguments + instead of (const char *) + o The InsertOrderedChild(), ReorderChild(), HasChild(), + GetChild(), and RemoveChild() in the DataNode class now all + take String arguments instead of (const char *). + o DataNode::GetPathClause() now returns a (const String *) + instead of a (const char *). + o FilterSessionFactory Put*Pattern() and Remove*Pattern() + methods now take (const String &) arguments instead of + (const char *). + o The StdinDataIO class no longer derives from the + FileDescriptorDataIO class. This way it is possible to + use StdinDataIO under Windows (which doesn't support + file descriptors). + o Moved the StdinDataIO code into its own separate StdinDataIO.cpp + file, to better hide the ugly Windows implementation. + * Fixed a VC++ compatibility issue in ObjectPool.h. Thanks to + Mika Lindqvist for reporting this problem. + * Fixed a bug in AcceptSocketsThread.cpp. Thanks to Mika + Lindqvist for reporting this problem also. + * Fixed a HANDLE leak in the Windows implementation of the + RS232DataIO and ChildProcessDataIO classes. + +v4.00 Released 10/03/2007 + *** WARNING - THIS RELEASE RATIONALIZES SEVERAL APIS AND THUS *** + *** BREAKS SOURCE COMPATIBILITY WITH MOST OLD CODE. DON'T *** + *** UPGRADE TO THIS RELEASE UNLESS YOU ARE WILLING TO UPDATE *** + *** YOUR CODEBASE TO MATCH. *** + - Added a SanitySetupSystem class to the CompleteSetupSystem + object. SanitySetupSystem just does some very quick tests + on the typedef sizes (int16, int32, etc) and endian-ness + of the compiled code to make sure that the code's build + settings are compatible with the run-time environment. + If any of the tests fail, it prints out a stern error + message and aborts the program. + - Added convenience methods for AddFlat(), PrependFlat(), + FindFlat(), and ReplaceFlat() to the Message class that + take a ByteBufferRef as an argument. That way you can + deal with Messages and ByteBufferRefs without always + having to look up how to cast a ByteBufferRef to a + FlatCountableRef and back. + - Added HasChars() and IsEmpty() convenience methods + to the String class, for consistency with the other + container classes. + - Rewrote the ObjectPool class to do its object allocations + in 8 kilobyte "slabs" rather than one at a time. + This cuts down on the number of calls to new/delete + by an order of magnitude. + - Added a -DMUSCLE_POOL_SLAB_SIZE build option in case + you want the ObjectPools to use a different slab size. + This value is specified in bytes. + - Added an ExitWithoutCleanup() function to + MiscUtilityFunctions.{cpp,h}, which is equivalent to + _exit(). This wrapper function exists because I + suspect _exit() may not be entirely portable. + - Added a "testpool" program to the tests folder to + measure the relative efficiency of various + object-allocation techniques. + - AbstractReflectSession::GetSessionIDString() now returns + a (const String &) instead of a (const char *). + - Added a GetSessionID() accessor to AbstractReflectSession. + - Added a CreateDefaultSocket() method to the + AbstractReflectSession class. This method is called + when AddNewSession() is passed a null SocketRef, and + if implemented to return a non-NULL SocketRef, can be + used to provide a default socket for the session. + - Added a StdinDataIO class that can be used to do + non-blocking-I/O on stdin without breaking the + blocking-I/O functionality of stdout. + o Renamed the SocketHolderRef class to Socket. + o Added the a SocketRef class (which subclasses Ref) + o All MUSCLE functions that previously dealt with sockets + as integer file descriptors now use the new SocketRef + type instead. This way there is no chance of leaking or + double-freeing allocated sockets. + o Removed the CloseSocket() function from the API, since + it is no longer necessary. + * Changed the static casts in StorageReflectSession.cpp + to dynamic_cast<>, so that ReflectServers holding some + sessions derived from StorageReflectSession and some + derived directly from AbstractReflectSession will now + work without crashing. + o Removed GetDataIORef() and GetGatewayRef(), and changed + GetDataIO() and GetGateway() to return references to Ref + objects instead of pointers to objects. + o AbstractReflectSession::CreateDataIO() now takes a + (const SocketRef &) argument instead of an int, and now + returns a DataIORef instead of a (DataIO *) + o AbstractReflectSession::CreateGateway() now returns an + AbstractMessageIOGatewayRef instead of a + (AbstractMessageIOGateway *) + o ReflectSessionFactory::CreateSession() now returns an + AbstractReflectSessionRef instead of a (AbstractReflectSession *) + o StorageReflectSession::GetNewDataNode() now returns a + DataNodeRef instead of a (DataNode *). This way any + chance of a memory leak is avoided. + o StorageReflectSession::ReleaseDataNode() has been + removed since it is no longer necessary. + o RefCountable's copy constructor no longer copies the + _manager pointer, since items allocated in one ObjectPool + can no longer be freed by a different ObjectPool. + o MessageTransceiverThread::CreateSupervisorSession() now + returns a ThreadSupervisorSessionRef instead of a + (ThreadSupervisorSession *) + o MessageTransceiverThread::CreateDefaultWorkerSession() + now returns a ThreadWorkerSessionRef instead of a + (AbstractReflectSession *) + o MessageTransceiverThread::CreateDefaultSessionFactory() + now returns a ThreadWorkerSessionFactoryRef() instead of + a (ReflectSessionFactory *). + o ThreadWorkerSessionFactory::CreateThreadWorkerSession() + now returns a ThreadWorkerSessionRef instead of a + (AbstractReflectSessionRef *). + o The MessageTransceiverThread::AddNew*Session() family + of methods now take a (const ThreadWorkerSessionRef &) + argument instead of (const AbstractReflectSessionRef &). + o The MessageTransceiverThread::PutAcceptFactory() family + of methods now take a ThreadWorkerSessionFactoryRef + argument instead of a ReflectSessionFactory. + o MessageTransceiverThread::CreateReflectServer() now + returns a ReflectServerRef instead of a (ReflectServer *). + o The ReflectServer class now derives from RefCountable + and has an associated ReflectServerRef typedef. + o Removed the SetOkayToClose*() methods from the + MessageTransceiverThread class, as they are no longer + necessary. + * BecomeDaemonProcess() now calls ExitWithoutCleanup() + instead of exit(), to avoid crashing the parent process + in the globals-cleanup phase (since the CompleteSetupSystem + object destructor never gets a chance to run). + * Replaced all calls to exit() with calls to + ExitWithoutCleanup(). + * Tweaked FileDescriptorDataIO to compile correctly on + 64-bit systems. + * Fixed typedefs of int32/uint32 for the PPC64 platform. + * Fixed typedef of muscle_socklen_t on PPC64 + * ZipFileUtilityFunctions.cpp was calling newnothrow to + allocate an array. Changed it to call newnothrow_array + instead. + * RefCount::SetFromGeneric() would return B_NO_ERROR if + you tried to call SetFromGeneric(NullRef()), but it + wouldn't actually change the state of the Ref that + you called it on. It now sets the state to NULL in + that case. + * AtomicCounter.h now uses the public QAtomicInt API + when compiled with Qt 4.4.0 or higher. (When compiling + with Qt 4.0.0 through 4.3.x, it will still use the old + private atomic API) + * Applied some tweaks to MuscleSupport.h for better + MacOS/X Leopard compatibility. + * Tweaked SharedMemory.h to compile on MacOS/X Leopard. + +v3.40 Released 9/6/2007 + - Added experimental IPv6 support. To use MUSCLE with + IPv6, compile your code with the -DMUSCLE_USE_IPV6 + flag. The new typedef ip_address will then be compiled + as a 128-bit datatype (the new ip_address class) instead + of being typedef'd to uint32. + - Added a new PacketTunnelIOGateway class that is useful + for "tunneling" arbitrarily large Messages over a + packet-based protocol with a smaller maximum packet + size (e.g. UDP). The PacketTunnelIOGateway class + will packetize large Messages and reconstruct them + from the fragments at the other end. + - Added SetAutoReconnectDelay() and GetAutoReconnectDelay() + methods to the AbstractReflectSession class. You can use + these to easily configure your AbstractReflectSession to + automatically reconnect itself after its connection to + the server has been broken. + - Added a new, optional (autoReconnectDelay) to all the + AddNewConnectSession() methods in MessageTransceiverThread, + QMessageTransceiverTherad, ServerComponent, and + ReflectServer. If specified, this argument will cause + SetAutoReconnectDelay() to be called on the new session. + - Added a new PacketizedDataIO class that can be used + to "wrap" a streaming DataIO class (e.g. + TCPSocketDataIO) and make it act more like a + packet-style DataIO class (e.g. UDPSocketDataIO). + - Accept() now has an optional second argument, which + will return the IP address of the interface that + a connection was accepted on. + - Added a new IPAddressAndPort class which is handy + for encapsulating an IP address and port number + together in the same object. + - PutAcceptFactory() and RemoveAcceptFactory() now + accept optional local interface IP address arguments + so that you can specify that connections to the + server should only be accepted on a certain + interface, if that's what you want. + - PutAcceptFactory() now accepts an optional (retPort) + argument which you can use to find out which port + the factory was installed on. + - PutAcceptFactory() now puts its connection-accepting + socket into non-blocking mode so that the server + can't ever block inside Accept(). + - Added a GetLocalInterface() method to the + ReflectSessionFactory class so you can see which + interface(s) that factory is associated with. + - Added some more convenience methods to the Hashtable + class: versions of GetNextKey(), PeekNextKey(), + GetNextValue(), and PeekNextValue() that take + a pointer-reference as their sole argument. + - ReflectSessionFactory objects are now auto-assigned + a globally unique ID number when they are created. + This number can be accessed by calling + ReflectSessionFactory::GetFactoryID(). + - Added a simple portscan utility (called "portscan") + to the tests folder. + - Added a GetChildProcessID() method to the + ChildProcessDataIO class, which returns the child + process's process ID (pid). + - Added a ByteBufferDataIO class that lets you read/write + ByteBuffer objects using the DataIO interface (as if + they were files). + - Added GetFirstFieldNameString() and GetLastFieldNameString() + convenience methods to the Message class, so that + you don't need to set up a MessageFieldNameIterator + just to get a single field out of the Message. + - Added a GetSourceOfLastReadPacket() method to the + UDPSocketDataIO class, so that you can find out after a + Read() call where the data you just read came from. + - Added a SetSendDestination() method to the + UDPSocketDataIO class, so that you can have Write() + call sendto() instead of send() if you prefer. + - Added a GetSendDestination() method to the + UDPSocketDataIO class. + - Added a testpacketio test to the tests folder, to + unit-test the PacketizedDataIO class. + - Added a testpackettunnel test to the tests folder, + to unit-test the PacketTunnelIOGateway class. + - Added a NestCount convenience class to the util + folder. This simple class handles some of the + drudge work associated with tracking recursion + levels of recursive function calls. + - Added a Reposition() method to the Hashtable class + that can be used to manually update the position + of an auto-sorted-by-value entry that has been + modified in-place. + - Added MoveToTable() and CopyToTable() convenience + methods to the Hashtable class. + - Made some minor optimizations to the auto-sort-by-value + code in the Hashtable class. + o Removed the MemoryBufferDataIO class, since it + was not very useful or well designed. + o Changed the MessageTransceiverThread class's + GetNextEventFromInternalThread() method so that + it passes back the ReflectSessionFactory's ID + (as a uint32) instead of its port number (as a + uint16). This was done because port numbers + are no longer unique identifiers for session + factories. + o Changed the ReflectSessionFactory::CreateSession() + method to take different arguments (note that this + will break old code that defined its own + ReflectSessionFactory subclasses! That code will + need to be updated to use the new arguments) + o The NetworkInterfaceInfo class's GetName() and + GetDescription() methods now return references to + String objects, instead of character pointers. + o Moved the declaration of Inet_NtoA() from + MiscUtilityFunctions.h to NetworkUtilityFunctions.h. + o Removed the GetLocalIPAddress() function from + NetworkUtilityFunctions.{cpp,h}, as it is redundant + with (and less useful than) the new + GetNetworkInterfaceInfos() function. + o Removed the GetPort() method from the + ReflectSessionFactory class, since factories are + no longer necessarily associated with exactly + one port number. + * The final argument to the CreateAcceptingSocket() + function in NetworkUtilityFunctions.h was documented + incorrectly. It lets you specify the IP address of + a local interface, not a client's IP address. + * The final argument to the SetPort() function in + AcceptSocketsThread.h was documented incorrectly. + It lets you specify the IP address of a local interface, + not a client's IP address. + * Fixed a bug in ReflectServer's ReflectSessionFactory + design that caused muscled not to work correctly if + you told it to listen to more than one port at once. + * Added an #ifdef to NetworkUtilityFunctions.cpp so + that it will compile cleanly on BeOS/R5.0.3 + * HashtableIterator::GetValue() was returning a const + reference. Changed it to return a non-const reference, + since there's no reason why you shouldn't be able to + modify the returned object. + * Fixed a bug where calling PutAcceptFactory() with + the port argument set to zero would cause all already + attached accept-factories to be removed. + * Fixed a minor bug in the ReflectServer event loop, + where the fd_sets would be inspected after select() + returned -1/EINTR. This could cause spurious + (albeit generally harmless) read-ready or + write-ready events to be detected. + * The assignment operator, equality operator, + ContainsValue(), and the batch Put() and Remove() + methods in the Hashtable class now specify the + HTIT_FLAG_NOREGISTER flag in their HashtableIterator + objects, for better thread safety. + +v3.34 Released 7/12/2007 + - Optimized CreateConnectedSocketPair() by having it + use the UNIX socketpair(AF_UNIX) function on systems + that support that call. + o Removed the optional (useNagles) argument from the + CreateConnectedSocketPair() function, since there + is little or no point in using Nagles algorithm + for sockets within the same process. (if you + really needed it for some reason, you could still + call SetSocketNaglesAlgorithmEnabled() manually on + the resulting sockets afterwards) + o tests/cvscopy.cpp is now tests/svncopy.cpp, since I + don't use CVS anymore. + * Thread::WaitForNextMessageAux() wasn't handling file + descriptors properly when called with a wakeupTime + less than or equal to the current time. Fixed. + o Added a testpulsenode.cpp file to the tests folder, to + test the reliability and scalability of the PulseNode + timed-event-callback implementation. + * Fixed some valgrind hits in NetworkUtilityFunctions.cpp + by having the code check the sin_family of sockaddr + structs before reading any IPv4-specific fields. + * QMuscleSupport.h wouldn't compile under Qt 3.x. Fixed. + * Added Mika Lindqvist's patches to get QMuscleSupport.h + to compile under older versions of Microsoft Visual C++. + * The SharedUsageLimitProxyMemoryAllocator class was not + always freeing all of the cached memory during large free + operations, which could result in the process-memory-cache + getting too large. Fixed. + * Fixed a bug in the PulseNode class's linked list code + that could cause PulseChildren to not have their Pulse() + methods called at the proper times. + * Merged in Bryan Varner's patch to the MessageTransceiverThread + Java class so that spurious connect-succeeded tags are no longer + sent by the I/O thread to the main thread. + +v3.33 Released 6/6/2007 + - Added a MultiDataIO class, for convenient writing of + mirrored files, etc. + - Added a qtsupport/QMuscleSupport.h header file to + contain Qt-specific utility/helper code. Currently + this file contains only a QString specialization of + the HashFunctor template, to allow QStrings to be + used as keys in a Hashtable. + * SetupSystem.cpp builds again under BeOS (worked around + BeOS's non-standard gmtime_r() call) + * Merged in Lior Okman's patch to the Java client code + to fix a bug where partially received Messages might + get dropped, causing a TCP disconnect. + * Added a loop around the Unix implementation of + SharedMemory::AdjustSemaphore()'s call to semop(), + so that if semop returns EINTR, the call is retried. + +v3.32 Released 5/2/2007 + - Added a new class, SharedFilterSessionFactory, which looks + at the contents of a shared memory region to decide which + client IP address(es) to allow to connect to the server. + - Added a IsIPAddress(const char *) convenience method + to NetworkUtilityFunctions.{cpp,h}. This method returns true + iff its argument is a string representing an IP address + in human-readable ASCII format. + - Added a new function GetNetworkInterfaceInfos() to + NetworkUtilityFunctions.{cpp,h}. This function returns + a list of NetworkInterfaceInfo objects describing the + various network interfaces currently available on the + host machine. + - tests/testnetutil.cpp Now tests GetNetworkInterfaceInfos() + by calling it and printing out the results. + - Added a new argument (emitEndMessageBatchIfNecessary) + to the QMessageTransceiverHandler::Reset() and + QMessageTransceiverThread::UnregisterHandler() methods, so + that if these methods are called while in the middle of a + receiving-Messages batch, the un-registering of the handler + will no longer cause an imbalance between the number of + BeginMessageBatch() and EndMessageBatch() signals that + get emitted by the handler. (You can override this behavior + by passing in false for the new argument) + - The muscle/java/build.xml file now builds JavaDocs too. + o AbstractReflectSession::GetHostName() now returns a + (const String &) instead of a (const char *). + * Fixed a bug in SharedMemory::DeleteArea() that was + preventing the shared memory area's semaphore from + being deleted. + * Rewrote the linked-list handling code in the + QMessageTransceiverThread class to use a doubly-linked-list. + This fixes a bug where the previous singly-linked-list + would occasionally not get updated properly, resulting + in the failure to emit EndMessageBatch signals in some + circumstances. + * The GlobalMemoryAllocator class now calls AboutToFree() + if AboutToAllocate() succeeded but malloc() or realloc() + failed. That way AboutToFree() can undo AboutToAllocate()'s + side effects in the usual fashion. + * The SharedUsageLimitProxyMemoryAllocator class would + deduct a buffer's memory size from the tally twice in + the event of a memory failure. Fixed. + * The SharedUsageLimitProxyMemoryAllocator class now detects + when a program tries to reduce its memory usage counter to + less than zero, and clamps the counter to zero instead of + letting it wrap around to the 4 gigabyte range. + * The GlobalMemoryAllocator class now calls SetAllocationHasFailed() + on the MemoryAllocator object only if the second try to + allocate memory has also failed. That way, if the first try + fails, but the MemoryAllocator's AllocationFailed() method + is able to free up some space to make the second try succeed, + program operation can proceed without any further disruption. + * ProxyMemoryAllocator::AllocationFailed() no longer calls + SetAllocationHasFailed(true) on its slave allocator. + That call is to be made solely by + ProxyMemoryAllocator::SetAllocationHasFailed(), instead. + * AutoCleanupProxyMemoryAllocator::AllocationFailed() now + calls up to its parent class as well as doing its regular + OutOfMemory() callbacks. + * Cleaned up the JavaDoc comments so that they now build + without any warnings from the javadoc utility. + * The POSIX implementation of GetCurrentTime64() wasn't + handling Daylight Savings Time properly, which meant its + result would be off by an hour for part of the year. Fixed. + * Changed some calls to gmtime() and localtime() to gmtime_r() + and localtime_r() respectively, to make them thread-safe. + +v3.31 Released 3/14/2007 + - The AtomicCounter API is now aware of Qt 4's atomic + counter API, and will use that as its implementation in + Qt-enabled programs compiled on systems where MUSCLE's + own inline-assembly support isn't available. + - hexterm will now send ASCII bytes if you prefix them + with a slash (e.g. "/0 /1 /2" sends 0x30, 0x31, 0x32) + - Added a Hashtable::IndexOfValue() function that lets you + find the index of the first (or last) value of a given + type in a Hashtable. (with O(N) search time) + - Added implementations of the GetByteBufferFromPool() and + GetMessageFromPool() functions that take a user-specified + ObjectPool instead of using a default object pool. + - Added support for a new compiler flag, MUSCLE_64_BIT_PLATFORM, + which can be defined in your Makefile if you are on a 64-bit + platform that isn't autodetected inside of MuscleSupport. + - Added INT32_FORMAT_SPEC and UINT32_FORMAT_SPEC macros so + that printf() statements can be made less architecture-specific. + - Added a section to the tests/testtypdefs.cpp test that checks + to make sure the *_FORMAT_SPEC macros are working correctly on + the current platform. + - DataNode::GetChildIterator() now takes an optional flags parameter + that is passed on to the HashtableIterator constructor. + - Added a DataNode::HasChildren() convenience method. + o Made the String class's destructor inline. + * SetupSystem.cpp wouldn't compile on 64-bit systems when + using Mutexes to implement atomic operations. Fixed. + * Merged in Lior Okman's patch to the Java client code that + sets the socket's send and receive buffers to 128KB. + * Merged in Lior Okman's patch to the Java client code to + fix an occasional busy-loop that could occur when + receiving very large (>400KB) Message objects. + * Merged in Nathan Whitehorn's patch to the FreeBSD + #ifdef's in NetworkUtilityFunctions.cpp. + * Fixed a bug in MemMem() that where an incorrect return + value would be returned when both buffers were the same size. + * Fixed a bug in RawQueryFilter::SetFromArchive() that could + cause a crash. + * Some of muscle's text output would be wrong on 64-bit + platforms. Fixed by changing all printf()'s to use the new + INT32_FORMAT_SPEC and UINT32_FORMAT_SPEC macros when appropriate. + * Fixed a bug in the Makefiles so that g++ now compiles all code + with all warnings enabled (except the multi-char constants warning). + * Went through the test files and fixed the minor problems that + were causing them to generate compiler warnings with -Wall enabled. + * hexterm now prints out an error message when it aborts due to + an invalid return code from Read() or Write(). + +v3.30 Released 1/16/2007 + ***NOTE*** In this release, the byte-swapping functions for + floating point and double-precision floating point operations + have been renamed. If your code handles floating-point byte + swapping itself, you will need to update your code before it + will compile. See the FLOAT_TROUBLE and DOUBLE_TROUBLE comments + in support/MuscleSupport.h for details about this issue. + - Added a QMessageTransceiverHandler helper class to + QMessageTransceiverThread.{cpp,h}. This class handles the + multiplexing and demultiplexing of multiple session objects inside + a single QMessageTransceiverThread object, so that Qt-based programs + that use a N:1 session-to-thread model will be easier to write + and maintain. + - Added a QMessageTransceiverThreadPool helper class to + QMessageTransceiverThread.{cpp,h}. This class manages the automatic + creation of QMessageTransceiverThreads when QMessageTransceiverHandlers + are set up, so that an N:1 session-to-thread model can be implemented + automatically in Qt-based programs. + - Rewrote hexterm's output routine to be more useful; it now outputs + both ASCII and hex data, in a format similar to that used by od. + - Added chatclient.cpp to the tests folder. chatclient.cpp is a + simple command-line BeShare compatible chat client (previously + distributed separately as "Clyde") + - Added a minichatclient.c to the tests folder. minichatclient.c + is a C-only implementation of chatclient.cpp, just to show how + it can be done using only the MiniMessage and MiniMessageGateway APIs. + - Added a MemMem() function to MiscUtilityFunctions.{cpp,h}. MemMem() + is similar to strstr(), except that instead of operating on + NUL-terminated strings, it operates on binary data. + - Rewrote the PulseNode class to be more efficient when many children + are present -- PulseNode now stores its children in ordered linked + lists rather than in a Hashtable, and it no longer needs to iterate + over all children in order to recalculate the next event time. + - Merged in Lior Okman's enhancement to the Java API: + StringBuffer is now used in toString() methods, instead of + concatenating strings directly. + - Merged in Lior Okman's enhancement to the Java API: + Faster ByteBuffer operations are now used to fill in arrays, + instead of looping over the arrays and manually setting the values. + - Merged in Lior Okman's enhancement to the Java API: + Use List instead of Vector and HashMap instead of HashTable. This is + to lose the extra unneeded synchronization point that exists in these + old and deprecated (as of Java 1.2) classes. Since ByteBuffer requires + at least JDK 1.4 anyway, there is no need to use the older API. + - Merged in Lior Okman's enhancement to the Java API: + Use Iterator instead of Enumeration - same reason as above. + Note that this change will break compatibility with old Java + code that calls Message.fieldNames() -- it will need to be + updated to use an Iterator instead of an Enumeration. + - Merged in Lior Okman's enhancement to the Java API: + Changed the implementation of Queue.removeAllElements() to a + more efficient implementation. + - Merged in Lior Okman's enhancement to the Java API: + Modified the ThreadPool to use a ThreadGroup and provide thread names. + This makes the class more profiler-friendly. + - Merged in Lior Okman's enhancement to the Java API: + Message.flatten() is faster now, because it doesn't call + flattenedSize() as much anymore. + - Merged in Lior Okman's patch that adds a setOutgoingEncoding() + method to the Java IOGateway classes, so that you can change + message encodings on the fly. + - Made the IOGateway _outgoingEncoding variable private: + use setOutgoingEncoding() and getOutgoingEncoding() to access it. + - Merged in Nathan Whitehorn's patch to make FinalizeAsyncConnect() + work properly under FreeBSD 7. + - Merged in Nathan Whitehorn's patch to fix a bug in SSLSocketDataIO + (previously, it could sometimes error out if the message length + exceeded the network's MTU) + - Rewrote the B_SWAP_* macros as C-compatible inline functions + (previously they called the muscleSwapBytes() template function, + which made them unusable in C programs) + - Added the new replacement floating-point byte-swap API to + support/MuscleSupport.h, including the following new functions: + B_HOST_TO_BENDIAN_IFLOAT B_BENDIAN_TO_HOST_IFLOAT + B_HOST_TO_LENDIAN_IFLOAT B_LENDIAN_TO_HOST_IFLOAT + B_HOST_TO_BENDIAN_IDOUBLE B_BENDIAN_TO_HOST_IDOUBLE + B_HOST_TO_LENDIAN_IDOUBLE B_LENDIAN_TO_HOST_IDOUBLE + - Added a -DMUSCLE_AVOID_INLINE_ASSEMBLY flag that you can set if + you want to avoid the use of inline assembly for some reason. + - Changed the ConvertFromNetworkByteOrder() and ConvertToNetworkByteOrder() + private virtual methods in the Message::PrimitiveDataTypeArray private + class by changing them to operate on N items at once, instead of having + to be called repeatedly for each item in an array. + - PointerDataArray::ConvertFromNetworkByteOrder() and + ConvertToNetworkByteOrder() now cause an assertion failure if called + (because they should never be called: pointers aren't serializable) + - Added a set of common comparison functions (Int8CompareFunc, + Int16CompareFunc, Int32CompareFunc, Uint8CompareFunc, etc) + to MuscleSupport.h because I was tired of reimplementing them + separately every time I needed to sort a Queue or a Hashtable. + - support/MuscleSupport.h now defines a new preprocessor constant, + SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE, when compiled on an OS + that doesn't know how to select on file descriptor (e.g. stdin). + Currently that includes Win32 and BeOS. + o Removed the GetChildren() and GetPulseChildrenIterator() methods + from the PulseNode class, since they are no longer applicable. + o Separated the single-argument Hashtable and Queue constructors into + two separate constructors: a zero-argument default constructor, and + a single-item constructor tagged "explicit". That way nonsensical + conversions (e.g. myHashtable.Put(5)) get caught at compile-time + rather than becoming hard-to-discover run-time errors. + o Rewrote muscleSwapBytes() to use a union instead of pointer-magic. + o The Java IOGateway classes are once again declared public instead + of being package-private -- that way you don't have to use the + MessageIOGatewayFactory class if you don't want to. + o MakePrettyTypeCodeString() is now implemented as an inline function + instead of a preprocessor macro. + * Merged in Lior Okman's fix for the flattenMessage() methods + in the MessageIOGateway classes, to make them work reliably + with non-blocking sockets. + * Fixed a bug in MessageTransceiverThread.cpp that would cause + many of the commands sent to the internal thread to be received + by all of the internal thread's sessions instead of just the + intended target. + * Fixed some bugs in testendian.cpp, and made its output prettier + and more informative. + * Removed the following functions from MuscleSupport.h: + B_SWAP_FLOAT B_SWAP_DOUBLE + B_HOST_TO_LENDIAN_FLOAT B_HOST_TO_BENDIAN_FLOAT + B_HOST_TO_LENDIAN_DOUBLE B_HOST_TO_BENDIAN_DOUBLE + B_LENDIAN_TO_HOST_FLOAT B_BENDIAN_TO_HOST_FLOAT + B_LENDIAN_TO_HOST_DOUBLE B_BENDIAN_TO_HOST_DOUBLE + Because they cannot be made to work reliably on x86-architecture + chips. (See the FLOAT_TROUBLE and DOUBLE_TROUBLE comments in + support/MuscleSupport.h for details) + * Message.py's unit test now correctly reads and writes its file + in binary mode. Thanks to David Rene for reporting this bug. + +v3.24 Released 11/28/2006 + - The MacOS/X implementation of GetRunTime64() is now implemented + using the CoreServices UpTime() function instead of POSIX times(), + which makes it more efficient, allows it to return more accurate + values, and avoids some potential problems with wrapping. + - Added GetKey() and GetValue() convenience methods to the + HashtableIterator class. + - Added -- and ++ operators to the HashtableIterator class. + - Added SetBackwards() and IsBackwards() methods to the + HashtableIterator class. + - Added a GetFlags() method to HashtableIterator class. + - Added a new MessageFieldNameIterator constructor that takes + a Message object. This is more efficient than calling + msg.GetFieldNameIterator() and requires less typing. + - Added ++ and -- operators to the MessageFieldNameIterator class. + - Added a GetFieldName() method to the MessageFieldNameIterator class. + - Each DataNode object now keeps a running maximum of the IDs in + the child nodes that are attached to it. You can access this + running maximum via the new GetMaxKnownChildIDHint() method. + This value can be useful when generating a unique name for + a new child node. + - Also added a SetMaxKnownChildIDHint() method to the DataNode + class. This lets you manually reset the hint if you need to. + - Updated tests/testhashtable.cpp so that it now tests for the + HashtableIterator bug described below. + - The MessageReplaceFunc callback used by CloneDataNodeSubtree() + now takes a node-path as its first argument, in addition to the + other arguments it took before. + - The Python error strings generated by the ConvertMessageToPyObject() + in PythonUtilityFunctions.cpp are now a bit more informative (they + contain the problematic field name and/or type code) + o Reduced the default output-stall timeout from 20 minutes + to 3 minutes. + o Removed the () operator from the MessageFieldNameIterator class + because it was inconsistent with the operators of other iterator + classes, and rarely (never?) used anyway. + o Updated the copyright notices to 2007 Meyer Sound Laboratories Inc + (a.k.a. the new owners of what was Level Control Systems) + * The output-stall-detector mechanism would not disconnect + moribund clients in a timely manner if the server was + completely idle. Now it does. + * Fixed a subtle bug in the HashtableIterator class that could + cause the iterator to skip past the 2nd item in a traversal + if the 1st item in the traversal was deleted at the wrong time. + * Updated the portablereflectclient.cpp and portableplaintextclient.cpp + example programs to include a hack-around for Win32's inability + to select() on stdin. + +v3.23 Released 9/15/2006 + - Added Nathan Whitehorn's SSLSocketDataIO class to the dataio + subfolder. This class can be used to layer MUSCLE traffic over + an SSL connection. Thanks Nathan! Note that this code requires + the OpenSSL developer's toolkit to be installed before it will + compile. + - Enhanced the ParseArgs() routine to handle spaces intelligently + (e.g. "x = 5" is now equivalent to declaring a key "x" with value + "5", not declaring three separate variables named "x", "=", and "5") + - Added Put() and Remove() methods to the Hashtable class that + can put or remove the contents of an entire Hashtable at once. + - Added NybbleizeString() and DenybblizeString() convenience functions + to the MiscUtilityFunctions API. + - Added a MUSCLE_USE_QUERYPERFORMANCEHARDWARE compile-time flag that + tells MUSCLE to use QueryPerformanceCounter() under Windows instead + of timeGetTime(). Specifying this flag improves GetRunTime64()'s + accuracy, but QueryPerformanceCounter() is known to have problems on + certain PC systems. + * Fixed the Win32 and POSIX implementations of GetRunTime64() so that + when the underlying OS clock "wraps" around to zero, it is detected + and transparently handled so that the values returned by GetRunTime64() + continue to increase monotonically. + * Fixed the vc++/muscle.dsp project file so that things link properly + again (thanks to Maurizio for help with this) + * Added several tweaks to compile properly under MS Visual Studio 2005. + * Made sure the files in the vc++ subfolder have Windows line endings. + * Fixed a couple of bugs in the String class -- empty strings that + had extra space preallocated in them could end up with unitialized + garbage in them. + * MuscleSupport.h now #defines WIN32 if _MSC_VER is defined. + * Updated the QMessageTransceiverThread and QAcceptSocketThread + classes further, so that they compile under Qt4 even when Qt3 + compatibility support is disabled. + * Fixed a problem under Win32, where GetSystemPath() would return an + incorrectly formed path when unicode characters were present in the + path name. + +v3.22 Released 7/5/2006 + - Updated the Mutex, Thread, and QMessageTransceiverThread classes so + that they are compatible with both Qt3 and Qt4. + - Added a Python implementation of the zlib/ZLibUtilityFunctions.{cpp,h} + API. It is in python/ZLibUtilityFunctions.py + * Fixed a valgrind hit in the Linux implementation of GetSystemPath() + * Commented out the buggy-QueryPerformanceCounter() warning under Win32, + since it usually causes more confusion than enlightenment when it + is printed. + * Changed the custom-event codes in QMessageTransceiverThread.cpp + and QAcceptSocketsThread.cpp to be in the allowed range for user-codes. + * Added Lior Okman and Rony Gutherz' patches to re-enable compilation + on 64-bit systems, Solaris, MSVC, and Windows 2000. + * Added Mika Lindqvist's patch to allow use of the newnothrow macro + under VC++6. + * Updated the Makefile in the borland subdirectory to compile correctly. + +v3.21 Released 5/27/2006 + - Added SYSTEM_PATH_USERHOME, SYSTEM_PATH_DESKTOP, and + SYSTEM_PATH_DOCUMENTS, and SYSTEM_PATH_ROOT to the list + of directories returnable by GetSystemPath(). + - GetHostByName() now requires a second argument, (expandLocalhost), + which determines whether 127.0.0.1 should be returned verbatim, or + expanded out to the primary local IP address. + - Added an (expandLocalhost) argument to the various AddNewConnectSession() + methods that take a string for a hostname argument. + - Added an (expandLocalhost) argument to the GetPeerIPAddress() function. + - Added a RemoveEscapeChars() utility function to StringMatcher.{cpp,h} + - Rewrote GetLocalIPAddress() to use the if_nameindex() API if it + is available. If not, it will fall back to the old method of using + gethostname(). + - StorageReflectSession::SaveNodeTreeToMessage() and + StorageReflectSession::RestoreNodeTreeFromMessage() now both take + an optional (maxDepth) argument which can be used to specify the + maximum depth of the node-subtree to be saved or restored. + - The PR_COMMAND_GETDATATREES Message now has an optional PR_NAME_MAXDEPTH + int32 field: If specified, the returned data trees will be clipped to + the specified maximum depth + - Added a PutAndGet() convenience method to the Hashtable class. + This method puts a new object into the Hashtable, and returns a pointer + to the object in the table. + +v3.20 Released 3/20/2006 + - Added SetGlobalQueryFilterFactory() and GetGlobalQueryFilterFactory() + calls, so that it is possible for MUSCLE-based servers to install + their own custom QueryFilterFactory objects if desired. + o Moved the DataNode class out into its own file, DataNode.{cpp,h}. + It is no longer an inner class of the StorageReflectSession class. + o All QueryFilter::Matches() methods now take a DataNode pointer + as their second argument. None of the built-in QueryFilter subclasses + use this value, but it is available so that custom QueryFilters can + use DataNode information in their matching decisions if necessary. + o Renamed InstantiateQueryFilter() to CreateQueryFilter(), and + made it a virtual method in the new QueryFilterFactory class, + instead of a global function. + * Added serialization to muscleAlloc()/muscleRealloc()/muscleFree(), + to avoid potential race conditions in multithreaded programs that + have memory-usage-tracking enabled. + * Fixed a bug in the Java receive code that could cause a + parse error and subsequent disconnect if the Message stream + was uncompressed (DEFAULT_MESSAGE_ENCODING) and a large + Message was followed by a small one. + * Fixed a typo in the java/build.xml file. + * Rewrote MusclePowerPCSwapDouble() and MuscleX86SwapDouble() + to use unions instead of C-style casting. Doing it this way + makes them more readable and avoids errors due to uncontrolled + interactions with g++'s optimizer. + * Made the testendian test program a little more robust. + +v3.11 Released 3/1/2006 + - Added a testsysteminfo.cpp test to the test folder, to test + the functions in the SystemInfo.{cpp,h} files. + - Merged in Lior Okman's updates to the muscle Java classes so that + they now support the new, more efficient java.nio.* interfaces. + Note that this means that the Java API now requires Java 1.4.0 + or higher; let me know if that is a problem for you. + - Added zlib/ZipFileUtilityFunctions.{cpp,h}, which contain functions + that create a .zip file from a MUSCLE Message, and vice versa. + Also added a testzip.cpp program to the test folder to test them. + - Added a more convenient Inet_NtoA(uint32) function (that returns + a String) to MiscUtilityFunctions.{cpp,h} + o Optimized TCPSocketDataIO::FlushOutput() slightly under Linux. + * Merged in Lior Okman's AMD-64 compatibility patch. + * Patched NetworkUtilityFunctions.cpp to handle net_length_t + properly under *BSD. + +v3.10 Released 1/16/2006 + - Added the JCraft JZLib code to the included Java code tree, so that + the MUSCLE Java code can again be compiled as provided. + - Added a hashCode() method to the Java com.lcs.muscle.support.Point class. + - Added NybbleizeData() and DenybbleizeData() convenience functions + to MiscUtilityFunctions.{cpp,h}. + - Added GetFirstKey(), GetFirstValue(), GetLastKey(), and GetLastValue() + utility methods to the Hashtable class. + - Added a HTIT_FLAG_NOREGISTER flag to the HashtableIterator class + that (when specified) will prevent the HashtableIterator from registering + itself with the Hashtable. This option allows you to do a 100% + thread-safe Hashtable iteration, at the expense of requiring you to + guarantee that the Hashtable won't be modified during the traversal. + - Added a HasItems() convenience method to the Queue and Hashtable classes. + This method returns true iff there is at least one item present in + the object (i.e. it is the logical negation of the IsEmpty() method) + - Added a HasNames() convenience method to the Message class. This method + returns true if the Message contains any fields. + o Removed the Flattenable::CopyToImplementation() methods since they were + redundant. Instead, CopyTo() just calls CopyFromImplementation() with + the arguments reversed. + o Replaced the boolean (backwards) argument for HashtableIterators with a + uint32 (flags) argument. Currently supported flags are + HTIT_FLAG_BACKWARDS (same as backwards=true in older versions) and + the new HTIT_FLAG_NOREGISTER (see above for details) + o Inlined some trivial one-liner methods in the Message class, for efficiency. + * Merged in Eli "Scanty" Dayan's portability fixes so that the + code again compiles properly under Solaris. Thanks Scanty! + * const methods in the Message class are now properly thread-safe + (provided the Message object isn't altered by any other threads + during their execution), thanks to the use of the + HTIT_FLAG_NOREGISTER flag. + * The math used in the MUSCLE_POWERPC_TIMEBASE_HZ implementation of + GetRunTime64() would overflow after a week or two of uptime, + causing GetRunTime64() to return incorrect results. Fixed. + * PythonUtilityFunctions.cpp now compiles correctly under MacOS/X Tiger. + * Tweaked MuscleSupport.h so that MacOS/X will properly use the + x86 assembly language functions when running on an Intel-based system. + +v3.03 Released 10/10/2005 + - Added a GetLocalIPAddress() function to NetworkUtilityFunctions.{cpp,h}. + This function returns the IP address(es) of the machine it is running on. + - Refactored the ThreadWorkerSessionFactory::CreateSession() method out + into two methods: CreateSession() and CreateThreadWorkerSession(). That + way subclasses can override CreateThreadWorkerSession() but still take + advantage of the session-accepted-notification functionality present + in CreateSession(). + - MessageTransceiverThread now identifies sessions via their root node + path instead of just their session ID. (e.g. "/192.168.0.5/17", not "17") + - Added David Grossman's -DMUSCLE_PREFER_QT_OVER_WIN32 patch, so that you + can choose at compile time whether Muscle should prefer to use Win32 or + Qt APIs for its threading/synchronization mechanisms. + - Added the 'cvscopy' utility program to the test folder. + * GetPeerIPAddress() and GetHostByName() now call GetLocalIPAddress() if + necessary to determine the local IP address, rather than returning + 127.0.0.1. + * GetSystemPath(SYSTEM_PATH_EXECUTABLE) would break under MacOS/X if + the executable path contained any non-ASCII characters. Fixed. + * GetSystemPath() was broken under Win32 if Unicode support was + enabled. Fixed. + * hexterm now compiles properly under MacOS/X. + +v3.02 Released 9/10/2005 + - The second argument to Hashtable::GetOrPut() is now optional. + - Merged in Lior Okman's changes to the Java client API: these + included several bug fixes and the added ability to send + compressed Messages. (If you have JZLib in your class path, + you can receive compressed Messages also) + - Added Lior's build.xml file for ant-based compilation and + JAR-packaging of the muscle java API (into the java folder) + - Added FailoverDataIO.{cpp,h} to the support folder. FailoverDataIO + is a class that supports automatic failover across multiple redundant + DataIO streams. + - Connect() now takes an optional timeout argument, for people + who are too impatient to wait for the operating system's standard + TCP connect timeout period to elapse. + - The ParseFile() function in MiscUtilityFunctions.{cpp,h} now supports + hierarchical sub-sections (delimited in the text file with "begin foo" + and "end" tag lines). + - Added testparsefile.cpp to the test folder. + o Moved the implementations of Inet_AtoN(), Inet_NtoA(), + SetLocalHostIPOverride() and GetLocalHostIPOverride() from + NetworkUtilityFunctions.cpp to SetupSystem.cpp, so that projects + can include MiscUtilityFunctions.cpp without having to also + include NetworkUtilityFunctions.cpp + o Removed the MUSCLE_USE_CLONE support from the Thread class, since it + didn't work properly anyway. + * The Win32 implementation of the Thread class would leak a thread handle + each time the internal Thread terminated. Thanks to Eivind Midtgård + and Raymond Dahlberg for providing the fix! + +v3.01 Released 8/13/2005 + - Added a "shared" parameter to the BindUDPSocket() function, + to support the simultaneous reception of broadcast UDP packets + by multiple processes on a single computer. + - Updated the included zlib distribution to v1.2.3 + - Updated the hexterm app to handle UDP packet I/O (in addition + to the TCP stream and serial device data I/O it could do before) + - Added equality (==) and inequality (!=) operators to the Hashtable class. + * GetSystemPath(SYSTEM_PATH_EXECUTABLE) now returns the proper + path under Linux (it uses the /proc filesystem to figure it out) + * ChildProcessDataIO now uses the constants found in sys/ttydefaults.h + to fill in the termios struct, instead of using hard coded magic numbers. + +v3.00 Released 7/12/2005 + NOTE: THIS RELEASE BREAKS SOURCE CODE COMPATIBILITY WITH PREVIOUS + RELEASES OF THE MUSCLE API. IF YOU HAVE CODE THAT WAS WRITTEN FOR + PREVIOUS VERSIONS OF MUSCLE, YOU WILL NEED TO MODIFY IT SOMEWHAT + IN ORDER FOR IT TO COMPILE AND RUN PROPERLY. SEE THE TWO ITEMS + MARKED WITH (o) BELOW FOR DETAILS. + - Added Matt Emson's client API for Delphi, in the "delphi" + subdirectory. Thanks, Matt! + - Updated the included zlib distribution to v1.2.2. + - Added StoreTraceValue(), TCHECKPOINT(), SetTraceValuesLocation(), + and associated functions to MuscleSupport.h. These functions can + be useful in tracking down where code is executing at when a + debugger isn't available. + - Added a maxDepth argument to the LogStackTrace() call. + - Added a PrintStackTrace() function to syslog/SysLog.{cpp,h}. + This is similar to LogStackTrace(), but the stack trace + goes directly to stdout instead of going to the log facility. + - Added a "win32client" test/example app to the test folder. + This app test/demos the Win32MessageTransceiverThread class. + A VC++ project file (win32client.vcproj) is also included. + - Added a SetSignalHandlingEnabled() method to the ReflectServer + class, to allow servers to exit cleanly when an interrupt + signal is received. + - muscled now accepts an optional "catchsignals" argument that + enables handling of interrupt signals (Control-C) to initiate + a controlled shutdown. + - muscleMax() and muscleMin() can now take up to five arguments, + for convenience. + - If you define the compiler constant MUSCLE_ENABLE_MEMORY_PARANOIA + to an integer value (and also define MUSCLE_ENABLE_MEMORY_TRACKING), + then the muscle memory allocator/deallocator code will add that many + guard values to the front and end of all dynamically allocated + buffers in order to detect bad memory writes. The guard values will + be checked whenever the buffers are freed or resized, to make sure + they haven't been overwritten, and it will also fill all allocated + and freed buffers with special garbage bytes (0x55 and 0x66), + so that it will be more obvious when freed or uninitialized + memory is read. This slows down execution a bit and uses up + extra memory, so it should only be enabled while debugging. + - Added a MemoryParanoiaCheckBuffer() function to + GlobalMemoryAllocator.{cpp,h}. Call this function to manually + validate that an allocated memory buffer hasn't been corrupted. + - All AbstractObjectRecycler objects (i.e. all ObjectPools) + now register themselves in a global linked list, so that + they can be iterated over. + - Added the function GlobalFlushAllCachedObjects() to the + AbstractObjectRecycler class. + - Added a FlushCachedObjects() virtual method to the + AbstractObjectRecycler class. This method is implemented + in the ObjectPool subclasses to call Drain(). + - The CompleteSetupSystem destructor now calls + AbstractObjectRecycler::GlobalFlushAllCachedObjects(), so + that all the ObjectPools get flushed before any static objects + start getting destroyed. This helps avoid dangling pointer + problems caused by the undefined ordering of static object + destructor calls. + - Added GetCount() and SetCount() methods to the AtomicCounter + class, since it's occasionally necessary (albeit discouraged) + to get or set the counter's value explicitly. + - Added IsRefPrivate() and EnsureRefIsPrivate() methods to + the Ref class to help facilitate Copy-on-Write semantics. + - Added a FlushInput() method to the PlainTextMessageIOGateway class. + - Added a BUILDOPTIONS.txt file to the muscle folder, to document + the various compile-time flags that muscle uses. + - Added WriteFully() and ReadFully() convenience methods to + the DataIO class. These methods call Write() or Read() + in a loop as necessary to guarantee that the whole buffer + get written. + - Added new convenience methods FlattenToByteBuffer(), + UnflattenFromByteBuffer(), FlattenToDataIO() and + UnflattenFromDataIO() to the Flattenable class, so that you + can now Flatten()/Unflatten() to/from ByteBuffers and + DataIOs with a single call when using blocking I/O. + - Added a GetLength() convenience function to the Flattenable + class, for easy determination of a DataIO's length in bytes. + - Rewrote InflateMessage() and DeflateMessage() so that they + spend less time holding the global muscle lock. + o Changed all Ref arguments to be passed by const reference + instead of by value. Note that this change will break most + current Muscle-using source code -- your source code will + need to be changed to match, in order for the virtual methods + to be overridden properly. To update your code, you should + change all function arguments of type BlahRef to (const BlahRef &). + o Moved the AbstractObjectRecycler pointer out of the Ref class + and into the RefCountable class, and changed it into an + AbstractObjectManager pointer. Removed the recycler + argument from all Ref class methods. This makes the + Ref class a bit simpler and more efficient, but it also + breaks existing source code. To update your code, you + should change all instances of BlahRef(blah, NULL) to + BlahRef(blah), and all instances of BlahRef(blah, NULL, false) + to BlahRef(blah, false). + o Replaced StorageReflectSession::DrainPools() with + AbstractObjectRecycler::GlobalFlushAllCachedObjects(), + which does the same thing but more thoroughly, since + it flushes all ObjectPools and not just a few of them. + o Moved a few overly large inline convenience functions into + SetupSystem.cpp, for faster compilation and smaller binaries. + o InflateMessage() now guarantees that the inflated Message + will have the same 'what' code as the passed-in Message + (the what code of the 'interior' Message is ignored). + * Added a newnothrow_array #define to MuscleSupport.h and + changed all the muscle code to use it instead of + newnothrow when allocating arrays. This macro works + the same as new (nothrow) for arrays, except it works + around a bug in gcc 3.x that could cause crashes on + memory failure. + * ChildProcessDataIO now forces the removal if the SIGHUP + signal handler in the child process, so that any + grandchild processes will be cleaned up when the child + process is killed. + * Improved ChildProcessDataIO handling of restricted + permissions on the /dev/tty* nodes. + * Under Win32, if there were no sockets to select() on, select() + would error out and the server loop would return. Fixed. + * Log() and LogTime() could forget to unlock the global + muscle lock if called re-entrantly. Fixed. + * Fixed a nasty bug in the MUSCLE_POWERPC_TIMEBASE_HZ inline + assembly implementation of GetRunTime64() that would cause + GetRunTime64() to very occasionally go into an infinite loop. + +v2.65 Released 5/20/2005 + - Optimized the String class by providing overloaded + implementations of various common methods that take a + (const char *) directly, rather than a (const String &). + This eliminates much of the need for the compiler to + construct and destroy temporary String objects when + using string literals. + - Added StartsWith() and EndsWith() convenience methods + to the Queue class. + o Made about a dozen one-liner String methods into into + inline methods, to reduce CPU overhead in calling them. + * Fixed a bug in the Windows implementation of + ChildProcessDataIO that would cause the child process + to be killed the first time it generated any text to stdout. + * Fixed a problem in GetHumanReadableTimeValues() when + compiling under Windows with pre-WinXP headers. Thanks + to Mika Lindqvist for helping with this fix. + * Added an #ifdef for _SOCKLEN_T to NetworkUtilityFunctions.cpp, + so that net_length_t will be typedef'd properly on systems + that need it. + +v2.64 Released 4/20/2005 + - ConvertMessageItemToPyObject() now handles restoring list + objects and dictionary objects. + - testendian.cpp now does a bit more thorough job of testing + the correctness of the byte swapping routines + - Merged in Mika Lindqvist's patch to add inline-assembly + implementations of the byte-swapping and atomic counter + functions for the Win32/Visual C++ build environment. + - Added Equals(char), EqualsIgnoreCase(char), EndsWith(char), + EndsWithIgnoreCase(char), StartsWith(char), and + StartsWithIgnoreCase(char) methods to the String class. + - Added an optional (maxResults) parameter to + ServerComponent::FindMatchingSessions(), and added a + ServerComponent::FindMatchingSession() convenience method. + - MuscleSupport.h now looks for a MUSCLE_FD_SETSIZE compiler + constant; if found, it will use that to force the host's + FD_SETSIZE to a new value (particularly useful under + Windows where FD_SETSIZE is a pitiful 64... just add + -DMUSCLE_FD_SETSIZE=512 to make it more reasonable) + - Added a SetFromGenericUnchecked() method to the Ref class, + for fast/unsafe reference-type conversion. + - Added testtime.cpp to the test folder, to test MUSCLE's + time/date handling functions. + - GetCurrentTime64(), GetHumanReadableTimeValues(), + ParseHumanReadableTimeString(), and GetHumanReadableTimeString() + now all take a timezone argument that indicates whether the uint64 + time value arguments are meant to represent local time or UTC. + These parameters have default values that are set to preserve + the previous behavior, for backward compatibility. + o Moved QMessageTransceiverThread's event handling code + out into a virtual separate method, called + HandleQueuedIncomingEvents(). + o Removed "volatile" keyword from the assembly byte-swapping + inline function implementations, since it is not necessary + and impedes optimization. + * Merged in David Grossman's Visual C++ compatibility changes. + * Fixed several bugs in the String class where memcpy() was + being called in situations where memmove() was necessary. + +v2.63 Released 2/6/2005 + - muscled now accepts "port=0" as a command line argument. + This argument will cause muscled to choose an available + TCP port to listen for incoming messages on. + - Added a GetHumanReadableTimeValues() function to + MiscUtilityFunctions.{cpp,h}. It's similar to + GetHumanReadableTimeString(), except the results + are returned as separate integers rather than as a String. + - Added an optional "muscleSingleThreadOnly" bool argument to the + ThreadSetupSystem and CompleteSetupSystem constructors. This + flag lets you globally disable all Mutexes and other + MUSCLE thread-safety mechanisms at run time. Useful if you + need to compile your app without the MUSCLE_SINGLE_THREAD_ONLY + compiler flag, but know (sometimes) at runtime that you will + be running the process single-threaded... + - Added a ChildProcessReadyToRun() hook method to the + ChildProcessDataIO class. This method is called from inside + the child process (except under Windows, which can't support it). + - Added a GetSessionSelectSocket() convenience method to the + AbstractReflectSession class. + o ChildProcessDataIO now has a separate LaunchChildProcess() + method, instead of launching the child process from inside + the ChildProcessDataIO constructor. + * Merged in Wilson Yeung's update to the C# client code -- + this update fixes an infinite-loop bug and makes flattening + and unflattening of Message objects more efficient. + * NetworkUtilityFunctions.cpp once again compiles under BeOS. + * Incorporated Stephane Petithomme's patch to allow muscle + to compile correctly under SunOS. + * Merged in Monni's patches to GetSystemPath() so that it + handles Windows Unicode paths correctly. + * Replaced the Win32 implementation of GetCurrentTime64() + with a more standard (read: accurate) implementation. + * The Windows implementation of GetHumanReadableTimeValues() + was not quite accurate. Fixed. + +v2.62 Released 1/1/2005 + - Added a SetUDPSocketBroadcastEnabled() function to the + NetworkUtilityFunctions API. + - Added a broadcastIP constant declaration to NetworkUtilityFunctions.h + - Updated the "listener" test program to send hex bytes that are + entered on stdin, to make outgoing TCP connections as well as + accepting incoming ones, and to be able to connect to an + RS232 serial port, if desired. Renamed "listener" to "hexterm" + to reflect its expanded functionality. + - Added an AdoptBuffer() method to the ByteBuffer class, so that + you can populate a ByteBuffer with a pre-existing array of bytes, + if you are careful about memory-ownership issues.. + - Added a ChildProcessDataIO class, which is useful for spawning + child processes and communicating with them interactively via + their stdin/stdout streams. Works under POSIX, Windows, OS/X, and BeOS. + - Added a RemoveANSISequences() function to MiscUtilityFunctions.{cpp,h}. + This function strips all ANSI escape sequences from a given String. + - Added a testchildprocess.cpp test to the test folder. This + program is used to make sure ChildProcessDataIO works correctly. + - Added a system/SystemInfo.{cpp,h} support API. These files contain + a GetSystemPath() function (for locating various important directories + on the host system), and a GetOSName() function that returns + the name of the host operating system. + o Adopted Jonas Sundström' simplified Makefile for muscled. + * Updated the dev-c++/muscled.dev file to properly build muscled.exe again. + * The included Python scripts are updated to raise exceptions using + proper Exception objects, rather than old-style bare-string exceptions. + * pythonchat.py now handles exceptions properly. + * SharedMemory regions are now created with full permissions (0777) + instead of just permissions for user and group (0770). + * Worked around a gcc inline-assembly bug in the PowerPC assembly + implementation of GetRunTime64(). (this bug would cause GetRunTime64() + to sometimes return the wrong value if the MUSCLE_POWERPC_TIMEBASE_HZ + compiler constant was defined) + * Added some BeOS-compatibility tweaks to the RS232DataIO class. + (I haven't really tested them to see if they work properly though) + +v2.61 Released 11/04/2004 + - Added a CPULoadMeter class to the util subfolder. The CPULoadMeter + class knows how to monitor the amount of CPU being used on your + system. Handy for doing xload style status displays. Implemented + for OS/X, Windows, and Linux only. + - Added Jeff Koftinoff's Win32FileHandleDataIO class to the winsupport + directory. + - Added a "testtypedefs" test to the test folder. You can run this + program to check that the MUSCLE typedefs (int8, int16, int32, etc) + are correctly defined for your build environment. + - Added SetMaxPoolSize() and GetMaxPoolSize() methods to the + ObjectPool class. + - Added a handy utility named "listener" to the test folder. + This simple program just listens on a specified port and prints + out (in hexadecimal) the bytes it receives from any connecting + TCP client. + - Added PLOCK() and PUNLOCK() macros to Mutex.h, and a new utility + called deadlockfinder.cpp to the test subfolder. Together, these + are useful for tracking down potential synchronization deadlocks in + multithreaded programs. See tests/deadlockfinder.cpp for details. + * Added Peter Vorwerk's Tru64-compatibility patches. + * Added Julien Torres' AMD64-compatibility patches. + * Added an #ifdef to Thread.{cpp,h} so that the Thread class will + compile properly with older (pre 3.2) versions of Qt. + * Tweaked the code to again compile properly under BeOS/R5/PPC. + * muscleFree now checks its argument for NULL-ness when memory + tracking is disabled. + +v2.60 Released 9/25/04 + - Added in the initial implementation of the MiniMessage code. + MiniMessage is a minimalist C-only implementation of the Message + dictionary class, for use with C programs or C++ programs where + simplicity and low resource usage is more important than flexibility + and convenience. See the minimessage subfolder for details. + - Added a Clear() method to the String class, for more efficient + resetting of String objects. + - Added a SetFromString() method to the String class, for more efficient + copying of String objects. Changed the copy constructor and + assignment operator to call this method instead of SetCstr(). + - Added a constructor to the String class that takes a String and + a maximum length in characters. + - Added HashtableIterator constructors that take the Hashtable to + iterate over as an argument, which is more convenient and efficient + than calling table.GetIterator() + - Rewrote the HashtableIterator construction code to be more efficient. + - Added a compiler flag MUSCLE_AVOID_ASSERTIONS. If you specify this + flag in your Makefile, all MASSERT statements will become no-ops. + - MuscleSupport.h is now parseable as a C header file (the C++ portions + of this file will be #ifdef'd out when compiling as C). The non-C++ + inline functions in this file are now declared as 'static inline' + to avoid linker problems when compiling C programs. + o muscled is now compiled with optimizations enabled by default. + To turn optimization off, compile muscled using 'make debug' instead. + * Merged in Monni's patch to make the INT64_FORMAT_SPEC, the + UINT64_FORMAT_SPEC, and the MCRASH_IMPL macros work correctly under + Visual C++ 6.0. + * Calling Destroy() more than once on a Python MessageTransceiverThread + object would cause an exception to be thrown. Fixed. + +v2.52 Released 8/13/04 + - Added a GetInternalQThreadPriority() hook to the Qt implementation + of the Thread class. This lets you specify the Thread priority + of MUSCLE Threads launched from Qt programs, if you wish to. + - Added a GetEmptyString() convenience function to the String class. + This function returns a read-only reference to an empty string. + * The Read() and Write() methods of the FileDescriptorDataIO class + now return the correct values when used in blocking I/O mode. + * Fixed a syntax error in Tuple::Replace() (thanks to Mika Lindqvist + for bringing this to my attention). + * Merged in Mika's patch to Message.cpp that fixes the template code + so that it will compile without errors under gcc 3.4.1. + * Rewrote the MuscleX86SwapInt64(), MuscleX86SwapDouble(), + MusclePowerPCSwapInt64() and MusclePowerPCSwapDouble() inline + assembly functions to work around problems with gcc's optimizer. + * ParseArgs() now properly parses arguments with commas in them. + * Integrated Wilson Yeung's fix for a race condition in the C# code. + +v2.51 Released 6/27/04 + - Wilson Yeung contributed an alpha version of a C# port of the Java + client code (included in the "csharp" subdirectory). Thanks Wilson! + - GetRunTime64() can now use the PowerPC time-base register, + if running on a PowerPC CPU and you have #defined the compiler + constant MUSCLE_POWERPC_TIMEBASE_HZ as the proper update rate. + - Added SetMemoryAllocationStrategy() and GetMemoryAllocationStrategy() + methods to the ByteBuffer class. You can call these to specify an + IMemoryAllocationStrategy object that the ByteBuffer will use to + allocate and free its buffers, instead of calling muscleAlloc() and + muscleFree(). + - The PlainTextMessageIOGateway class now has SetFlushPartialIncomingLines() + and GetFlushPartialIncomingLines() methods, to govern whether "orphan" + incoming text without a carriage return should be passed to the owner + immediately, or whether it should be buffered until the carriage return + is read. + - Added in Monni's QNX compatibility patches. + * Calling SetGateway() or SetDataIO() from within a ClientDisconnected() + callback now does the right thing, instead of just immediately + disconnecting the newly installed gateway (or DataIO) object afterwards. + +v2.50 Released 6/01/04 + - If using gcc on a PowerPC or x86 target, inline assembly will be used + in the B_SWAP_* byte swapping macros, which is more efficient than the + traditional muscleSwapBytes() template function. + * IsRegexToken() now also returns true when passed in any of the + following characters: =^+${}:- + * Removed the kludgy workaround from Mutex.h that simulated a recursive + pthreads Mutex using an int counter, and replaced it with a proper + PTHREAD_MUTEX_RECURSIVE Mutex. Note that the new code requires + a UNIX98 compatible pthreads implementation -- under Linux, you + may need to add -D_GNU_SOURCE to your Makefile in order to use this + code. + +v2.49 Released 5/07/04 + - The "daemon" keyword now takes an optional argument specifying + where the daemon process's stdout and stderr should be redirected + to. If no argument is specified, /dev/null will be used by default. + - Added support in the Thread class for a MUSCLE_USE_CLONE compile flag. + If specified, this flag tells the Thread class to use Linux's clone() + system call to create new threads, instead of the pthreads API. If + this flag is defined, the method Thread::SetCloneParameters() is + available to specify the child thread's launch flags and stack size. + - Added SwapContents() methods to the Message, ByteBuffer, and Ref classes. + o StorageReflectSession methods SetDataNode(), RemoveDataNodes(), and + InsertOrderedData() are now virtual. + o The (createIfNecessary) argument to SpawnDaemonProcess() and + BecomeDaemonProcess() now defaults to true. + o ServerComponent::IsAttachedToServer() is now public. + * ParseArgs() now handles comment # marks inside quotes properly. + * Fixed a couple of Win32 incompatibilities. Thanks to Monni + for reporting these. + * SysLog.cpp wouldn't compile under BeOS/PPC. Fixed. + * QMessageTransceiverThread::event() is now public and not a slot. + * ByteBuffer::SetNumBytes() wasn't updating number-of-valid-bytes + value correctly when called with retainData=true. Fixed. + +v2.48 - Released 3/17/04 + - Added a GetGroupSize() accessor method to the + SharedUsageLimitProxyMemoryAllocator class. + - Added ParsePort() and ParseConnectArg() convenience methods to the + MiscUtilityFunctions.{cpp,h} files. These functions let you + easily parse values out of "port" and "hostname:port" arguments. + - Added an IsCallerInternalThread() method to the Thread class. + This method returns true iff it's being called from the internal + Thread itself, or false otherwise. + - Added GetOwnerSocketSet() and GetInternalSocketSet() methods to + the Thread class. These let you customize the blocking behaviour + of GetNextReplyFromInternalThread() and WaitForNextMessageFromOwner(), + respectively. + - Added a ParseArgs() method to MiscUtilityFunctions.{cpp,h}. + This method knows how to parse multiple arguments from a line of text. + o The Thread class now prefers to use the Windows threading API + over Qt threading when, both are available. + * Connect() now logs an appropriate error message if a hostname + lookup fails and logging is enabled. + * Rewrote LogLineCallback::Log() to be more robust (it now calls + vsnprintf() instead of vsprintf() and does proper bounds checking) + * Fixed a problem with Message.py's parsing of 64-bit Message fields. + Thanks to pyCube for reporting this bug! + * Cleaned up miscellaneous errors in the autodoc header comments. + +v2.47 - Released 2/26/04 + - The muscle code now supports a -DMUSCLE_AVOID_NAMESPACES compile + flag that will cause all namespacing directives to be commented out. + * Fixed an off-by-one error in String::SwapContents() that would cause + memory corruption if the longer of the two strings contained 7 bytes. + * The SetFromArchive() methods of the AndOrQueryFilter and + the NandNotQueryFilter classes were broken. Fixed. + * UDP didn't work in Windows, because sin_family wasn't being set. Fixed. + * Fixed a bug that would cause ambiguities in the semantics of the + PR_RESULT_DATAITEMS Message, if a subscribed database node was updated + and then deleted immediately afterwards. + +v2.46 - Released 1/27/04 + - Added some UDP support: There is now a UDPSocketDataIO class, and the + following new NetworkUtilityFunctions: CreateUDPSocket(), BindUDPSocket(), + SetUDPSocketTarget(), SendDataUDP(), and ReceiveDataUDP(). + - Added some advisory timeslicing support to the PulseNode class. Now + any PulseNode subclass can find out when its time-slice started, by + calling GetCycleStartTime(), set a suggested time-slice-limit by calling + SetSuggestedMaximumTimeSlice(), and check to see if that time limit is up + by calling IsSuggestedTimeSliceExpired(). These methods can be used + to help ensure consistent low latency to all clients. + - Added a MathSetupSystem class (included in the CompleteSetupSystem class). + This class disables BorlandC's floating point exceptions, to avoid + crashing when a floating point exception occurs. + - Added callback function arguments to CloneDataNodeSubtree(), to allow + you to easily customize the Message payload of any nodes that it creates. + - Added String::SwapContents() and Queue::SwapContents() methods for + efficient (O(1)) state swapping between objects of these classes. + - Added a HandleStandardDaemonArgs() function to MiscUtilityFunctions. + This function parses command line arguments that are of general use. + - Added a muscleSgn() convenience template function to MuscleSupport.h. + o DisconnectSession() now returns a bool indicating whether or not + the session decided to terminate itself. + o Moved BecomeDaemonProcess() and SpawnDaemonProcess() from + NetworkUtilityFunctions.{cpp,h} to MiscUtilityFunctions.{cpp,h}. + o Rewrote muscledMain() to parse arguments using ParseArgs() and + HandleStandardDaemonArgs(), rather than by looking at argv directly. + o Rewrote Thread::InternalThreadEntry() to be cleaner. + o Rewrote String::Replace() to be much more efficient. Also, it now returns + the number of substring replacements done, rather than an error code. + * Failed asynchronous-connect-attempts instigated by a call to + AddNewConnectSession() were not being detected under Windows. Fixed. + * Calling AbstractReflectSession::Reconnect() from inside the + AbstractReflectSession::ClientConnectionClosed() method now works properly. + * Calling AddNewSession(ref, int) with an AbstractReflectSession that + already had a DataIO object installed would cause the pre-installed + DataIO object to be deleted and replaced. Now the pre-installed + DataIO object is retained, instead. + * SharedMemory::SetArea() with a zero createSize parameter now works + correctly under Windows. + +v2.45 - Released 11/10/03 + - Added GetNextKeyAndValue() convenience methods to the Hashtable class. + - Added an Atoll() function to MiscUtilityFunctions, to complement + the already included Atoull() function. + - Added a Pad() convenience method to the String class, for easy + padding of a string to a given minimum length. + - Added SetSocketSendBufferSize() and SetSocketReceiveBufferSize() + functions to the NetworkUtilityFunctions function collection. + - Added a ConvertReturnValueToMuscleSemantics() function to MuscleSupport.h, + and modified the various DataIO classes to call this function instead of + each one reimplementing the conversion-logic separately. + - Added QString-style Arg() methods to the String class, so that numeric values + can be inserted into strings in a convenient and relatively safe manner. + o Moved the PreviousOperationWouldBlock() and PreviousOperationWasInterrupted() + functions from NetworkUtilityFunctions.h to MuscleSupport.h, since they + weren't really networking-specific. + o Moved the %llu's and %lli's into MuscleSupport.h constants + INT64_FORMAT_SPEC and UINT64_FORMAT_SPEC, so that they are + defined correctly for different compilers (some expect %Lu instead). + * GetRunTime64() now contains a work-around for a bug in Windows' + QueryPerformanceCounter() function that would cause time to "skip ahead" + under certain motherboard chipsets. See system/SetupSystem.cpp for details. + * Under Windows, GetCurrentTime64() was returning microseconds-since-1601. + Fixed it to return microseconds-since-1970, as documented. + * GetCurrentThreadId() was misspelled in Win32Support.h. Fixed. + * Renamed the SWAP_* macros to B_SWAP_*, for consistency with the + naming convention used in the other macros. + * The FileDescriptorDataIO class now handles EINTR and EWOULDBLOCK + return values appropriately, instead of erroring out. + +v2.44 - Released 09/15/03 + - The QAcceptSocketsThread and QMessageTransceiverThread constructors + now take optional parent and name arguments, which are passed on to the + QObject constructor. + - The QAcceptSocketsThread and QMessageTransceiverThread destructors + now call ShutdownInternalThread(), so you no lnoger have to call it + yourself if you don't want to. + - Added a setNotifyOutputQueueDrainedTag() method to MessageTransceiver.java, + so that the user thread can be notified when the output queue drains. + Thanks to Bryan Varner for this code. + - Added a Replace() method to the Tuple class. + * Some of the SetFromArchive() calls in MultiQueryFilter and its + subclasses didn't mark their argument as const. Fixed. + * Queue::AddTail(const ItemType *, uint32) was broken. Fixed. + * Removed reference to QEvent::MaxUser from the QMessageTransceiverThread + class, so that it can be compiled against Qt 2.3. + * Replaced calls to QThread::postEvent() with QApplication::postEvent(). + +v2.43 - Released 08/19/03 + - Added an optional (count) argument to String::Append() and + String::Prepend(). + - Added a DisconnectSession() method to the AbstractReflectSession class. + Calling this method force-disconnects the session's TCP connection. + - PR_COMMAND_SETDATA now processes multiple sets to the same node in + a single Message. + - Added a (numPreallocSlots) argument to the Queue::EnsureSize() method, + so you can specify how many extra slots to preallocate in the event + of an array reallocation. + - Added SetSignalHandle(), SetSignalValue(), and SetReplyThreadID() + methods to the Win32MessageTransceiverThread class. + * Fixed a bug in Hashtable::Clear() that could cause crashes under + certain circumstances (iterators weren't being unregistered reliably). + * Fixed a bug in the Queue class that was disabling queue-slot-preallocation, + making calls to AddHead() and AddTail() inefficient -- O(N) instead + of amortized O(1). Oops! + * The Win32 implementation of GetHumanReadableTimeString() was returning + date strings 369 years too early. Fixed. + +v2.42 - Released 7/15/03 + o DebugTimer's default minimum-print time is now 1000 (1 millisecond) + o DeflateMessage()'s force parameter now defaults to true. + * Fixed a bug in PathMatcher::MatchesPath() that would cause absolute + paths to not be matched correctly. Thanks to VitViper and Monni for + reporting this bug! + * ReflectServer::GetServerUptime() was broken. Thanks to Monni + for finding this bug! + * The first call to Queue::EnsureSize() no longer does an implicit doubling + of the initial array size, since it's likely that the caller to EnsureSize() + has the best idea of how many slots are needed, and thus shouldn't be + second-guessed. + +v2.41 - Released 6/27/03 + - Added an sdlsupport folder with Shard's SDLMessageTransceiverThread class + in it. This class interfaces MUSCLE to the SDL game library. Thanks Shard! + - Added a PRINT_CALLS_PER_SECOND macro to TimeUtilityFunctions.h + - Added a PreviousOperationWasInterrupted() function to NetworkUtilityFunctions.h + - ZLibCodec::GetInflatedSize() now takes an optional second argument which + can be used to find out if the given compressed buffer is independent or not. + - Added ZLibUtilities.{cpp,h} to the zlib folder. This file contains + InflateMessage() and DeflateMessage() functions that are handy for + compressing Messages down into a flattened state for efficient storage. + - Added a muscleRealloc() function to GlobalMemoryAllocator.h, to go with + the already existing muscleAlloc() and muscleFree() functions. + - The String and ByteBuffer classes now use muscleRealloc() when + appropriate, to avoid unnecessary data copying while resizing buffers. + - Added a minimum-log-time argument to the DebugTimer class so that + printing of very small time intervals can be suppressed. + - Added a GetNumPreallocatedSlots() accessor method to Hashtable. + - Added GetHumanReadableTimeString() and ParseHumanReadableTimeString() + functions to util/MiscUtilityFunctions.h. These functions convert uint64 + time values to ASCII "YYYY/MM/DD HH:MM:SS" strings, and vice versa. + o SetupSystem's constructor is now protected, to make sure nobody declares + a SetupSystem object by itself (they should use CompleteSetupSystem instead) + o Removed the (copyBuffer) argument from the ByteBuffer constructor and + several associated methods, because it was error prone and not + very useful. + * Fixed a crashing bug in Queue.AddHead(const Queue &) + * SharedUsageLimitProxyMemoryAllocator::AllocationFailed() was broken. Fixed. + * String::SetCstr() would store bytes past the NUL terminator of the passed-in + string if (maxLen) was too large. Fixed. + * Made String::Substring() is more efficient, and it now handles + out-of-range values gracefully, instead of throwing an assertion failure. + * The String constructor and SetCstr() took an int32 for (maxLen). + Now they take a uint32 instead. + * Replaced calls to CreateThread() with calls to _beginthreadex() in the + Win32 implementation of the RS232DataIO and Thread APIs. + * The (independent) flag to ZLibCodec::Deflate() wasn't working properly. + Fixed. (The ZLibCodec header format has also changed slightly) + +v2.40 - Released 6/2/03 + - MUSCLE is now licensed under the standard BSD Open Source License. + (Previously it used an equivalent but non-standard license) + - Added the optional zlib compression library to the MUSCLE archive. + (Add -DMUSCLE_ZLIB_ENCODING_ENABLED to your Makefile to enable it) + - Added 9 levels of ZLib encodings to the MessageIOGateway class + (MUSCLE_MESSAGE_ENCODING_ZLIB_1, ..., MUSCLE_MESSAGE_ENCODING_ZLIB_9) + - Added QueryFilter objects (regex/QueryFilter.{cpp,h}) to allow queries + to be restricted to include only nodes whose data Messages match + user-specified criteria. + - Rewrote the PathMatcher class and API to support QueryFilter objects. + - Added a PR_NAME_FILTERS keyword that can be added to commands (along + with PR_NAME_KEYS) to restrict results based on a QueryFilter. + - Added a QueryFilterRef parameter to the SendMessageToMatchingSessions(), + FindMatchingSessions(), and RemoveDataNodes() methods of the + StorageReflectSession class. + - Rewrote the MessageIOGateway implementation and protected API to + use ByteBuffers, in order to better support ZLib encoding. + - Added a PR_NAME_REPLY_ENCODING parameter to StorageReflectConstants. + This parameter can be used to tell muscled to compress Messages that + it sends back to your client. + - Added a SetOutgoingMessageEncoding() method to the + MessageTransceiverThread class. + - Added a GetRunTime64() function to TimeUtilityFunctions.h. This + function is similar to GetCurrentTime64(), except it is not synced + to the real-time clock, and thus is guaranteed not to jump around + (e.g. when the user sets the system clock, or during leap-seconds) + GetRunTime64() is now used instead of GetCurrentTime64() where appropriate. + - Added a Reset() method to all the AbstractMessageIOGateway classes, + so that they can be re-used after a disconnect or parse error. + - Merged in Bryan Varner's enhancements to the Java muscle code. + These include a new Preferences wrapper class, and non-exception + throwing get*() methods in Message class. Thanks, Bryan! + - Calls to the installed LogCallbacks are now checked so that + re-entrant calls (read: infinite recursion) cannot occur. + - StorageReflectSession::SendMessageToSessions() and + StorageReflectSession::FindMatchingSessions() now take + a (matchSelf) boolean argument. + - Optimized the Hashtable class to be a bit more efficient. + - Added an AddPathFromString() convenience method to the PathMatcher class. + - Added a muscleVoidPointer convenience typedef to MuscleSupport.h + - Added IsReadyForInput() and HasBytesToOutput() methods to the + AbstractReflectSession class; ReflectServer now calls these, + and the default implementations pass the calls through to the gateway. + - Added a GetOrPut() convenience method to the Hashtable class. + (handy for demand-allocated Hashtable entries) + - ByteBuffers no longer re-allocate their memory buffer when + they are resized smaller. + - Added DebugTimer.{cpp,h} to the utils subfolder. This class + is useful for quick-and-dirty profiling of code execution times. + - Added a GetTimeSliceElapsedTime() method to the ServerComponent + class, so that sessions and factories can see how much time + they have taken so far in their current cycle iteration. + - Added an EnsureSize() method to the Hashtable class, to support + pre-allocating tables (for efficiency). + - Added item-array implementations of AddHead() and AddTail() + to the Queue class. + - Added optional startIndex and numItems arguments to the Queue-arg + implementations of AddHead() and AddTail() in the Queue class. + - Updated the Beginner's Guide HTML to document QueryFilters, + ZLib encoding parameters, and a few other details that had + been left out of it. + o Moved the ByteBufferPool functions from Message.{cpp,h} to + ByteBuffer.{cpp,h}. + o Demoted certain log messages from MUSCLE_LOG_INFO priority + to MUSCLE_LOG_DEBUG priority. + o Adjusted the DECLARE_MUSCLE_TRAVERSAL_CALLBACK macro to make + it more reusable in StorageReflectSession subclasses. + o The ByteBuffer class now refers to its data as an array of uint8's, + rather than using void pointers. + * RateLimitIOSessionPolicy would divide by zero if you + specified a rate limit of zero bytes per second. Fixed. + * RS232DataIO::GetAvailableSerialPortNames() now works correctly + under MacOS/X. + * Fixed a couple of VC++-specific code problems. + * Fixed a bug that was causing Messages sent to a + MessageTransceiverThread to be sent back to the user thread. + * StorageReflectSession::FindMatchingSessions() was broken. Fixed. + +v2.31 - Released 4/14/03 + - Rewrote the Hashtable class so that it no longer needs to + allocate HashtableEntries dynamically during hash clashes. + - Added in a remapping layer to the HashtableEntries so that + Hashtable operations can be efficient even when the Hashtable + is full or nearly full. Removed the (loadFactor) argument to + the Hashtable constructor. + - Lowered the Hashtable's default (initialCapacity) to 7. + - Added some new optional parameters to StorageReflectSession's + SetDataNode() and CloneDataNodeSubtree() methods to allow for + more control over how nodes get placed into their parent's index. + - Added muscleAlloc() and muscleFree() calls to the + GlobalMemoryAllocator.h API, for those who like C-style + memory management. + - Added GetArrayPointer() methods to the Queue class. This + enables efficient bulk-manipulation of items in the Queue. + - Added an optional node-name argument to DataNode::InsertOrderedChild(). + - Added an optional "releaseDataBuffers" argument to the Clear() + methods of Queue, Hashtable, and Message classes, to force + the immediate release of internally held memory buffers. + - Added GetNumAllocatedItemSlots() method to the Queue class. + - Added a muscleRintf() function to MuscleSupport.h + - Added a Win32MessageTransceiverThread class to the winsupport + folder. This subclass interfaces the MessageTransceiverThread + class to the Win32 event API. + - Added support for MUSCLE_DISABLE_DEBUGGING compile-time + flag to turn all Log(), LogTime(), etc calls into no-ops. + - Added a GetGlobalMuscleLock() function that returns a process-wide + Mutex for simple serialization of Log operations, etc. + - Added an argument for enabling Nagle's algorithm to + CreateConnectedSocketPair(). Defaults to false/disabled. + - Added a GetPosition() method to the DataIO interface and + all subclasses thereof. + * Fixed a bug in RestoreNodeTreeFromMessage() that would + sometimes cause indexed nodes not to be restored. + * Fixed a bug in Queue::EnsureSize(x, true) where sometimes it + _itemCount wouldn't be properly set after a successful call. + * Some of the byte-order-swap macros weren't handling complex + arguments correctly. Added extra parentheses to fix them. + * Added support for a -DMUSCLE_AVOID_NEWNOTHROW flag that will + tell all MUSCLE code to use new instead of new (nothrow). + * Trim() will no longer strip UTF8 chars from the end of a String. + * Fixed a bug in the RateLimitSessionIOPolicy that would cause + rate-limited transfers to occasionally stall. Thanks to + Garjala for all his help in tracking this problem down! + * Sessions that are about to be destroyed now have DoOutput() + called on them, in case they have any last-second data to send. + * Muscle now includes instead of to avoid gcc warnings. + * MessageIOGateway::DoInputImplementation() would sometimes hold + on to a large memory buffer instead of freeing it immediately. Fixed. + * muscleAbs() was returning int instead of the templated type. Fixed. + +v2.30 - Released 3/03/03 + - Added a SharedMemory class to the system folder. This class + acts as a platform-neutral wrapper API for the host OS's + cross-process-shared-memory-area facilitities. + - Rewrote the MemoryAllocator API to be more flexible. + - ReflectServer's constructor now takes a MemoryAllocator + pointer instead of a UsageLimitProxyMemoryAllocator pointer. + - Added a SharedUsageLimitProxyMemoryAllocator class that + uses a small shared memory area to allow multiple daemon + processes to share a single aggregate memory-usage cap. + - Added a Reconnect() method to the AbstractReflectSession + class. This lets a session that was added with + AddNewConnectSession() restore his connection if it breaks. + - Added a GetSessionDescriptionString() method to the + AbstractReflectSession class, for convenience. + - Added GetAsyncConnectIP() and GetAsyncConnectPort() + methods to AbstractReflectSession, for convenience. + - Added a FindMatchingSessions() method to the + StorageReflectSession class, for convenience. + - GetPathDepth() now handles paths without a leading slash correctly. + - Added SetSocketNaglesAlgorithmEnabled(), ShutdownSocket(), + SendData(), ReceiveData(), and PreviousOperationWouldBlock() + to the NetworkUtilities function collection. + - Added IsBlockingIOEnabled() and IsNaglesAlgorithmEnabled() + accessor methods to the TCPSocketDataIO class. + - Added the AbstractGatewayMessageReceiver interface, and the + QueueGatewayMessageReceiver convenience class. + - Rewrote ReflectServer::ServerProcessLoop() to be slightly + more efficient and fair. + - Added a (void *) misc-utility argument to the + MessageReceivedFromGateway() method, and made it part of + the AbstractGatewayMessageReceiver interface. + - Added testnagle.cpp and testresponse.cpp to the test folder. + o Inet_NtoA() no longer returns a String object, since that + requires the String class to be linked in to programs that + could otherwise live without it. + o Rewrote the AbstractMessageIOGateway API to be more efficient. + Gateways no longer maintain a Queue of incoming Messages; rather, + whenever they parse a Message they immediately pass it to a + specified AbstractGatewayMessageReceiver object. + o AbstractMessageIOGateway subclasses no longer override DoInput() + and DoOutput() directly. Instead, they should implement/override + DoInputImplementation() and DoOutputImplementation(). + o Moved the StringMatcher and StringMatcherQueue singleton + ObjectPools out of the StorageReflectSession class and into + the StringMatcher.cpp and PathMatcher.cpp files, respectively. + They can now be accessed anywhere, via the functions + GetStringMatcherPool() and GetStringMatcherQueuePool(). + o PathMatcher no longer accepts pointers to ObjectPools in its + AddPathString() methods -- instead it just always uses + the appropriate singleton ObjectPools. + o Removed the type_code data type; now uint32 is used instead. + o Removed the PulseNode::Tick() method hook, since it turned + out to be unnecessary and encourages bad programming habits. + * ReflectServer's status output is now more informative. + * The ThreadSetupSystem and NetworkSetupSystem classes now + handle multiple nested instantiations gracefully. + * TCPSocketDataIO's Read() and Write() calls would return an + error code for zero-length reads or writes. Fixed. + * PathMatcher::MatchesPath() wasn't giving correct results + for paths with leading slashes. Fixed. + * TCPSocketDataIO::FlushOutput() wasn't working in OS/X. Fixed. + +v2.25 - Released 2/04/03 + - Renamed Hashtable::Get() to GetValue() (Get() is preserved + as a synonym for compatibility with existing code) + - Added two Hashtable::GetKey() methods to allow quick lookup + of held key objects. + - Added a PrintStackTrace() function to the SysLog module. + MCRASH() and MASSERT() now call PrintStackTrace() so you + get more debug info when you crash. (Only works under gcc) + - Added a (setNumItems) argument to Queue::EnsureSize(), which + can be handy if you want to add or remove a large number of + default data items at once. + - Updated MessageTransceiver.java with Bryan Varner's changes. + - Hashtable::Sort() now uses Simon Tatham's very efficient + merge-sort algorithm instead of insertion sort. + - Added ToString() and AddToString() methods to the Message class. + - Added a SetCstr() method to the String class. + * binary String operators now return String instead of const String. + * StorageReflectSession::CloneDataNodeSubtree() was not + cloning node indices. Fixed. + * DataNode::InsertIndexEntryAt() now uses GetKey() so as not + to have to iterate over the node's child list. + +v2.24 - Released 11/15/02 + - Added an "oldData" argument to StorageReflectSession's + NotifySubscribersThatNodeChanged() and NodeChanged() methods, + and removed the NodeCreated() and NotifySubscribersOfNewNode() + methods (since they are now redundant). + - Added a "backwards" flag argument to Message::GetFieldNameIterator(), + and added a Message::GetFieldNameIteratorAt() method. + - Added a GetBlankMessage() convenience method to the + StorageReflectSession class. This method returns a reference to + an empty Message. + - Optimized the tree search traversal algorithm so that non-wildcard + patterns do a single hash lookup instead of matching against every + child node. + - Added GetPattern() and IsPatternUnique() methods to the + StringMatcher class. + - StringMatcher::SetPattern and the StringMatcher constructor + now take a (const String &) instead of a (const char *). + - Merged in Bryan's enhancements the the Java classes. The + Jave MUSCLE API now includes better support in the + MessageTransceiver class for listening for incoming TCP + connections, and a way to interrupt() the current Thread + running in a MessageQueue. + - String::Replace() now returns a status_t to indicate whether + any replacements were actually made or not. + * QMessageTransceiverThread was using an illegal event code to + signal the master thread of new network events. Fixed. + +v2.23 - Released 11/01/02 + - Added RS232DataIO.{cpp,h} to allow for simple serial port I/O. + Currently it only works under Windows and Linux; support for + other OS's may be added in the future, if there is need for it. + - Exposed the AddPyObjectToMessage() function in the Python + argument-conversion-utilities package. + * Cleaned up the various comparison operators to return bool + instead of int, and the equality-adjustment operators to + return (Class &) instead of (const Class &) + * Tweaked the code to compile cleanly under gcc 3.x. + +v2.22 - Released 10/16/02 + - Added PR_COMMAND_SETDATATREE, PR_COMMAND_GETDATATREE, + and PR_JETTISON_SUBTREES commands, and a PR_RESULT_DATATREE + reply. These can be used to conveniently get and set + database subtrees. + - MessageGateways now free their scratch buffers if they + are bigger than 10 kilobytes, so as not to waste memory after + having sent or received a very large Message. + * Integrated Monni the Cat's VC++6 compatibility tweaks. + * Fixed a horrendously embarrassing memory leak in muscled. + In fact, ALL of muscled's heap memory allocations were being + leaked, as of v2.18. Sorry guys, that was a really dumb + mistake. :^( + +v2.21 - Released 10/08/02 + - Added a muscleAbs() utility template to MuscleSupport.h + - Queue::RemoveItemAt() and Queue::InsertItemAt() have been + optimized a bit -- they should be about twice as fast now. + - Added a GetLogLevelKeyword() function to SysLog.h + - StringMatcher::SetPattern() now looks for an optional tilde + character ('~') at the beginning of simple patterns; if it + finds it, it sets a negate flag so that Match() will return + the logical opposite of what it usually would return. + - Added manual SetNegate() and IsNegate() methods to the + StringMatcher class. + - IsRegexToken() now takes a second argument specifying whether + the character in question is the first in the string (since + there are several characters that are only 'special' when they + are the leading character) + - Database node paths are no longer cached, to save memory. + Instead, GetNodePath() now generates the node-path on the fly, + as needed. Because of this, the function signatures of + GetNodePath() and GetNodeName() have changed. + - Added a handy DataNode::GetPathClause() method that allows + quick access to the node name at a specified depth in + a node's path. + * Queue::GetItemPointer() is now named GetItemAt(). (The old + name is still available, but deprecated). + * The PowerPC assembly implementation of atomic increment and + decrement wasn't being used under OS/X. Fixed. + +v2.20 - Released 9/05/02 + - Added a () convenience operator to the MessageFieldNameIterator + class. It operates as a synonym for GetNextFieldNameString(). + - Added PythonUtilityFunctions.{cpp,h}, which currently includes + two functions, ConvertMessageItemToPyObject(). and + ParsePythonArgs(). These are useful when embedding Python + and MUSCLE C++ code into the same application. + - Added ParseArg() and ParseFile() methods to MiscUtilityFunctions + .cpp and .h files. These parse an argument (ParseArgs()) style + out of a single string, or a FILE pointer, respectively. + - Added a GetScheduledPulseTime() to the PulseNode class. This + method returns the time at which Pulse() will next be called. + - Added a muscleCompare template that returns -1 if the first arg + is greater than the second, or 0 if they are equal, or else 1. + - Added an Atoull() function to turn an ASCII string into a uint64. + (useful since atoll() and atoull() aren't standard functions) + - Added a SendMessageToMatchingSessions() convenience method to + the StorageReflectSession class. + - Added a dev-c++ subfolder that contains project files suitable + for compiling muscled under Windows' Dev/C++ IDE. (Thanks + to Marcin "Shard" Konicki for supplying these!) + - Added inline x86 assembly atomic-increment and atomic-decrement + code to AtomicCounter.h. + - Added a "remap" argument to muscled, and corresponding accessor + methods to ReflectServer, to support explicit remapping of IP + addresses (e.g. so you can tell muscled to advertise local LAN + machines by their world-accessible IP address) + * AtomicCounter.h no longer relies on the (non-portable) + asm/atomic.h API when compiled under Linux. + * ParseArgs() now parses argv[0] as well as the others. + * BeOS/net_server bone-check in FinalizeAsyncConnection() now uses + be_roster->IsRunning() if possible for more accurate results on + systems where BONE has been installed and then disabled. + * Fixed a bug that would cause an assertion failure if you called + ReverseItemOrdering() on an empty Queue. + * StringMatcher's wildcard-to-regex code was overly complicated + and broken, especially when using the included regex parser (i.e. + under Windows). Rewritten to be simpler and work right. + * When set to a numeric-range pattern like "<0-50>", StringMatcher + now only considers matches on strings that start with a digit. + +v2.19 - Released 7/22/02 + - Added a GetPathClauseString() method to PathMatcher.{cpp.h}. + This version returns a String object containing just the one + clause you specified. + * GetPathClause() was not returning NULL on failure. Fixed. + * When presented with an unknown hostname, MessageTransceiverThread + would sometimes try to connect to IP address 0.0.0.0 instead of + failing cleanly. Fixed. + * The NodeCreated() callback would see the newly created node with + an empty Message payload. Now it sees the node complete with the + Message it was created to have. + * The Win32 implementation of Thread::StartInternalThreadAux() wasn't + handling the return value of CreateThread(), which in turn caused + Thread::ShutdownInternalThread() to not work properly. Fixed. + (Thanks to VitViper for detecting this bug!) + * The BeOS/net_server implementation of FinalizeAsyncConnection() + would cause net_server to half-close the connection after 60 + seconds if no send or receive activity had occurred by then. Fixed. + +v2.18 - Released 7/4/02 + - Removed checks for DISABLE_MUSCLE_MEMORY_TRACKING, and put in + checks for MUSCLE_ENABLE_MEMORY_TRACKING instead. In other words, + memory tracking is now disabled unless the Makefile says otherwise. + The muscled Makefile contains -DMUSCLE_ENABLE_MEMORY_TRACKING, so + memory tracking is enabled by default for muscled. + - Added two GetNumInstancesOf() methods to the String class; these + are handy for counting how many instances of a substring are + present in a String. + - DefaultConsoleLogger now calls fflush() after every call to printf(). + - Added a Reverse() method to the String class. + - Added a muscleInRange() function template to MuscleSupport.h + This function tests whether a given value is within a given range. + * The 'maxmem' argument is no longer parsed if muscled was compiled + without -DMUSCLE_ENABLE_MEMORY_TRACKING. + * The '==' operator of the Message class had a bug in which + Messages would sometimes be wrongly reported as equivalent + when they weren't. Fixed. + * Message class now holds its fields using a table of GenericRefs + instead of AbstractDataArrayRefs (to avoid errors under compilers + that don't like creating templates for declared-but-undefined classes) + * The MemoryAllocator class hierarchy has been rewritten so that it + no longer allocates or frees memory directly; rather it is used only + to monitor and/or veto memory requests called in by the new and delete + operators. This makes it work better with the constructors and + destructors of static objects, and fixes a crash-on-exit bug in + muscled. + * Folded in Charlie Buckheit's SGI compatibility changes into the + muscled Makefile, made the Makefile simpler, and added comments. + +v2.17 - Released 6/14/02 + - Message class now uses ObjectPools to cache extra AbstractDataArray + objects, to avoid having to dynamically allocate them most of the + time. You can define MUSCLE_DISABLE_MESSAGE_FIELD_POOLS to suppress + this behaviour, if you need to. + - AtomicCounter now uses inline assembly to implement atomic operations + on the PowerPC processor with gcc, and the asm/atomic.h functions when + under Linux/Intel. + - Added '==' and '!=' operators to the Message class. + - Modified Head() and Tail() in the Queue class to return read-only + or read-write references to the head or tail items, instead of + doing return-by-value. + - Added PeekNextFieldNameString() and GetNextFieldNameString() methods + to the MessageFieldNameIterator class. These return a (const String *) + instead of a (const char *), which is sometimes more efficient. + * MessageFieldNameIterator::PeekNextFieldNameString() was broken. Fixed. + * AtomicCounter::AtomicIncrement() and RefCountable::IncrementRefCount() + now return void instead of bool (Linux doesn't support returning bool) + +v2.16 - Released 6/5/02 + - Added SetNewInputPolicy(), SetNewOutputPolicy(), and + RemoveSessions() methods to the MessageTransceiverThread class. + These methods allow for some on-the-fly control of existing + ThreadWorkerSessions. + - Added comparison operators (less than, greater than, etc) + to the Tuple class template. These do a lexical comparison + using the comparison operators of each sub-element, in sequence. + - syslog/SysLog.h now looks for a MUSCLE_MINIMALIST_LOGGING + preprocessor constant. If found, Log(), LogTime(), and LogFlush() + will be defined as simple in-line pass-throughs to printf(). + This allows small programs to use MUSCLE classes without having + to link in all the stuff that the regular logging facility + requires. + - Added GetKeyAt() and IndexOfKey() methods to the Hashtable class. + - Added two Substring() convenience methods to the String class, + that take a String as an argument. + - Added a GetNumInstancesOf() method to the Tuple class. + * To ensure logical correctness, all methods in the Hashtable class + that used to return (Key *) now return (const Key *). + * Folded in VitViper's Visual C++ compatibility fixes. + +v2.15 - Released 5/17/02 + - The Hashtable class now uses a HashFunctor template argument + to calculate hash codes, instead of a function callback. This + allows for more efficient code, but will break some source + code (sorry!). To fix the souce, you typically just need to + remove the function pointer argument to the Hashtable ctor. + - Broke out the log-line-label-generation code in SysLog.cpp + into its own separate function, GetStandardLogLinePreamble(), + so that it can be accessed independently by other code. + - Changed the Hashtable class's performance parameters to + capacity=11, loadFactor=75%. This should save some memory. + - Added an AfterMessageReceivedFromGateway() callback method + to the AbstractReflectSession class. PushSubscriptionMessages() + is now called by this method, rather than having to be + called manually by the subclasses. + - Added a CallMessageReceivedFromGateway() method to the + AbstractReflectSession class. This method calls both + MessageReceivedFromGateway() and AfterMessageReceivedFromGateway(), + and should be used to 'fake' message reception (instead of + calling the aforementioned methods directly). + - Optimized the PushSubscriptions() method with a dirty-flag. + - Added the muscleCopyIn() and muscleCopyOut() templates to + MuscleSupport.h. These support alignment-safe value copying. + - Added a GetEmptyMessage() utility function to Message.{cpp,h} + - ObjectPool now derives from AbstractObjectManager, which in + turn derives from AbstractObjectGenerator and AbstractObjectRecycler. + This helps me use pools polymorphically. + * The ServerComponent detach/delete sequence is now better defined, + avoiding possible errors that could occur in custom servers. + * A little more OS/X compatibility tweaking. + * Some of the Flatten/Unflatten code would crash on processors + (e.g. MIPS) that required values to be read on written on + word-aligned boundaries. Fixed. (Thanks to Charlie Buckheit + for reporting this bug) + * Fixed a race condition in the Thread class that would cause + the thread's owner to not be signalled correctly in some cases. + +v2.14 - Released 5/2/02 + * The Python Message implementation now flattens/unflattens + B_INT64_TYPE fields properly even under old (pre 2.2) versions + of Python. + * Bryan Varner found and fixed a bug in the Java Message class; + it wasn't handling the sending and receiving of UTF8 strings + correctly. Thanks Bryan! + * Removed the B_POINTER_TYPE from the Java Message implementation, + since Java doesn't have pointers, and flattening a pointer into + a Message that goes across the network is a silly thing to do + anyway. + * Changed all instances of #include to + #include , since Muscle does not require any + WinSock2-specific features. + * The included fall-back regex library is now Henry Spencer's + public domain implementation, instead of the GPL'd GNU version. + * The Windows version of Thread::StartInternalThread() was broken. + Thanks to VitViper for finding and fixing this bug! + +v2.13 - Released 4/26/02 + - Added Python support, with Message and MessageTransceiverThread + classes, and pythonchat, a BeShare-compatible command-line + demo chat app. Check out the README.TXT file in the python + subfolder for details. + - Added a readmessage.cpp utility to the test folder. + - Added |= and &= operators to the Rect class. + - StorageReflectSession methods NodeChanged(), NodeIndexChanged(), + and NodeCreated() are now declared as virtual. + * String::Trim() would crash with an assertion failure on some + systems, due to incorrect handling of unsigned values. Thanks + to Austin M. Brower (a.k.a. bobman) for catching this bug! + * Tweaked the Makefiles and the SetupSystem.cpp file so that they + compile properly under OS/X. (thanks again to Austin for this) + * Fixed some problems that kept muscled from compiling under VC++. + * Added a couple of checks to Message::Unflatten() to keep it from + crashing if given a mangled flattened-Message to parse. + +v2.12 - Released 4/4/02 + - Added a SetAboutToFlattenCallback() method to the MessageIOGateway + class. This allows you to install a hook that gets called when + a Message is about to be flattened, in which you can modify or + inspect the Message. + - gcc now compiles muscled with the -fno-exceptions flag, resulting + in somewhat more efficient code since muscle doesn't use exceptions + anyway. + - Added a muscleClamp() utility function to MuscleSupport.h. + - Added << and >> operators to the Tuple declaration macros. + - Removed the default/blank member items from the Tuple, Queue, + and Hashtable classes, as I finally found a way to declare them + properly on the stack instead. This should save some memory. + * Snooze64() and GetCurrentTime64() were broken under Windows. Fixed. + * Added a MUSCLE_MAX_OUTPUT_CHUNK #ifdef into ReflectServer.cpp, so + that people with finicky routers can limit their send-chunk sizes + by putting a -D flag into their Makefile if necessary. + o Removed TCPSocketDataIO::PollSocketReadiness(), since it is never + used, encourages polling, and causes warnings under C++Builder5.5. + +v2.11 - Released 2/28/02 + - Added RemoveDataNodes() and DoRemoveData() convenience methods + to the StorageReflectSession API. + - Changed the Thread class to have separate + SetOkayToCloseInternalThreadWakeupSocket() and + SetOkayToCloseOwnerWakeupSocket() methods, instead of passing + in a "release" flag with the Get*WakeupSocket() calls. Also changed + the Thread class to keep the socket values even when they are + considered "released", so that signalling will still work if needed. + * ThreadSupervisorSession::MessageReceivedFromOwner()'s argument list + was missing a parameter, and thus it wasn't getting called. Fixed. + +v2.10 - Released 2/7/02 + - MUSCLE now supports compilation under Win32 using Borland's + C++Builder 5.5 compiler. (use the Makefile in the 'borland' folder) + - StringMatcher can now (as a special case) do simple integer range + queries of the form "<15-252>", which would match strings. + "15", "16", ..., "252". See StringMatcher::SetPattern() for details. + - Added RemoveFirstInstanceOf() and RemoveLastInstanceOf() methods + to the Queue class. + - Added ByteBuffer and FlatCountable classes to the util folder. + - Added AddFlat(FlatCountableRef), FindFlat(FlatCountableRef), and + friends to the Message class. These allow you to add and retrieve + FlatCountable objects to/from a Message by reference, + so that they aren't actually flattened until the Message itself is. + - Rewrote the Message class to use ByteBufferRefs instead of raw + byte arrays for its flattened objects and raw data buffers. + Note that this changes the semantics of these fields slightly-- + copying a Message object will no longer copy the contents of these + fields, but rather the two Messages will now both reference the + same data. + - Pointer fields in Messages are no longer flattened, as the mechanics + of doing so are non-portable (and silly). + - All methods in the Message class now take String references instead + of (const char *)'s for field names. + - Removed all but one of the Message::GetInfo() methods, since they + were redundant and/or inconsistent with the rest of the Message API. + - Moved the "okayToAdd" parameter of the Replace*() methods in the + Message class to the front of the argument list. (It was causing + some nasty problems involving type promotion at its previous position) + - class ObjectPool now derives from an interface class named + AbstractObjectRecycler. Also, the Ref class now holds a pointer to + an AbstractObjectRecycler, rather than to an ObjectPool. This allows + more flexibility when keeping heterogeneous sets of objects referenced. + - Added optional CopyTo() and CopyFrom() methods to the Flattenable class. + These methods support copying one Flattenable object to another in the + most efficient way possible. Per-class optimizations can be implemented + by overriding the CopyToImplementation() and CopyFromImplementation() + virtual methods as desired.. + * Default implementation of Flattenable::AllowsTypeCode() now accepts + B_RAW_TYPE as well as TypeCode(), to allow unflattening from untyped data. + * Removed the (char) constructor from the String class, as it was + interfering with proper type checking of arguments. + * Changed all index parameters in the Message class to use uint32 + instead of int32 (since negative indices don't make sense). + * MessageTransceiverThread wasn't adding new sessions asynchronously + (i.e. after StartInternalThread() had been called). Fixed. + * MessageTransceiverThread would not deliver a + MTT_EVENT_SESSION_DISCONNECTED message to the user code if an + asynchronous connection to localhost failed. Fixed. + * FinalizeAsyncConnect() was using the wrong test and thus sometimes + thought an asynchronous connection had succeeded when it had in + fact failed. Fixed. + * ConnectAsync() was broken under Windows. Fixed. + +v2.00 - Released 1/19/02 + - All MUSCLE code is now declared as being in the 'muscle' namespace. + This, plus all the changes listed below, pretty much guarantee that + any source code that compilable with previous versions of MUSCLE won't + compile now, and vice versa; sorry about that. Hopefully everything + that needed changing has been changed by now though, so it shouldn't + happen again (well, not soon anyway ;^)). If you need help updating + your old MUSCLE-using code, let me know. + - muscled now parses a 'maxmessagesize' argument, allowing you to limit the + maximum size of incoming Message objects (so e.g. you don't have to worry + about some joker sending you 100 megabyte Messages, tying up all your RAM) + - muscled now parses 'maxsessions' and 'maxsessionsperhost' keywords to allow + limiting of the number of simultaneous connections, or simultaneous + connections from any given client IP address. + - muscled now accepts a 'localhost' keyword which lets you specify what + the advertised IP address of clients connecting from localhost should be, + instead of (the rather useless) 127.0.0.1. + - muscle now accepts the keywords "maxsendrate", "maxreceiverate", and + "maxcombinedrate" -- these allow you to specify maximum allowed + bandwidth usage (in KB/sec) for sending, receiving, or both. + - muscled now accepts a 'require' keyword on the command line as well + as 'ban', allowing for 'whitelisting' as well as 'blacklisting'. + - admin now parses 'require' and 'unrequire' keywords on the command line. + - Removed the word 'Portable' from all class names that had it; since + muscle is now in its own namespace, a unique prefix is no longer necessary. + - Completely rewrote the MessageTransceiverThread class. Now the + MessageTransceiverThread class is portable code, with optional thin + OS-specific wrapper subclasses available if you wish to use them. + Moreover, MessageTransceiverThread now holds its own ReflectServer + object, which means that it uses the same event loop as the servers + do, and that a single MessageTransceiverThread object can manage multiple + incoming and outgoing connections simultaneously. + BMessageTransceiverThread, QMessageTransceiverThread, and + AMessageTransceiverThread are the wrappers for BeOS, Qt, and AtheOS, + respectively. (MessageTransceiverThread can also be used by itself + if you prefer to stick with threads-and-TCP only) + - Split the accept-tcp-sockets-thread functionality into its own + class, AcceptSocketsThread. There is also a Qt-specific subclass of + of this available, named QAcceptSocketsThread. + - Added a Tuple templated class to the support folder. This class + represents an array of numeric values, and is used to do simple + vector math operations conveniently. + - Point and Rect (nee PortablePoint and PortableRect) are now derived from + Tuple, to avoid code duplication. The formerly public member variables + x, y, left, top, right, and bottom values are now exposed as + inline methods instead (e.g. use x() instead of x). + - Added aggregate bandwidth metering/management capabilities to MUSCLE. + This includes the introduction of a new interface, AbstractSessionIOPolicy, + and a bandwidth-limiter subclass, RateLimitSessionIOPolicy. + - Added a SetMaxIncomingMessageSize() method to the MessageIOGateway class. + - Added a FilterSessionFactory decorator class, that can be used + in conjunction with any other ReflectSessionFactory object to enforce + ban-lists, require-lists, and/or connection limits. + - ReflectSessionFactory and AbstractReflectSession now have a common base + class, named ServerComponent, that provides them with limited access to + their ReflectServer object. + - Added SetLocalHostIPOverride() and GetLocalHostIPOverride() functions + to NetworkUtilityFunctions.h, to set a custom IP address to use instead + of 127.0.0.1 when necessary. + - Added -= and - operators to the String class. (you saw it here first!) + - Added a String constructor and assignment operator for char arg. + - Renamed ObjectPool::GetObject() to ObtainObject() to avoid a naming + conflict with a #define in the MS-Windoze headers. (evil!) + - ObjectPool class is now thread-safe by default (unless you specify + -DMUSCLE_SINGLE_THREAD_ONLY in your build line) + - Added a new subfolder named "system", which contains code for exporting + OS-specified functionality in a platform-independent manner. This folder + currently holds a Mutex class and a Thread class, both of which + wrap the relevant portions of several supported native APIs: + BeOS, AtheOS, pthreads, Qt, and Win32. It also holds the + new MessageTransceiverThread and AcceptSocketsThread classes, since + these are special types of Thread, and the GlobalMemoryAllocator. + - Moved the memory-usage-tracking code out of ReflectServer.cpp and into + its own file, system/GlobalMemoryAllocator.{cpp,h}. Now you can choose + whether or not to link this in to your server. + - Added util/MemoryAllocator.{cpp,h}. These files contain a set of + memory-allocation-handler classes, which can be composed together to + create a custom memory allocation strategy. Included subclasses include + BasicMemoryAllocator, ProxyMemoryAllocator, UsageLimitMemoryAllocator, + and AutoCleanupMemoryAllocator. + - Merged PortableTypeConstants.h, PortableTypeDefs.h, PortableByteOrder.h, + PortableMacros.h, and PortableStatusConstants.h into a single file + called MuscleSupport.h + - Renamed the BeOS and AtheOS conversion routines to ConvertToBMessage(), + ConvertFromBMessage(), ConvertToAMessage(), and ConvertFromAMessage(). + - Added a RawDataMessageIOGateway class to the iogateway folder. This + class is handy for reading or writing chunks of unformatted bytes + to/from a socket. + - AbstractMessageIOGateway::DoInput() and DoOutput() now return the number + of bytes that they read/wrote (respectively), or -1 if there was an error. + (Before they returned a status_t). They now take a uint32 argument + instead of a (uint32 &). The argument now indicates the maximum number + of bytes they are allowed to read or write before returning. + - Added an InsertOrderedData() method into StorageReflectSession so + that subclasses can get more info about what gets inserted where. + - Added new methods to the Message class: AddTag(), PrependTag(), + FindTag(), and ReplaceTag(). These let you assign non-persistent, + non-flattenable tag objects to a message that will be automatically + dereferenced when the Message goes away. + - ReflectSessionFactory classes are now in charge of deciding who + gets to connect and who doesn't, rather than the StorageReflectSession + class. This is useful because it means you can specify different + access policies for different ports. + - Removed AbstractReflectSession::GetClientPort() and replaced + it with the (slightly more useful) GetPort() method, which returns + the session's port number on the server side, instead. + - Added AbstractReflectSession::SendMessageToFactory(), and + ReflectSessionFactory::MessageReceivedFromSession(), so that + sessions can send instructions to the factories that created them. + - ReflectSessionFactory::CreateSession() now takes the IP string + of the remote peer, to allow for filtering on IP addresses. + - CreateAcceptingSocket() now takes optional arguments to return the + selected port, and to specify an IP address to only accept from. + - Added ConnectAsync(), FinalizeAsyncConnect(), GetHostByName(), + CloseSocket(), Accept(), and Inet_AtoN() functions into the + NetworkUtilityFunctions toolkit. Also added a version of Connect() that + takes a numeric IP address rather than an ASCII string. + - INet_NtoA() now returns a String instead of writing into a char buffer. + - Added support for sessions that asynchronously connect outwards from + the server: ReflectServer::AddNewConnectSession(), + AbstractReflectSession::AddNewConnectSession(), and + the callback AbstractReflectSession::AsyncConnectCompleted(). + - Added SaveNodeTreeToMessage() and RestoreNodeTreeFromMessage() + methods to StorageReflectSession. These allow easy recursive + archiving and restoration of database node subtrees and their indices. + - Made a bunch of formerly protected members of StorageReflectSession + private, and added protected accessors for some of them, where necessary. + - Added sort-by-value capability to the Hashtable class, and rewrote the + auto-sort interface so that there are separate SetKeyCompareFunction(), + SetValueCompareFunction(), and SetCompareCookie() methods, as well as + associated accessor methods. + - Added Message::GetNumValuesInName(). + - GetMessageFromPool() now returns a MessageRef instead of a Message *, + to avoid any possibility of a memory leak. + - Added templated convenience functions muscleSwap(), muscleMin(), and + muscleMax() to MuscleSupport.h. + - Added a Shutdown() method to the AbstractMessageIOGateway interface, + and made AbstractMessageIOGateway::GetDataIO() protected. + - Added a GetSelectSocket() method to the DataIO interface. This method + returns the socket fd to select() on for that DataIO (if any). + - Removed AbstractMessageIOGateway::SetEnabled() and IsEnabled(), and + replaced them with the single virtual method IsReadyForInput(), which + function similarly to HasBytesForOutput(). + - Consolidated the server-side Pulse logic into a single base class, + PulseNode. ReflectServers, AbstractMessageIOGateways, + ReflectSessionFactories, and AbstractReflectSessions now all inherit from + PulseNode, and thus are all capable of scheduling and receiving Pulse() + calls for themselves. + Also renamed ReschedulePulseTime() to InvalidatePulseTime(). + - AbstractDataIOGateway objects no longer take a DataIO pointer in their + constructor arguments; instead, a separate SetDataIO(DataIORef) call + is used to install the DataIO into the gateway. + - Renamed the constant MUSCLE_NEVER_PULSE to MUSCLE_TIME_NEVER (since + it is used in situations other than just Pulse() now). + - Added the constant MUSCLE_NO_LIMIT, which is defined as ((uint32)-1). + - Separated the DataIO creation from the CreateGateway() callback, + making CreateGateway() implementations a bit simpler. Also added + SetGateway() and GetGateway() methods so you can define a custom + gateway for your session "in advance" if you want to. + - Added SetRef(), SetFromGeneric(), and GetGeneric(), conveniences methods + to the Ref class, as well as a Ref constructor that takes a GenericRef. + - Renamed AddLogCallback() to PutLogCallback(), and change the arguments + to take a LogCallbackRef instead of just a pointer. + - Moved the log callback stuff into its own header file, syslog/LogCallback.h + - Modified several APIs that were taking a pointer and an "ownIt" boolean + to take Refs instead. That way the deletion can be handled automagically. + - Removed the InitializeTCPStack() method from NetworkUtilityFunctions and + added instead some SetupSystem classes, including a ThreadSetupSystem, + a NetworkSetupSystem, and a CompleteSetupSystem. Placing one of these + (usually a CompleteSetupSystem) on the stack at the beginning of main() + is now the correct way to handle all environment setup and tear-down. + - Added MiscUtilityFunctions.{cpp,h} files to the util subfolder. These + contain utility functions that don't fit in anywhere else--currently + they contain a ParseArgs() function that parses command line arguments + into a Message object. + - Added the SignalMessageIOGateway class -- a very simple gateway used + primarily as a convenience class for thread synchronization. + - Renamed MessageReceivedFromNeighbor() to MessageReceivedFromSession(), + and renamed BroadcastMessageToNeighbors() to BroadcastMessageToSessions(). + - Added MessageReceivedFromFactory() and BroadcastMessageToFactory() + methods to the ServerComponent class and its subclasses. + - Added util/SocketHolder.h, to facilitate leak-proof socket file descriptor + transfers via Message objects. + - merged testreflectclient.cpp and testatheosclient.cpp into a single program, + and got rid of testplaintextclient.cpp + * Renamed Queue::CountItems() to GetNumItems(), and renamed Hashtable::Size() + to GetNumItems(), for clarity and consistency. + * Renamed all methods named MakeEmpty() to Clear(), for consistency. + * functions in the SysLog API are now serialized in order to be thread-safe. + * Removed the UnintrusiveRef class, as it isn't useful enough to keep around. + * Fixed a bug in the PlainTextMessageIOGateway that would sometimes + allow garbage characters into the imported text. + * All TCP port number arguments are now specified as uint16s instead of ints. + * All usages of size_t and ssize_t have been replaced with + uint32 and int32, respectively (since that is more explicit/precise). + * Removed the MUSCLE_CLASS_DECLARATIONS and MUSCLE_FUNCTION_DECLARATIONS + tokens from the headers, since they didn't have a well-defined meaning. + * Certain MUSCLE methods were using a non-standard convention of returning + a boolean value to indicate success or failure. Modified all of the + following methods to return a status_t (B_NO_ERROR or B_ERROR) instead: + HashtableIterator::GetNextKey() HashtableIterator::PeekNextKey() + HashtableIterator::GetNextValue() HashtableIterator::PeekNextValue() + Hashtable::Get Hashtable::Put() Hashtable::Remove Hashtable::RemoveAux() + Hashtable::GrowTable() Hashtable::MoveToFront() Hashtable::MoveToBack() + Hashtable::MoveToBefore() Hashtable::MoveToBehind() Hashtable::Sort() + Queue::AddTail() Queue::AddHead() Queue::RemoveTail() Queue::RemoveHead() + Queue::RemoveItemAt() Queue::GetItemAt() Queue::ReplaceItemAt() + Queue::InsertItemAt() Queue::EnsureSize() + AbstractMessageIOGateway::EnsureBufferSize() + Message::GetNextFieldName() Message::PeekNextFieldName() + StorageReflectSession::DataNode::PutChild() + StorageReflectSession::DataNode::InsertOrderedChild() + StorageReflectSession::DataNode::ReorderChild() + StorageReflectSession::DataNode::RemoveIndexEntry() + StringMatcher::SetPattern() Flattenable::ReadData() + +v1.93 - Released 12/05/01 + - Added an optional second argument to PortableHashtable::GetIterator(), + that lets you traverse the hashtable's contents backwards. + - Added PortableHashtable::GetIteratorAt(), to allow iterations + that start from any item in the table's traversal list. + - Changed the compare function type for hash tables to return an int + (strcmp() style) rather than a bool (== style), so that it can be used + for sorting as well as comparison. Also added a (void *) cookie parameter + so that the callback can access user-defined context information easily. + - Changed PortableHashtable::SetCompareFunction() to take a second parameter + specifying whether or not the compare function should be used to auto-sort + entries as they are placed into the hash table. + - Added Sort() and Sort(compareFunc) methods to PortableHashtable to allow + in-place traversal order sorting. + - Added the PR_COMMAND_REORDERDATA message type, to let clients reorder their + indices without having to delete and reupload the associated data nodes. + +v1.92 - Released 11/23/01 + - PortableHashtables now retain the ordering of the items you Put() into them. + (And since PortableMessage uses a PortableHashtable internally, this means + also that PortableMessage now retains the ordering of the data fields you + add to it) + - Added MoveToFront(), MoveToBack(), MoveToBefore(), and MoveToBehind() + methods to the PortableHashtable class. These methods allow the user + to modify the iteration traversal order. + - It is now possible to modify a PortableHashtable in the middle of + traversing its contents with a PortableHashtableIterator, without causing + an assertion failure or even messing up the traversal(!). + - Renamed PortableHashtable::Contains() to ContainsValue(), to avoid + confusion. + - Rewrote PortableHashtableFieldNameIterator to be simpler and more efficient. + - Rewrote ReflectServer to use only a PortableHashtable to hold + AbstractReflectSessionRefs, instead of both a PortableHashtable and a + PortableQueue. + - Renamed ReflectServer::GetSessionsList() and + AbstractReflectSession::GetSessionsList() to GetSessions(), which now + returns a hashtable iterator instead of a reference to a PortableQueue. + - Renamed ReflectServer::GetSessionByID() and + AbstractReflectSession::GetSessionByID() to GetSession(), which now + returns an AbstractReflectSessionRef instead of a status_t. + - Added a +(char) operator to PortableString class. + - Made the non-session-specific server API access methods in + AbstractReflectSession protected, instead of public. + - Added a SwapContents() method to the PortableHashtable class. + * Fixed the GrowTable() method -- the user compare function wasn't + being retained. + +v1.91 - Released 11/01/01 + - Added atomic inc/decs for Linux and Windows into PortableRefCount.h + - The admin tool now waits for a response from the server, and informs you + if your admin commands failed due to a privilege violation. + - Changed GetPeerInfo() to try and find out what the local host's "real" + IP address is if necessary, instead of returning 127.0.0.1. + - moved the signal(SIGHUP, SIG_IGN) code into InitializeTCPStack(). + * Integrated Vitaliy's patches to get QMessageTransceiverThread to work + under Windows. + +v1.90 - Released 10/26/01 + - Changed the PortableRefCount class to require that the items it + reference-counts are subclasses of PortableRefCountable. This has the + advantage of removing all the RefCountMem and RefCountMemPool dependencies; + unfortunately it also breaks most of the MUSCLE-using code out there + (slightly). The old PortableRefCount implementation is now called + UnintrusivePortableRefCount, and is left in for use in cases where the + reference-counted class cannot be modified to derive from + PortableRefCountable. + - Added a StorageReflectSession::CloneDataNodeSubtree() convenience method. + - Added a CreateAcceptingSocket() function to NetworkUtilityFunctions. This + function creates and returns a socket that is listening for incoming TCP + connections on the given port. + - Added a Flush() callback method to the LogCallback class, and a LogFlush() + global function, so that SysLog output can be fflush()'d in a generic + manner. + - Added support for defining a custom threadsafe incrementor function in + PortableRefCount.h + - Added #define PR_NAME_SESSION "session" into StorageReflectConstants. + Now when muscled sees a string with this field name in a client-to-client + PortableMessage, it will replace the string value with the sending client's + session ID. This allows receiving clients to be sure that the client who + sent them the message is the client indicated in the server field of the + message (i.e. it disallows "spoofing" of other clients). + - Added an example flattened-PortableMessage byte listing to the end of + PortableMessageIOGateway.h + - Added AddHead() and AddTail() methods to PortableQueue that take + other PortableQueues, rather than individual items. + - QMessageTransceiverThread::PortableMessageReceived()'s second argument + is now a uint32 instead of an int. + - Added the InitializeTCPStack() function to NetworkUtilityFunctions.h + (Starts up the TCP stack; only needed under Windows) + - Added handy factory functions for the PortableMessageIOGateway and + PlainTextMessageIOGateway classes. + - Added the LogLineCallback convenience subclass to SysLog.{cpp,h} + - Added Vitaliy's vc++ subfolder with VC++ project files. + * More windows compatibility tweaks; muscled now compiles and runs under + Windows XP with VC++! (many thanks to Vitaliy Mikitchenko aka VitViper!) + * StorageReflectSession::NodePathMatcher::DoTraversal() was not giving + correct results for traversals that used multiple query strings and + didn't start from the global database root. Fixed. + +v1.84 - Released 10/15/01 + - Added a SpawnDaemonProcess() function to NetworkUtilityFunctions.h + - Added a Snooze64() function to NetworkUtilityFunctions.h + - Added ReleaseFile() and GetFile() methods to FileDataIO.h + - Added a PR_NAME_SET_QUIETLY tag so you can upload nodes to + the database without causing subscribers to be notified. + - Added a PR_NAME_REMOVE_QUIETLY tag so you can remove nodes from + the database without causing subscribers to be notified. + - Added Jonathon Padfield's QSocketDataIO class to the qtsupport + folder (for single-threaded Qt programs). Thanks, Jonathan! + - Added a MCHECKPOINT macro to PortableMacros.h for use in debugging. + - Added a RemoveAllInstancesOf(const ItemType &) method to the + PortableQueue class. + - Added == and != operators to the PortableQueue class. + - testgateway.cpp now takes optional filename arguments. Any + filenames given to it will be read as flattened PortableMessages + and their contents printed to stdout. + - Added a OnceEvery(uint64, uint64 &) function to TimeUtilityFunctions.h + - PortableMessage::PrintToStream() now has optional arguments to make + it function recursively with varying levels of indentation. + - Added a new class regex/PathMatcher.{cpp,h} to the repertoire. + This class can be used to do efficient pattern matching against + node-path strings. Moved the functions in + reflector/StorageReflectUtils.{cpp,h} into these files instead. + - Previously subscribing to a string you were already subscribed to + was a no-op. Now it is equivalent to a PR_COMMAND_GETDATA using + that string. + - PortableQueue::Sort() now takes an optional void pointer that will + be passed through to the item-compare callback function. + * Jonathon Padfield contributed a patch to PortableByteOrder.h to + make it compile under FreeBSD. Thanks again J :^) + * PortableRef class now has a proper copy-constructor. + * Changed B_ERROR and B_NO_ERROR declarations from an enumeration + to #defines, to avoid compiler warnings. + * Fixed a bug in QMessageTransceiverThread that would cause + TCP connections to be broken as soon as they were connected. + * TCPSocketDataIO::Shutdown() now calls shutdown() before close(). + (Linux wasn't being properly punctual with just close()) + * PortableString::Unflatten() now checks to make sure the + flattened string is terminated, and errors out if it isn't. + * PortableHashtable::Put()'s default value for the last argument + was false instead of NULL. Fixed. + * Made several methods in StorageReflectSession inline and const tagged. + +v1.83 - Released 9/12/01 + - Added a qtsupport folder, containing QMessageTransceiverThread + and QThreadSafeObjectPool classes for use with the Qt API. + - Added more of David Rene's Visual C++ compatibility changes in. + - Renamed GetCurrentTime() to GetCurrentTime64() to avoid a + conflict with the like-named Windows function, and added + David's Win32 implementation of the method. + - Added MUSCLE_CLASS_DECLARATIONS and MUSCLE_FUNCTION_DECLARATIONS + tokens into the header files where appropriate. These tokens + are #defined to nothing by default, but can be overridden by the + compile environment (-D flag) if necessary. + - Added an "okayToAdd" argument to all the Replace*() methods + in the PortableMessage class; if set to true, then attempting + to replace a non-existing item will cause the new item + to be added to the message instead of returning B_NO_ERROR. + If set false, the old behaviour (fail if old item not present) + is used instead. + - Added a Reset() method to the MessageTransceiverThread classes. + You can call Reset() on a MessageTransceiverThread object to + re-initialize its state (comparable to deleting the + MessageTransceiverThread object and creating a new one) + - Added GetOutputStallLimit() methods to the AbstractMessageIOGateway, + PortableDataIO, and TCPSocketDataIO classes. Now output stall + detection is only done on TCP connections, and can be controlled + per gateway or per data io type. + - Changed the ReflectServer to take a ReflectSessionFactory object + in PutAcceptPort() instead of a callback function. Converted all session + creation callback functions into ReflectSessionFactory subclasses. + - Added an overloaded () operator to the StringTokenizer class, as + a convenience. It works as a synonym for GetNextToken(). + +v1.82 - Released 8/21/01 + - During a memory shortage, muscled now kicks users whose output + message queues have grown too large. This keeps someone with + a bad network connection and an ambitious subscription request + from tying up all the memory on the server indefinitely. + - Changed the signature of AbstractMessageIOGateway::DoInput() and + AbstractMessageIOGateway::DoOutput() to DoInput(uint32 & addReadBytes) + and DoOutput(uint32 & addWroteBytes). DoInput() and DoOutput() + should add to this argument the number of bytes they read or + wrote during the call, respectively. This helps the calling + code know when I/O is stalled. + - muscled now keeps track of how long an output has been "stalled" + (i.e. it has bytes queued to send to the client but none have + actually been sent) and will drop any clients that have been + stalled for more than 20 minutes. + +v1.81 - Released 7/16/01 + - Added Alan's stream operators ("<<") to the PortableString class. + * Made AtheOS refcounts use atomic_add(), for thread safety. + * Merged in David Rene's header tweaks to help MUSCLE code compile + cleanly under Visual C++. + * Tweaked time.h includes to compile properly under Debian Linux. + * Messages being returned to the global message pool now have + their 'what' code set to zero. + * Fixed a potential source of problems during out-of-memory + conditions in the PortableMessageIOGateway class. + +v1.80 - Released 6/26/01 + - Added an atheossupport directory, which is a AtheOS port of + the BeOS code in the besupport directory. + - Added testatheosclient.cpp and testatheossupport.cpp testing + stubs to the test folder. + * Consolidated the netutil, string, stringtokenizer, hashtable, + refcount, pool, and queue subfolders into a single subfolder + named 'util'. All code that uses MUSCLE will probably need + to have its #include lines modified to reflect this, before + it will compile again. :^( (this change was necessary in + order to avoid #include space conflicts with the STL headers) + +v1.72 - Released 6/22/01 + - Added a "maxnodespersession" parameter to muscled, which lets + you specify the maximum number of nodes any given session may + have in its server-side database at one time. + - Added a uint32 PR_NAME_MAX_NODES_PER_SESSION read-only parameter + to the parameter set returned by PR_COMMAND_GET_PARAMETERS. + This returns the value mentioned above. + - Made some of the members of the DataNode class demand-allocated, + so as to use a little less memory. + - Tweaked the code to compile without errors under the CygWin environment. + (thanks to Joshua Schmiedlin for his help with this) + - Added a Sort() method to the PortableQueue class. This method + uses a nice in-place merge-sort algorithm that I liberated from + Thomas Baudel's sorting visualizer applet at + http://www-ihm.lri.fr/~thomas/VisuTri/ + - Added Swap() and ReverseItemOrdering() methods to PortableQueue. + These let you swap two entries or a invert the ordering of a + whole range of entries, respectively. + - ReflectServer now preallocates 256 slots for the lame-duck session + list, so that adding a session to the lest is less likely to fail + during a memory pinch. + - Added a SetNotificationMessage() method to MessageTransceiverThread + class (in case the regular old PORTABLE_MESSAGES_RECEIVED message + isn't good enough for you) + +v1.71 - Released 6/11/01 + - Renamed ReflectServer::AddAcceptPort() to PutAcceptPort(). + Changed ReflectServer to allocate accept-sockets inside the + PutAcceptPort() call instead of at the beginning of the + ServerEventLoop(). Also added a RemoveAcceptPort() method to + ReflectServer, and added PutAcceptPort() and RemoveAcceptPort() + pass-through methods to AbstractReflectSession. + - Added a BroadcastToAllNeighbors() convenience method to + the AbstractReflectSession class. + - Added a Seek() method to the PortableDataIO interface, and + Seek() implementations to the various subclasses thereof. + Moved FileDescriptorDataIO's implementation into a .cpp file + so that it will link properly under Linux. + * Added some missing constants to StorageReflectSession.java + +v1.70 - Released 5/20/01 + - Changed the Makefiles to reflect the changed, final location + of the BONE headers (/boot/develop/headers/be/bone) + - Changed the Put() methods of PortableHashtable to return + true on success, false on error (memory allocation failure). + NOTE: This changes the Put()'s return value semantics! + If you code was relying on them, you will need to modify it. + - Changed ReflectServer::ServerProcessLoop() to take no arguments, + and added a method ReflectServer::AddAcceptPort(func, port) instead. + This change allows a single server to accept connections on multiple + ports if it wants to, and create different AbstractReflectSession objects + based on the port the TCP connection was received on. As a consequence + of this, you can now have muscled listen on multiple ports if you like; + e.g. muscled port=2960 port=4000 port=9999 + - Added a GetServerUptime() method to the ReflectServer and + AbstractReflectSession classes, and a PR_NAME_SERVER_UPTIME + field to the message returned by PR_COMMAND_GET_PARAMETERS. + The value returned is muscled's uptime, in microseconds. + * Changed the AddLogCallback() and AddOutOfMemoryHandler() functions + to return B_ERROR on out-of-memory. + * Changed the muscled code to handle Put() failing a little better. + This should fix some of the memory leaks that occur when the server + has hit its memory cap. + +v1.64 - Released 5/07/01 + - Added dataio/MemoryBufferDataIO.h. This class lets you use + an in-memory array as a limited input or output device. + - Added a new first parameter to BecomeDaemonProcess(). This + first param lets you specify the directory that the child + process should chdir() to. Leave as NULL to not change directory at all. + - Added a GetReadByteTimeStamp() method to the PortableDataIO + interface. (used to support high-precision time-stamping in LCS-specific + subclasses. Not implemented by any of the included MUSCLE classes, though) + - Added a PR_COMMAND_BATCH message type. When the server receives + this message, it will parse the submessages in the PR_NAME_KEYS + field and execute them in order as if they had arrived separately. + - Added a PR_COMMAND_NOOP message types. When the server receives + this message it is guaranteed to do nothing at all with it. + - In the StorageReflectSession class, changed NotifySubscribersOfNewNode(), + NotifySubscribersThatNodeChanged(), and + NotifySubscribersThatNodeIndexChanged() to be virtual so that they can be + overridden and used as hooks in a subclass. + - Added a CreateConnectedSocketPair() function to the + NetworkUtilityFunctions.h package. This function is useful + for thread coordination. + - Added a SetSocketBlockingEnabled() function to NetworkUtilityFunctions.h + - muscled now compiles and runs under AtheOS, thanks to a + patch submitted by Jonas Sundstrom. + * The docs were referring to the muscled's pattern matching + feature as 'regular expressions'. This was inaccurate, as + muscled doesn't parse regular expressions so much as wildcard + expressions, a.k.a. globbing. Changed the docs to reflect this. + * PortableString::LastIndexOf(char) was broken. Fixed. + * Boolean PortableMessage fields were broken on architectures + where sizeof(bool) was not equal to one. Fixed. + +v1.63 - Released 3/25/01 + - iogateway/PlainTextMessageIOGateway.cpp now works. While not + used by muscled, this class can be used by other programs for + easy communication with servers that speak ASCII text (e.g. + web servers, XML server, e-commerce servers, etc). See + PlainTextMessageIOGateway.h for details. + - Added test/plaintextclient.cpp and test/portableplaintext.cpp. + These are command line communications test programs, similar + to testreflectlient.cpp and portablereflectclient.cpp, respectively, + but using plain text streams rather than flattened PortableMessages. + - Added a Shutdown() method to the PortableDataIO classes. This + method can be used to immediately close the underlying socket/file + descriptor/whatever, without having to delete the PortableDataIO object. + - Made AbstractMessageIOGateway::GetDataIO() public. + - BecomeDaemonProcess() now takes two optional arguments: the + first indicates where to redirect stderr and stdout to. Default + value is "/dev/null". The second indicates whether or not to + try and create a file if it can't be opened; default is false. + * When the MessageTransceiverThread gets an I/O error, it now closes + its connection immediately (by calling GetDataIO()->Shutdown()) + rather than waiting for the user to do it. + * The muscled ServerProcessLoop() and MessageTransceiverThread now + both suppress SIGPIPE signals, as these can be raised when sending + to a remotely-closed socket, killing the thread. + +v1.62 - Released 3/2/01 + - Added a PR_NAME_SERVER_VERSION parameter to the parameter set + returned by the server. This field contains a string to indicate + the version of MUSCLE the muscled server was compiled from. + (Currently the string is "1.62") + * Several parameter name constants were missing from + StorageReflectConstants.java. Added. + * The clever byte-swapping macros put in for MacOS/X were screwing + up the byte-ordering for Linux, so I removed them. + * PortableMessage.java had a call to Vector.add() in it. Changed it to + Vector.addElement(). + * Made the _empty Hashtable in PortableMessage.java demand allocated, + otherwise IE would barf on it. + +v1.61 - Released 2/16/01 + - Changed the Pulse()/GetPulseTime() callbacks to use uint64s instead of + timeval structs--uint64's are much easier to use than timevals. + - Added a BecomeDaemonProcess() function to NetworkUtilityFunctions.h + This function calls fork() and all the other incantations necessary to + convert the current process into a canonical Posix daemon task. + - muscled now recognizes the parameter 'daemon'. If specified, muscled + will detach from the shell and run as a background/daemon task. + * muscled now parses keywords even if they have no '=' sign/value specified. + * Changed the SysLog implementation to use separate callbacks for console + and file logging. Before it was using the same callback for both, but + certain versions of Linux crash if I try to re-use a va_list object. + +v1.60 - Released 2/1/01 + - Added Java client support to MUSCLE! (See README-JAVA.txt) + - Changed header comments to JavaDoc style throughout. + - Added extra commenting to a lot of previously uncommented header code. + Now all public and protected methods and member items are documented. + - The muscle/html/autodoc/genDocs.sh script now calls doxygen instead + of PERCEPS. Doxygen is a very nice autodocumenting program, and a + BeOS port can be found on BeBits. + - Added a muscle.dox config file to the muscle/html/autodoc directory. + - Added a () operator to the PortableString class so you can say + myString(), instead of myString.Cstr(), if you want. + - PortableString methods StartsWith, EndsWith(), EndsWithIgnoreCase(), + and StartsWithIgnoreCase() now return bool instead of int. + - The PR_RESULT_PARAMETERS messages returned by the server now include + uint64 PR_NAME_SERVER_MEM_AVAILABLE, PR_NAME_SERVER_MEM_USED, and + PR_NAME_SERVER_MEM_MAX fields that indicates how much + memory the server has available to be allocated by all MUSCLE clients. + - Added a FileDescriptorDataIO class to the dataio directory. This + dataio supports sessions that want to do read() and write() on a + file descriptor for their I/O. + * AddFlat(), PrependFlat(), ReplaceFlat(), and FindFlat() will + now call their Point, String, or Message counterparts if they + are passed a PortablePoint, PortableString, or PortableMessage object. + * Flattened objects added to a PortableMessage are now filed using + the field type returned by their TypeCode() member, rather than + always as B_OBJECT_TYPE. This will break compatibility with old + clients that expected B_OBJECT_TYPE flattened objects; sorry about that. + * Added in some extra tweaks so that MUSCLE will compile under MacOS/X. + * A bunch of member variables of the AbstractMessageIOGateway class + were protected when they should have been private. Changed them + to private and added protected accessor methods where necessary. + * HTML-ized the README.TXT file (now named README.html) + +v1.51 - Released 1/3/01 + - Added PR_COMMAND_KICK, PR_COMMAND_ADDBANS, and PR_COMMAND_REMOVEBANS + commands. Messages of these types instruct the muscle server to + disconnect other clients, forbid IP addresses from connecting to + the server, and remove previously added bans, respectively. + These messages will only be honored if sent by a privileged + client; otherwise a PR_RESULT_ERRORACCESSDENIED message is returned. + - muscled now takes "privban=", "privunban=", + "privkick=", and "privall=" arguments, to + specify which clients are allowed to do kick, ban, or unban ops. + (if not specified, no clients will be privileged) + - Added an 'admin' command-line tool to the muscle/server + directory that lets you manage your server's ban lists + and/or kick users without having to restart the server. + Run 'admin help' for args. + - Made ReflectServer::GetCentralState() public. + * Removed the ClientConnected() and SetClientConnected() + callbacks in favor of a more elegant StorageReflectSession + oriented privileges system. The new system allows the server + operator to specify which clients are allowed to kick, ban, + and unban other clients. + +v1.50 - Released 12/29/00 + - Loosened up the flattened-PortableMessage protocol specification + a little bit. It is now legal to add fields with arbitrary typecodes + to a PortableMessage, and PortableMessages with fields that have + arbitrary typecodes may be Unflatten()'d without causing an error. + This will allow the addition and use of new datatypes without breaking + backwards compatibility, thus avoiding a repeat of the problems that + were caused by adding the B_MIME_TYPE support. + - support/PortableMacros.h now contains a MUSCLE_VERSION_STRING + #define, indicating the current version of MUSCLE. muscled + will print this version string out on startup if you set + the display threshold to DEBUG or lower. + * Tweaked StorageReflectSession.{cpp,h} to get it to compile under + Red Hat Linux 7.0. + +v1.45 - Released 12/22/00 + - Added a GetPeerInfo() function into NetworkUtilityFunctions.{cpp,h}. + This function returns the remote host IP and port of a connected socket. + - Added a ClientConnected() virtual method and + SetClientConnectedCallbackFunc() method to ReflectServer. Both of these + mechanisms allow connecting clients to be checked before allowing them + access to the server. + - muscled can now accept zero or more ban= arguments on the command + line. Each ban argument specifies an IP address (or wildcard pattern of + IP addresses) to ban from the server. + - Added EndServer() methods to the ReflectServer and AbstractReflectSession + classes. These allow you to cause muscled to exit in a controlled manner. + - You can now disable the memory-usage-tracking overloads of new and + delete by defining the preprocessor symbol DISABLE_MUSCLE_MEMORY_TRACKING + for ReflectServer.cpp. + - muscled's Makefile now supports both optimized and unoptimized builds + of muscled. Do "make" for an unoptimized build, or "make optimized" + for an optimized build. (Be sure to "make clean" first when switching + from one to the other!) + * Fixed a couple of memory leaks in the PortableHashtable class. + * Rewrote PortableHashtable::GrowTable() to handle out-of-memory + conditions correctly. + * Removed the WATCH_MUSCLE_MEMORY_USAGE macros, since the overloaded + new and delete operators do the same thing in a more elegant way. + * Updated the docs slightly to reflect the fact that level one + server-database nodes are named after the clients' IP addresses, + not their hostnames. + * Fixed an uninitialized-pointer problem that could make muscled + crash while denying a login. + +v1.44 - Released 12/21/00 + - Modified PortableHashtable to maintain a linked list of + PortableHashtableEntry objects. This should speed up traversals + somewhat, as the PortableHashtableIterator no longer needs to + iterate over the empty portions of the table. + - Merged in Trey Boudreau's changes to support adding fields of + type B_MIME_TYPE to a PortableMessage. + - Added IndexOfIgnoreCase() and LastIndexOfIgnoreCase() methods + to PortableString that take chars instead of (const char *)'s + as their second argument. + +v1.43 - Released 12/01/00 + - Added a "Custom Servers.html" file to muscle/html. This file contains + information on making your own custom MUSCLE server. + - Changed ObjectPool to reuse items in LIFO order rather than FIFO. + This may increase cache coherency in some cases. + - Makefile now compiles muscled with -O3 and -fomit-frame-pointer options + under gcc, for efficiency. + - Added SetMessageFlattenCallback() and SetMessageUnflattenCallback() + methods to the PortableMessageIOGateway class (for when you can't be + bothered to do proper subclassing ;^)) + - Added ParseLogLevelToken() function to SysLog.h, and added 'display' + and 'log' arguments to muscled, so that you can specify how what sort + of output you want on the command line. + (e.g. "./muscled display=debug log=none") + - Added more *IgnoreCase() methods to PortableString, and changed + PortableString::Equals() to return a bool instead of an int. + - Pulled the timeval utility functions out of NetworkUtilityFunctions.{cpp,h} + and into their own file, netutil/TimeUtilityFunctions.h + Also declared them all inline, so you don't need to link anything + extra in to use them. + - Moved references to into PortableTypeDefs.h, and added + a #include there so that including this header can be avoided. + (This is to support certain embedded architectures which don't + seem have this file...) + - Added a operator() overload to the PortableRefCount(), so you can + now specify myRef() instead of myRef.GetItemPointer(). + * Renamed the LOG_* constants in SysLog.h to MUSCLE_LOG_*, to avoid + conflicts with the constants in Be's support/syslog.h header. (This + may break some existing code, sorry about that) + * Disabled the memory-allocation code under BeOS/PowerPC environments, + as it was causing mysterious crashes (possible compiler bug?). This + means that if you are running muscled on a PowerPC BeOS machine, the + memory allocation limits will be ignored. + +v1.42 - Released 10/18/00 + - Added a new function, AddOutOfMemoryHandler((void *)()). This lets you + install your own handler that will be called in the event of a memory + allocation failure. + - Added a new command code, PR_COMMAND_PING. When muscled receives a message + of this type from its client, changes the message's 'what' code to + PR_RESULT_PONG, and sends the message right back to the client. + - Added a GetRemainderOfString() method to the StringTokenizer class. + * Moved the StorageReflectSession::DrainPools() call out of ReflectServer.cpp, + and into a callback that is installed by muscled.cpp. This allows custom + servers to be compiled without having to link in StorageReflectSession.o. + * The memory-tracking new and delete operators now count their own + 4-byte-per-allocation overhead in the allocated-memory total. + +v1.41 - Released 10/8/00 + - Added a FindDataPointer() method to the PortableMessage class. + This method returns a writable pointer to a field's data + array, for efficiency. + - PortableMessage::AddData() now allows the (data) parameter to + be NULL. If (data) is NULL, default-constructed objects or + uninitialized data bytes will be added to the PortableMessage. + - PortableMessage::FindData() and FindDataPointer() now allow + the (numBytes) parameter to be NULL. + - muscled now parses command line arguments of the form + keyword=value. Currently supported keywords are 'port', + 'help', and 'maxmem'. + - muscled now detects out-of-memory situations and tries to free up + some memory by draining the object pools and/or forcibly + disconnecting the active session when an out-of-memory error occurs. + - muscled now lets you specify the maximum number of bytes of + data it should allow itself to have allocated at any one time. + For example, entering "muscled maxmem=15" will cause muscled + to not help itself to more than 15 megabytes of memory. + * Went through the code and made it more resistant to crashing + in out-of-memory situations. (i.e. it should correctly handle + NULL being returned by new (nothrow) in all cases) + * Fixed a bug that would cause muscled to crash if it was + sent a malformed PortableMessage. Thanks to Ben and Pete + for helping me find this bug! + * Now compiles cleanly with full warnings enabled, thanks + to Christopher Tate. + +v1.40 - Released 9/8/00 + - Added ordered-child-indexing to StorageReflectSession. Support + for this includes the new PR_COMMAND_INSERTORDEREDDATA code, + the PR_RESULT_INDEXUPDATED result code, and the INDEX_OP_* + enums. (see comments in StorageReflectConstants.h and the new + chapter at the end of the Beginner's Guide for details) + - Added Prepend*() methods to the PortableMessage class. + - PortableQueue and PortableString can now handle very small + strings/lists without having to do any dynamic memory allocation. + - Added Head(), Tail(), HeadPointer(), TailPointer(), and + InsertItemAt() to PortableQueue. + +v1.31 - Released 7/30/00 + - Added custom-callback capability to the SysLog module. + (AddLogCallback(), RemoveLogCallback()) + - Added the OnceEvery() function to NetworkUtilityFunctions. + * Fixed an uninitialized timeval struct in AbstractMessageIOGateway-- + this was causing muscled to panic and exit sometimes under FreeBSD. + (Thanks to Peter Schultz for helping track down this bug!) + +v1.30 - Released 6/30/00 + - Added optional object-allocation tracking into the code. + This tracking is useful for hunting down memory leaks; to enable it + uncomment the -DMUSCLE_WATCH_MEM_USAGE line in the Makefile and + recompile the server (from scratch). With this flag enabled, + the server will print out memory-allocation stats every so many + seconds. + - Added some PR_COMMAND_RESERVED and PR_RESULT_RESERVED constants + to StorageReflectConstants.h to allow future expansion. + - Added a PR_RESULT_ERRORUNIMPLEMENTED code. This message is + sent back to the client when the client sends a PR_COMMAND_RESERVED + code that the serverd doesn't understand. + - Added a PR_COMMAND_JETTISONRESULTS command. When the server receives + a message of this type, it will match the paths given in PR_NAME_KEYS + against the items in its list of outgoing PR_RESULTS_DATAITEMS messages, + and any matched items will be deleted and not sent. If no PR_NAME_KEYS + field is found in the PR_COMMAND_JETTISONRESULTS message, then all + pending PR_RESULTS_DATAITEMS messages will be dumped. (This is useful + when a client is receiving a large result set and decides in the middle + of the download that he's no longer interested in it) + - muscled no longer does a hostname lookup when a client logs in. + This speeds up muscled's response time (and frees it from depending + on a working nameserver) but it means that nodes in level one + of the database tree will look like '127.0.0.1' instead of + 'mycomputer.mydomain.com'. + - Added ClientConnectionClosed() callback to the AbstractReflectSession + class, so that a session can decide whether it wants to stick around + after its TCP connection is gone. + - Replaced the AbstractReflectSession class's GetIncomingMessageQueue() + and GetOutgoingMessageQueue() methods with the more flexible + GetGateway() and SetGateway() methods. + - Added some utility functions to StringMatcher.{cpp,h}: + IsRegexToken(), HasRegexTokens(), and MakeRegexCaseInsensitive(). + - Now compiles under FreeBSD4.0, using gmake. + * The StringMatcher wasn't handling parentheses properly. Fixed it, + so now you can use expressions like *.(jpg,mp?). Also rewrote the + regex translation code to be more readable, and fixed a nasty + dangling-pointer bug in the process. + * Changed the RefCountMem class to use atomic_add instead of the + C preincrement and predecrement operators, so that it can be used + safely in multithreaded environments. + * Fixed a subtle bug in the Remove*() methods of the PortableQueue + class that could cause crashes if you were removing items whose + assignment operators looked at the PortableQueue they were + being removed from... geez + * Added some missing virtual destructors + * Rewrote the PortableString class to be more efficient, and + in the process found and fixed a nasty bug--the CStringHashFunc() + was completely broken. + * StringMatcher::Match() is now tagged as const. + +v1.24 - 6/17/00 + - muscled now splits its PR_RESULT_DATAITEMS messages into multiple + messages if the message has more than 50 items in it. This allows + for more fine-grained updates. + - Added a PR_NAME_MAX_UPDATE_MESSAGE_ITEMS parameter that can + be set by the client to change the above threshold if desired. + +v1.23 - 6/15/00 + - Added a StartServerThread() method to the MessageTransceiverThread + class. Now the MessageTransceiverThread can be used to accept + multiple overlapping connections, instead of just a series of + single connections. + * make clean now removes .xSYM files. + +v1.22 - 6/14/00 + * MessageTransceiverThread::WaitForAllMessagesToBeSent() was broken. + Changed it to use benaphores instead of BLockers, and now all is well. + +v1.21 - 6/12/00 + - Added Prepend() and Append() methods to PortableString. + - Added InetNtoA() convenience method to NetworkUtilityFunctions.cpp + - Added EscapeRegexTokens() to StorageReflectUtils.h + * User messages would be reflected back to the sender even when + the reflect-to-self flag wasn't set. Fixed. + * Fixed several bugs in the MessageTransceiverThread class. + * muscled now handles connections from localhost correctly. + +v1.20 - 6/8/00 + - Added new accessors to PortableHashtable and PortableMessage that + return key and value objects by pointer rather than by value. + This can make access and traversals more efficient, as it eliminates + an unnecessary data copy. + - Added default constructors to the PortableHashtableIterator and + PortableMessageFieldNameIterator classes so that you can use + them in arrays, as class members, etc. + - Added SetOutgoingQueueDrainedMessage() to the MessageTransceiverThread + API so that you can (optionally) receive notification when the outgoing + message queue has become empty. + * Made ObjectPool's destructor virtual. + +v1.10 - 6/1/00 + - Added ReplaceSession() method, by which a server-side session object + can replace itself with a different one. + - AbstractMessageIOGateway objects can now request Pulse() callbacks. + - Added system logging, to the console or to a log file, or both. + * Fixed some minor compilation problems + +v1.01 - 4/11/00 + o Redesigned the Pulse() support to work based on absolute wakeup times + rather than relative periods. + - Added some convenience methods for working with timeval structs to + NetworkUtilityFunctios.h + +v1.00 - 3/29/00 + - Initial release diff --git a/HOWTOBUILD.txt b/HOWTOBUILD.txt new file mode 100644 index 00000000..5d764e37 --- /dev/null +++ b/HOWTOBUILD.txt @@ -0,0 +1,21 @@ +How to build a muscle server ("muscled") + +- If you wish to compile muscled under Win32 using Visual C++, + go to the 'vc++' subfolder and follow the directions in the + README.txt file there. + +- For all other operating systems (MacOS/X, Linux, BSD, + Win32+Cygnus, etc), simply cd to the 'server' folder and + type 'make' or 'gmake' (whichever your system prefers to use). + Note that if zlib headers aren't installed on your system, you may + need to go into the 'zlib/zlib' folder and do a "./configure;make" + first. + +When the compile finishes, you will have an executable named +'muscled' (or 'muscled.exe' under Windows). To start the server, +simply run this program. If you want the server to run in the +background as a daemon process, run it like this: './muscled daemon' + +For more command line options, run './muscled help' + +-Jeremy diff --git a/LICENSE b/LICENSE index a6ed1cf1..74c514f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,62 @@ -Copyright (c) 2014, jfriesne -All rights reserved. +All source code in this archive (except for the "csharp", "regex/regex", +"delphi", and "zlib/zlib" subfolders) is Copyright 2000-2007 +Meyer Sound Laboratories Inc. -Redistribution and use in source and binary forms, with or without +The "csharp" subfolder was written by Wilson Yeung, and is included +by permission. + +The "regex/regex" subfolder's contents were written by Henry Spencer, +and are used by permission. See the file regex/regex/COPYRIGHT for details. + +The "zlib/zlib" subfolder's contents were written by Jean-loup Gailly +and Mark Adler and are used by permission. See the file +zlib/zlib/README for details. + +The "delphi" subfolder's contents were written by Matthew Emson, +and are used by permission. See the file delphi/licence-delphi.txt +for details. + +---------------------------------------------------------------------- + +The rest of the code in this archive is licensed under the +BSD Open Source License, which is as follows: + +---------------------------------------------------------------------- +Copyright (c) 2000-2009, Meyer Sound Laboratories Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +Neither the name of Meyer Sound Laboratories Inc., nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +If you make modifications to the source code that you +think would be generally useful, you are encouraged to +send them to me (at the email address below) for +inclusion into subsequent releases of this distribution. + +Jeremy Friesner +jaf@lcsaudio.com +jaf@meyersound.com diff --git a/README.html b/README.html new file mode 100644 index 00000000..29da57e0 --- /dev/null +++ b/README.html @@ -0,0 +1,635 @@ +MUSCLE: (Multi User Server Client Linking Environment) + +

+MUSCLE: Crossbar Server, Portable Messaging and Support Classes

+6/27/2014 v6.05 jaf@meyersound.com

+Jeremy Friesner / Meyer Sound Laboratories Inc.

+Win32 compatibility contributions by Vitaliy Mikitchenko

+C# client code by Wilson Yeung

+SSL support code contributed by Nathan Whitehorn +

+

+Note: To compile the server, cd to the "server" subdirectory +and type 'make'. The server executable, "muscled", should be +generated. You can run this server from the command line, (type +"muscled help" to get info about some options) and connect to it +with any MUSCLE-aware program. +

+Alternatively, if you prefer a server that runs as a GUI program +and you have Qt installed, you can cd to the "qtsupport/qt_muscled" +subdirectory and type "qmake; make". A GUI server program named +qt_muscled will be created. +

+The main goal of these classes is to provide an easy way to use +BMessage-style message passing across a network of heterogeneous +(MacOS/X, Linux, Windows, BSD, Unix, etc) systems. The secondary goal +is just to provide some handy data containers and other utility classes. +

+All of this code (except the atheossupport, besupport, and qtsupport +directories) should compile under any up-to-date C++ compiler-- +no proprietary APIs are used except inside of the appropriate #ifdefs. +

+For better documentation than this, please see the MUSCLE web site. +

+This code has been compiled (typically with gmake, or MSVC8 in Windows) and tested in the following environments: +

+

    +
  1. SUSE, Debian, and Ubuntu Linux on various 32-bit and 64-bit PCs
  2. +
  3. MacOS/X on various PowerPC and Intel based Macs
  4. +
  5. Microsoft Windows XP and Windows 7 using Visual Studio 2008 or higher (use the projects files in the 'vc++' subfolder)
  6. +
+

+It has no known bugs, but may have some yet-to-be-discovered ones. +Use at your own risk, Meyer Sound is not responsible for any disasters, blah blah blah. +

+Directory contents descriptions follow: +

+

    +csharp/ +

    + This directory contains Wilson Yeung's alpha port of the Muscle client + API to the C# language. Email Wilson (wilson@whack.org) with questions + about this port. +

    + +

  1. +dataio/ +

    + This directory contains the DataIO interface class, which is an abstraction + for any file-like device that can read or write sequences of bytes, and + (optionally) seek to a specified position. The DataIO class defines an + interface for devices that you can Read() bytes from or Write() bytes to. + This folder also includes some useful implementations of the DataIO interface, + including the following: +

    + + + + + + + + + + + + + + + + +
    AsyncDataIOWrapper that delegates I/O calls to an internal I/O thread
    ByteBufferDataIOFor file-style reading/writing to a ByteBuffer held in memory
    ChildProcessDataIOFor launching a child process and communicating with it via its stdout/stdin
    FailoverDataIO.Wrapper for automatic fallback to a second output when a primary output fails
    FileDataIOFor reading/writing via a C (FILE *) file handle
    FileDescriptorDataIOFor reading/writing via a Unix file descriptor
    MultiDataIOMultiplexer wrapper class for writing to multiple outputs at once
    NullDataIODummy class for directing data to the bit-bucket
    PacketizedDataIOWrapper for making TCP act more tr UDP
    RS232DataIOFor communicating via an RS-232 serial port
    SSLSocketDataIOFor communicating over SSL over TCP
    StdinDataIOFor reading from stdin
    TCPSocketDataIOFor communicating using a TCP socket
    UDPSocketDataIOFor communicating using a UDP socket
    XorDataIOWrapper class that applies XOR-"encryption" to all data that flows through it
    +

    +

  2. +
  3. +delphi/ +

    + This directory contains the MUSCLE client API written for the Delphi + programming environment. The contents of this directory were contributed + by Matthew Emson; see the Readme.txt file in this directory for more information. +

    +

  4. +
  5. +html/ +

    + This directory contains various HTML documentation for the MUSCLE project, + including the Beginner's Guide to MUSCLE, a document on how to create + custom MUSCLE servers, and the autodoc folder that contains files useful + for creating API documentation using the DOxygen documentation tool. +

    +

  6. +
  7. +iogateway/ +

    + This directory contains the AbstractMessageIOGateway interface. + An AbstractMessageIOGatweay is a "gateway" object that knows + how to manage bidirectional FIFO Message-stream traffic going to and + coming from a DataIO object. A gateway object queues outgoing MessageRefs, + and when there is room in the outgoing buffer to send one, it flattens the + next MessageRef in the outgoing-message-queue into a sequence of bytes, + and sends those bytes out via the DataIO object. It also reads bytes + coming in from the DataIO object and assembles those bytes back into + Message objects, which are then handed back to the AbstractGatewayMessageReceiver + specified by the calling code. + + This directory also contains some useful implementations of the + AbstractMessageIOGateway interfaces, which are as follows: +

    + + + + + + + +
    MessageIOGatewayFlattens Messages to the standard MUSCLE flattened-message binary format
    PacketTunnelIOGatewayFlattens Messages into a series of fixed-size packets suitable for UDP transmission
    PlainTextMessageIOGatewayConverts free-form lines of ASCII text into Messages, and vice versa
    RawDataMessageIOGatewayConverts arbitrary raw data into Messages, and vice versa
    SLIPFramedDataMessageIOGatewaySimilar to the RawDataMessageIOGateway class, except it uses SLIP framing conventions
    SignalMessageIOGatewayDummy gateway that doesn't send actual data, only indicates when data is available
    + +

    +

  8. +
  9. +java/ +

    + This directory contains a Java implementation of the MUSCLE client side + API. You can use the code in this folder to enable your Java program + to talk to a MUSCLE server or any other program that speaks the MUSCLE + Message protocol. +

    +

  10. +
  11. +message/ +

    + This directory contains MUSCLE's Message class. A Message is a general-purpose + data structure that is similar to BeOS's BMessage class. A Message consists + of a 32-bit integer "what code", plus zero or more named data fields, each of + which can contain one or more data items of a specified type. +

    + Here are some relevant details: +

    +

      +
    1. + MUSCLE messages support the following field types: + +

      + + + + + + + + + + + + + + + +
      int8B_INT8_TYPE8-bit signed integer values
      int16B_INT16_TYPE16-bit signed integer values
      int32B_INT32_TYPE32-bit signed integer values
      int64B_INT64_TYPE64-bit signed integer values
      boolB_BOOL_TYPEboolean values
      floatB_FLOAT_TYPEIEEE 754 floating point values
      PointerB_POINTER_TYPEPointer values (non-flattenable)
      MessageB_MESSAGE_TYPEMessage objects
      Flattenable(various types)Flattened Flattenable objects
      StringB_STRING_TYPEUTF8 character strings
      RectB_RECT_TYPERectangles (floats for left,top,right,bottom)
      PointB_POINT_TYPEPoints (floats for x,y)
      Raw Data BufferB_RAW_TYPESequences of zero or more untyped bytes
      TagB_TAG_TYPEUser-provided arbitrary objects (non-flattenable)
      +

      +

    2. + Message is a subclass of Flattenable (see below), and therefore a Message can be serialized + into a "flattened" buffer-of-bytes, which can be sent across a network or saved to a file, + and later the bytes can be unflattened back into an equivalent Message object. This is the + basis for most MUSCLE network communication. The flattening and unflattening is endian-aware, + so that e.g. a PowerPC machine can communicate with an Intel machine without problems. +
    3. +
    4. + Message has a GetFieldNameIterator() method, which returns + a MessageFieldNameIterator object, which can be used to + iterate over the fields of a Message. +
    5. +
    +
  12. +

    +

  13. +minimessage/ +

    + This directory contains the MiniMessage and MiniMessageGateway C APIs. + These APIs are C implementations of the C++ Message and MessageIOGateway classes. + They can be used in cases where (for whatever reason) you want to code your + program in C only and avoid C++. They aren't as easy-to-use as the C++ implementation, + but they should be sufficient for simple things, and they compile down to only + a few dozen kilobytes of object code. See the testmini.c and minireflectclient.c + test files in the tests directory for examples on how they are used. +

    +

  14. +
  15. +micromessage/ +

    + This directory contains the MicroMessage and MicroMessageGateway C APIs. + These APIs are C implementations of the C++ Message and MessageIOGateway classes. + These APIs go even farther towards minimalism than the minimessage APIs: in + particular, these APIs never allocate or free any data. Instead of converting + the message's flattened-data-bytes into a separate in-memory data structure like + Message and MiniMessage do, MicroMessage operates on the flattened-data-bytes directly. + This makes for a potentially much more efficient implementation; the main downside + is that when creating a MicroMessage, you can only append data; you cannot insert + or remove fields that you previously added. +

    +

  16. +
  17. +python/ +

    + This directory contains a minimal MUSCLE client-side API written in Python. + You can use the code in this directory to enable your Python scripts to talk + to a MUSCLE server or any other program that speaks the MUSCLE Message protocol. + Also included in this directory is some C++ glue code (in PythonUtilityFunctions.cpp) + that is useful when embedding Python code into C++ code -- the glue code uses MUSCLE + Messages as to transfer arguments from C++ to Python context and back again. +

    +

  18. +

    +

  19. +qtsupport/ +

    + This directory contains several classes that support clients that use TrollTech's + Qt cross-platform GUI API. The main one is the QMessageTransceiverThread class, + which is a Qt-aware subclass of the MessageTransceiverThread class. + + Using a QMessageTransceiverThread for your network I/O makes network communication + very simple; instead of dealing with bytes and network protocols, you simply + receive a Qt signal whenever incoming Messages are available, and call a method to + send a Message, etc. + + This folder also contains some sub-directories that contain small example + programs written for MUSCLE+Qt: +

    + + + + + +
    qt_exampleA simple multi-user 'game' and chat program
    qt_advanced_exampleA demonstration of embedding a MUSCLE server thread inside a Qt application
    qt_muscledA demonstration of a Qt app that runs a MUSCLE server as a child process
    qt_muscled_browserA hierarchical browser GUI for seeing what data is present in a muscle server's database
    +

    +

  20. +
  21. +reflector/ +

    + This directory contains server code for an n-way + "message crossbar server" program. This program will listen + on a specified port for TCP connections, and will allow the + TCP connections to "talk to each other" by forwarding Messages from + one client to another (or to multiple others). + The ServerProcessLoop() method implements the server's event loop, + while the AbstractReflectSession class is the interface for the server's side + of a TCP connection. There are currently two subclasses of + AbstractReflectSession included: the DumbReflectSession + class just reflects all received Messages to all connected clients, while + the StorageReflectSession class adds nice features like wildcard-based + Message routing, server-side data storage, and "notify-me-on-change" + subscription services. (See the MUSCLE Beginner's Guide for more info on this) + More elaborate logic can be added by creating subclasses of these classes. +

    +

  22. +
  23. +regex/ +

    + This directory contains code to support the use of regular expressions. + This includes some C++ pattern-matching utility classes, as well as a + sub-folder containing Henry Spencer's freeware C regex engine, for use + in OS's that do not provide their own regex library. +

    + Classes implemented in this directory include: +

    + + + + + + +
    FilePathExpanderExpands shell-style wildcards into a list of file paths
    PathMatcherControls wildcard-directed recursive iterations down the tree of DataNode objects
    QueryFilterImplements various predicate-logic operations on DataNode Message data
    StringMatcherDoes shell-style pattern matching on arbitrary character strings
    SegmentedStringMatcherLike StringMatcher, except that the strings are divided up into substrings which are evaluated separately (e.g. "a*/b*")
    +

    +

  24. +
  25. +sdlsupport/ +

    + This directory contains the SDLMessageTransceiverThread class, which is + a handy way to implement MUSCLE communication ability into your SDL program. + SDLMessageTransceiverThread class is just a thin wrapper subclass around + the MessageTransceiverThread class, but it interfaces MessageTransceiverThread + to SDL's event-notification system. +

    +

  26. +
  27. +server/ +

    + This contains the Makefile and main entry point for the "muscled" + server executable, and the "admin" muscled-server-administration utility. +

    +

  28. +
  29. +support/ +

    + This directory contains various "small things" needed to compile the + rest of the code. These include byte-ordering macros, BeOS-style type + codes, typedefs, and result constants, and the Flattenable + interface definition. +

    +

  30. +
  31. +syslog/ +

    + This directory contains functions for logging event messages to stdout + and/or to a file. Log messages can be "raw" (works just like printf) + or nicely formatted with the current time, redirected to a file, and so + on. The logging system also has optional functionality to rotate, compress + and/or delete old log files, to avoid filling up too much disk space. +

    +

  32. +
  33. +system/ +

    + This directory contains classes that represent "generic" interfaces + to OS-specific APIs; as such, they are not guaranteed to work on + every platform. Currently this directory contains the following classes: + +

    + +
    AcceptSocketsThreadA thread that accepts incoming TCP connections and hands them back to the parent thread +
    AtomicCounterAtomic-increment and atomic-decrement counter, for lock-free reference-counting +
    DetectNetworkConfigChangesSessionA session object that notifies the program when the host computer's network configuration has changed +
    GlobalMemoryAllocatorCode to monitor and optionally restrict the program's heap usage +
    MessageTransceiverThreadRuns a MUSCLE ReflectServer object in a separate thread. Provides asynchronous I/O +
    MutexProvides efficient in-process locking (aka critical sections) for thread-safety +
    SetupSystemProvides standardized startup and shutdown routines that must be used by any MUSCLE process +
    SharedMemoryImplements inter-process shared memory regions, including inter-process read/write locking ability +
    SignalMultiplexerMakes system signalling (e.g. catching of SIGINT or SIHUP) available to multiple pieces of code in the same process +
    SystemInfoProvides information about the environment the program is operating in (current directory, OS version, etc) +
    ThreadAn OS-neutral Thread class for multithreading purposes. Includes send/receive Message functionality for easy control +
    ThreadLocalStorageAn OS-neutral implementation of thread-local data storage +
    ThreadPoolA thread pool implementation to allow handling of many Messages in parallel across a finite number of threads. +
    +

    +

  34. +
  35. +test/ +

    + This directory contains various test programs that I use + to test and develop the code, and a Makefile to build them with. +

    + Currently this directory contains the following programs: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +

    bandwidthtesterGenerates lots of Message traffic and measures how fast a MUSCLE server can receive it
    calctypecodePrints out the decimal equivalent of a four-character ASCII what-code
    printtypecodePrints out the four-character ASCII equivalent of a given decimal value
    chatclientMinimalist BeShare-compatible chat client that can connect to any MUSCLE server
    deadlockDeliberately tries to create a deadlock. Primarily used for testing deadlockfinder.
    deadlockfinderParses the output generated by MUSCLE's MUSCLE_ENABLE_DEADLOCK_FINDER feature, and detects potential deadlock issues
    findsourcelocationsParses source code and lists source-locations matching a specified code generated by MUSCLE's MUSCLE_INCLUDE_SOURCE_CODE_LOCATION_IN_LOGTIME feature
    hextermA simple interactive terminal that sends, receives, and prints hexadecimal representation of all bytes received via TCP, UDP, etc.
    portableplaintextclientA simple interactive terminal for ASCII communication over TCP
    portablereflectclientA simple interactive terminal for Message communication over TCP (typically with a MUSCLE server)
    portscanAttempts to connect via TCP to a range of ports on a host, and reports which ports accepted the connection
    printsourcelocationsPrints the source-code-location codes of all LogTime() commands. Useful for building up a directory of source-location-codes for a given codebase
    readmessageReads a flattened Message from a file on disk and prints it to stdout in human-readable form
    serialproxyMakes a local serial port available to the network over TCP
    svncopyCreates a command script for bulk-adding specified files to an SVN repository
    testatheossupportTests the AtheOS support files in the atheossupport folder
    testbatchguardUnit test for the BatchGuard class
    testbesupportTests the BeOS support files in the beossupport folder
    testbytebufferUnit test for the ByteBuffer class
    testchildprocessUnit test for the ChildProcessDataIO class
    testendianUnit test for the endian-swapping routines
    testfilepathinfoUnit test for the FilePathInfo class
    testgatewayUnit test for the MessageIOGateway class
    testhashtableUnit test for the Hashtable class
    testmatchfilesUnit test for the ExpandFilePathWildCards() function
    testmessageUnit test for the Message class
    testmicroUnit test for the MicroMessage and MicroMessageGateway C routines
    testminiUnit test for the MiniMessage and MiniMessageGateway C routines
    testnagleUnit test to verify the presence of absence of Nagle's algorithm
    testnetconfigdetectUnit test for the DetectNetworkConfigChangesSession class
    testnetutilUnit test for the GetNetworkInterfaces() function
    testpacketioUnit test for the PacketizedDataIO clas
    testpackettunnelUnit test for the PacketTunnelIOGateway class
    testparsefileUnit test for the ParseFile() and ParseArgs() functions
    testpoolUnit test for the ObjectPool class
    testpulsenodeUnit test for the PulseNode class
    testqueryfilterUnit test for the QueryFilter classes
    testqueueUnit test for the Queue class
    testrefcountUnit test for the RefCount class
    testreflectclientUnit test for the various OS-specific MessageTransceiverThread subclasses
    testregexUnit test for the StringMatcher class
    testresponseTest to measure the response latency of a muscle server
    testserialUnit test for the RS232DataIO class
    testsharedmemUnit test for the SharedMemory class
    testsocketmultiplexerUnit test for the SocketMultiplexer class
    teststringUnit test for the String class
    testsysteminfoUnit test for the SystemInfo functionality
    testthreadUnit test for the Thread class
    testthreadpoolUnit test for the ThreadPool class
    testtimeUnit test for the various time-string-parsing and time-string-generation functions
    testtupleUnit test for the Tuple class
    testtypedefsUnit test for MUSCLE's standard type typedefs (int32, int64, etc)
    testudpTest/demonstration of using MUSCLE to send/receive UDP packets
    testzipUnit test of the ReadZipFile() and WriteZipFile() functions
    udpproxyForwards UDP packets from one computer to another, and vice versa
    uploadstressSpams a MUSCLE server with requests to see if the server can keep up
    win32clientExample of integrating MUSCLE client code with a Win32 event loop
    + +
    +

    + +

    +

  36. +
  37. +util/ +

    + This directory contains many useful one-off classes and function + collections, including: +

      +
    1. + BatchOperator +

      + BatchOperator is a templated mechanism for ensuring that a specified + method gets called on the first-level recursion into a call tree, and + that a matching method gets called on the final recursion out of the call tree. + This is handy for making sure that setup and shutdown code is called a the + correct times. +

      + ByteBuffer +

      + ByteBuffer is an intelligent byte-array class, that stores + its length, knows how to resize itself efficiently, is + reference-countable and flattenable, etc. +

      +

    2. +
    3. + CountedObject +

      + CountedObject is a class that other classes can be derived from + if you want to keep track of how many instances of them are in + memory at any given time. +

      +

    4. +
    5. + CPULoadMeter +

      + CPULoadMeter reports the percentage of CPU time being used on + the local computer from moment to moment (similar to what is + reported in Task Manager under Windows, or Activity Monitor + under MacOS/X) +

      +

    6. +
    7. + Directory +

      + Directory is a platform-neutral API for scanning a filesystem + directory and iterating over its contents. +

      +

    8. +
    9. + DebugTimer +

      + DebugTimer is a useful utility class that is useful for debugging + performance problems. It records the current time in its + constructor, and then in its destructor it prints out the + time that has elapsed since then (if the elapsed time is more + than a specified minimum time). +

      +

    10. +
    11. + FilePathInfo +

      + FilePathInfo is a platform-neutral API for querying the various + properties of an entry at a specified location in a file system + (e.g. is a file, a directory, or a symlink, how large is it, + when was it created or modified, etc) +

      +

    12. +
    13. + Hashtable +

      + Hashtable is a handy hash table class, with templated + key and value types and the traditional O(1) lookup time. + In addition to that, it includes other nice features, such + as "safe" iterators (so you can modify the Hashtable while + iterating through it), minimal-frequency memory allocations, + and the ability to sort the table by key or by value (it + maintains the ordering of the objects placed into the table). + Hashtable is used by the Message class, but is also quite + useful on its own. +

      +

    14. +
    15. + NestCount +

      + NestCount is a simple class for recording when the execution + path enters or exits a particular function, and (optionally) + making decisions based on whether a specified function is currently + on the stack or not. +

      +

    16. +
    17. + NetworkUtilityFunctions +

      + NetworkUtilityFunctions.cpp is a repository for common BSD socket + operations (like setting up sockets to connect or accept connections) + that I'm tired of writing over and over again. I think my API + is much easier to use than the plain sockets API, although my + API handles TCP connections only. +

      +

    18. +
    19. + ObjectPool +

      + The ObjectPool class is used to avoid excessive deletions and allocations + of commonly used objects (such as Messages or RefCountMems). + It works by recycling the items for re-use, and is templated so it can be + used for any type of object. +

      +

    20. +
    21. + PulseNode +

      + The PulseNode is an interface for objects that want to execute a particular + action at a specified time. It works in conjunction with the standard MUSCLE + event loop. Implementing classes define a Pulse() method that will be executed + at a specified time, and a GetPulseTime() method that returns a clock value + indicating when Pulse() should next be executed. +

      +

    22. +
    23. + Queue +

      + The Queue class is just a little templatized double-ended + queue (i.e. AddHead(), AddTail(), RemoveHead(), and RemoveTail() + are O(1) operations). It can be used as a Vector, Stack, or FIFO. + It's templatized for easy, type-safe reuse. +

      +

    24. +
    25. + RefCount +

      + The RefCount class implements generic reference counting for C++ + objects or arrays. To enable reference counting on an object, you + simply create a single Ref for that object, and (optionally) + make one or more copies of the Ref via the copy constructor + or the equals operator. Then, when the last Ref object + disappears, the C++ object or array is automatically deleted. It's + not a garbage collector, but it beats having to keep track of all your + allocations by hand... +

      +

    26. +
    27. + Socket +

      + A reference-countable C++ class wrapper for a socket or file descriptor. + Wrapping sockets and file descriptors in these objects allows them to + be easily shared across objects without introducing the possibility of + leaking them, or closing them before some other piece of code is done + using them. +

      +

    28. +
    29. + SocketMultiplexer +

      + An easy-to-use wrapper around the socket() (or poll()) socket-multiplexing API. + To use the poll()-based implementation, compile your code with -DMUSCLE_USE_POLL +

      +

    30. +
    31. + String +

      + The String class is just your basic character-string class, + in this case inspired by the java.lang.String class from Java. + This class was originally written by Michael Olivero (mike95@mike95.com) + and modified by myself. String extends Flattenable, + so it can be serialized in a generic manner. +

      +

    32. +
    33. + StringTokenizer +

      + A string tokenizing class similar to Java's Java.util.StringTokenizer, + only more efficient. +

      +

    34. +
    35. + TimeUtilityFunctions +

      + TimeUtilityFunctions.h is a repository of functions for dealing with + microsecond-accurate timing issues. +

      +

    36. +
    +
  38. +
  39. +vc++/ +

    + This directory contains project files for building muscled under Visual C++ + for Windows. These files were provided by Vitaliy Mikitchenko (aka VitViper) +

    +

  40. +
  41. +winsupport/ +

    + This directory contains the Win32MessageTransceiverThread class, which is + useful for interface MUSCLE code to the standard Win32 GUI event loop. + You can use this class to enable your Win32 C and C++ programs to communicate + with a MUSCLE server or any other program that speaks the MUSCLE Message + protocol. +

    +

  42. +
  43. +zlib/ +

    + This directory contains a subfolder named zlib (which contains the complete + source code of the zlib compressor/decompressor package). This directory + also contains some zlib-related muscle source, including ZLibCodec, which is a + convenience class for compressing and decompressing chunks of data, ZLibDataIO, + a wrapper class for transparent compression and decompression of I/O streams, + and ZLibUtilityFunctions, which contain some convenience functions for quickly + compressing and decompressing a Message in a compatible manner. +

    +

  44. +
+

+For more details, have a look at the autodocs, header files and/or the source itself. +

+-Jeremy + diff --git a/atheossupport/AThread.h b/atheossupport/AThread.h new file mode 100644 index 00000000..eca073ec --- /dev/null +++ b/atheossupport/AThread.h @@ -0,0 +1,122 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAThread_h +#define MuscleAThread_h + +#include +#include "system/Thread.h" + +namespace muscle { + +/** 'what' code sent to our owner when we need to signal him */ +enum { + MUSCLE_THREAD_SIGNAL = 1299408750 // 'Msgn' -- sent to the main thread when messages are ready for pickup +}; + +/** + * This class is templated to use as a AtheOS-specific subclass of any + * MUSCLE Thread subclass. It modifies its base class to send os::Messages + * instead of writing to a TCP socket when it wants to notify the main thread. + * BaseThread would typically be MessageTransceiverThread, AcceptSocketsThread, or the like. + */ +template class AThread : public BaseThread +{ +public: + /** Default Constructor. If you use this constructor, you will want to call + * SetTarget() and/or SetNotificationMessage() to set where the internal thread's + * reply os::Messages are to go. + */ + AThread() {SetNotificationMessageAux(NULL);} + + /** Constructor. + * @param target os::Messenger indicating where events-ready notifications should be + * sent to. Equivalent to calling SetTarget() on this object. + */ + AThread(const os::Messenger & target) : _target(target) {SetNotificationMessageAux(NULL);} + + /** Constructor. + * @param target os::Messenger indicating where events-ready notifications should be + * sent to. Equivalent to calling SetTarget() on this object. + * @param notifyMsg Message to send to notify the main thread that there is something to check. + */ + AThread(const os::Messenger & target, const os::Message & notifyMsg) : _target(target) {SetNotificationMessageAux(¬ifyMsg);} + + /** Destructor. If the internal thread was started, you must make sure it has been + * shut down by calling ShutdownThread() before deleting this object. + */ + virtual ~AThread() {/* empty */} + + /** Sets our current target os::Messenger (where the internal thread will send its event + * notification messages. Thread safe, so this may be called at any time. + * @param newTarget new destination for the MUSCLE_THREAD_SIGNAL signal os::Messages. + * @param optNewNotificationMessage If non-NULL, this pointer will be used to change the notification + * message also. Defaults to NULL. (see SetNotificationMessage()) + * @return B_NO_ERROR on success, or B_ERROR on failure (couldn't lock the lock???) + */ + status_t SetTarget(const os::Messenger & newTarget, const os::Message * optNewNotificationMessage = NULL) + { + if (this->LockSignalling() == B_NO_ERROR) + { + _target = newTarget; + if (optNewNotificationMessage) SetNotificationMessageAux(optNewNotificationMessage); + this->UnlockSignalling(); + return B_NO_ERROR; + } + return B_ERROR; + } + + /** Set a new message to send to when notifying the main thread that events are pending. + * The default message is simply a message with the 'what' code set to MUSCLE_THREAD_SIGNAL. + * An internal copy of (msg) is made, and the internal copy receives a "source" pointer + * field that points to this object. Thread safe, so may be called at any time. + * @param newMsg new os::Message to send instead of MUSCLE_THREAD_SIGNAL. + * @return B_NO_ERROR on success, or B_ERROR on failure (couldn't lock the lock???) + */ + status_t SetNotificationMessage(const os::Message & newMsg) + { + if (this->LockSignalling() == B_NO_ERROR) + { + SetNotificationMessageAux(&newMsg); + this->UnlockSignalling(); + return B_NO_ERROR; + } + return B_ERROR; + } + + /** Returns our current notification os::Message (send when incoming events are ready for pickup). Thread safe. */ + const os::Messenger & GetTarget() const {return _target;} + +protected: + /** Overridden to send a os::Message instead of doing silly TCP stuff */ + virtual void SignalOwner() {(void) _target.SendMessage(&_notificationMessage);} + +private: + void SetNotificationMessageAux(const os::Message * optMsg) + { + if (optMsg) _notificationMessage = *optMsg; + else + { + _notificationMessage.MakeEmpty(); + _notificationMessage.SetCode(MUSCLE_THREAD_SIGNAL); + } + _notificationMessage.AddPointer("source", this); + } + + os::Messenger _target; + os::Message _notificationMessage; +}; + +}; // end namespace muscle + +#include "system/MessageTransceiverThread.h" +#include "system/AcceptSocketsThread.h" + +namespace muscle { + +// typedefs for convenience +typedef AThread AMessageTransceiverThread; +typedef AThread AAcceptSocketsThread; + +}; // end namespace muscle + +#endif diff --git a/atheossupport/ConvertMessages.cpp b/atheossupport/ConvertMessages.cpp new file mode 100644 index 00000000..12971fc0 --- /dev/null +++ b/atheossupport/ConvertMessages.cpp @@ -0,0 +1,131 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include "atheossupport/ConvertMessages.h" + +namespace muscle { + +status_t ConvertToAMessage(const Message & from, os::Message & to) +{ + to.MakeEmpty(); + to.SetCode(from.what); + + uint32 type; + uint32 count; + bool fixedSize; + + for (MessageFieldNameIterator it = from.GetFieldNameIterator(B_ANY_TYPE, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + const String & n = it.GetFieldName(); + if (from.GetInfo(n, &type, &count, &fixedSize) != B_NO_ERROR) return B_ERROR; + + for (uint32 j=0; jx(), p->y()); + if (to.AddPoint(n, bpoint) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_RECT_TYPE: + { + const Rect * r = (const Rect *)nextItem; + os::Rect brect(r->left(), r->top(), r->right(), r->bottom()); + if (to.AddRect(n, brect) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_MESSAGE_TYPE: + { + MessageRef * msgRef = (MessageRef *) nextItem; + os::Message amsg; + if (msgRef->GetItemPointer() == NULL) return B_ERROR; + if (ConvertToAMessage(*msgRef->GetItemPointer(), amsg) != B_NO_ERROR) return B_ERROR; + if (to.AddMessage(n, &amsg) != B_NO_ERROR) return B_ERROR; + } + break; + + default: + if (to.AddData(n, type, nextItem, itemSize, fixedSize, count) != B_NO_ERROR) return B_ERROR; + break; + } + } + } + return B_NO_ERROR; +} + +status_t ConvertFromAMessage(const os::Message & from, Message & to) +{ + to.Clear(); + to.what = from.GetCode(); + + int numNames = from.GetNumNames(); + for (int32 i=0; ix, p->y); + if (to.AddPoint(name.c_str(), pPoint) != B_NO_ERROR) return B_ERROR; + } + break; + + case os::T_RECT: + { + const os::Rect * r = (const os::Rect *)nextItem; + Rect pRect(r->left, r->top, r->right, r->bottom); + if (to.AddRect(name.c_str(), pRect) != B_NO_ERROR) return B_ERROR; + } + break; + + case os::T_MESSAGE: + { + os::Message amsg; + if (amsg.Unflatten((const uint8 *)nextItem) != B_NO_ERROR) return B_ERROR; + Message * newMsg = newnothrow Message; + if (newMsg) + { + MessageRef msgRef(newMsg); + if (ConvertFromAMessage(amsg, *newMsg) != B_NO_ERROR) return B_ERROR; + if (to.AddMessage(name.c_str(), msgRef) != B_NO_ERROR) return B_ERROR; + } + else {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + break; + + default: + if (to.AddData(name.c_str(), type, nextItem, itemSize) != B_NO_ERROR) return B_ERROR; + break; + } + + } + } + } + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/atheossupport/ConvertMessages.h b/atheossupport/ConvertMessages.h new file mode 100644 index 00000000..bcda99e9 --- /dev/null +++ b/atheossupport/ConvertMessages.h @@ -0,0 +1,32 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleConvertMessages_h +#define MuscleConvertMessages_h + +#include +#include "message/Message.h" + +namespace muscle { + +/* + * Functions to convert AtheOS Messages to MUSCLE Messages and back. + * This code will only compile under AtheOS! + */ + +/** Converts a MUSCLE Message into an AtheOS Message. Only compiles under AtheOS! + * @param from the Message you wish to convert + * @param to the Message to write the result into + * @return B_NO_ERROR if the conversion succeeded, or B_ERROR if it failed. + */ +status_t ConvertToAMessage(const muscle::Message & from, os::Message & to); + +/** Converts an AtheOS Message into a MUSCLE Message. Only compiles under AtheOS! + * @param from the Message you wish to convert + * @param to the Message to write the result into + * @return B_NO_ERROR if the conversion succeeded, or B_ERROR if it failed. + */ +status_t ConvertFromAMessage(const os::Message & from, muscle::Message & to); + +}; // end namespace muscle + +#endif diff --git a/besupport/BThread.h b/besupport/BThread.h new file mode 100644 index 00000000..6ba92f95 --- /dev/null +++ b/besupport/BThread.h @@ -0,0 +1,122 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleBThread_h +#define MuscleBThread_h + +#include +#include "system/Thread.h" + +namespace muscle { + +/** 'what' code sent to our owner when we need to signal him */ +enum { + MUSCLE_THREAD_SIGNAL = 1299408750 // 'Msgn' -- sent to the main thread when messages are ready for pickup +}; + +/** + * This class is templated to use as a BeOS-specific subclass of any + * MUSCLE Thread subclass. It modifies its base class to send BMessages + * instead of writing to a TCP socket when it wants to notify the main thread. + * BaseThread would typically be MessageTransceiverThread, AcceptSocketsThread, or the like. + */ +template class BThread : public BaseThread +{ +public: + /** Default Constructor. If you use this constructor, you will want to call + * SetTarget() and/or SetNotificationMessage() to set where the internal thread's + * reply BMessages are to go. + */ + BThread() {SetNotificationMessageAux(NULL);} + + /** Constructor. + * @param target BMessenger indicating where events-ready notifications should be + * sent to. Equivalent to calling SetTarget() on this object. + */ + BThread(const BMessenger & target) : _target(target) {SetNotificationMessageAux(NULL);} + + /** Constructor. + * @param target BMessenger indicating where events-ready notifications should be + * sent to. Equivalent to calling SetTarget() on this object. + * @param notifyMsg BMessage to send to notify the main thread that there is something to check. + */ + BThread(const BMessenger & target, const BMessage & notifyMsg) : _target(target) {SetNotificationMessageAux(¬ifyMsg);} + + /** Destructor. If the internal thread was started, you must make sure it has been + * shut down by calling ShutdownThread() before deleting this object. + */ + virtual ~BThread() {/* empty */} + + /** Sets our current target BMessenger (where the internal thread will send its event + * notification messages. Thread safe, so this may be called at any time. + * @param newTarget new destination for the MUSCLE_THREAD_SIGNAL signal BMessages. + * @param optNewNotificationMessage If non-NULL, this pointer will be used to change the notification + * message also. Defaults to NULL. (see SetNotificationMessage()) + * @return B_NO_ERROR on success, or B_ERROR on failure (couldn't lock the lock???) + */ + status_t SetTarget(const BMessenger & newTarget, const BMessage * optNewNotificationMessage = NULL) + { + if (this->LockSignalling() == B_NO_ERROR) + { + _target = newTarget; + if (optNewNotificationMessage) SetNotificationMessageAux(optNewNotificationMessage); + this->UnlockSignalling(); + return B_NO_ERROR; + } + return B_ERROR; + } + + /** Set a new message to send to when notifying the main thread that events are pending. + * The default message is simply a message with the 'what' code set to MUSCLE_THREAD_SIGNAL. + * An internal copy of (msg) is made, and the internal copy receives a "source" pointer + * field that points to this object. Thread safe, so may be called at any time. + * @param newMsg new BMessage to send instead of MUSCLE_THREAD_SIGNAL. + * @return B_NO_ERROR on success, or B_ERROR on failure (couldn't lock the lock???) + */ + status_t SetNotificationMessage(const BMessage & newMsg) + { + if (this->LockSignalling() == B_NO_ERROR) + { + SetNotificationMessageAux(&newMsg); + this->UnlockSignalling(); + return B_NO_ERROR; + } + return B_ERROR; + } + + /** Returns our current notification BMessage (send when incoming events are ready for pickup). Thread safe. */ + const BMessenger & GetTarget() const {return _target;} + +protected: + /** Overridden to send a BMessage instead of doing silly TCP stuff */ + virtual void SignalOwner() {(void) _target.SendMessage(&_notificationMessage);} + +private: + void SetNotificationMessageAux(const BMessage * optMsg) + { + if (optMsg) _notificationMessage = *optMsg; + else + { + _notificationMessage.MakeEmpty(); + _notificationMessage.what = MUSCLE_THREAD_SIGNAL; + } + _notificationMessage.AddPointer("source", this); + } + + BMessenger _target; + BMessage _notificationMessage; +}; + +}; // end namespace muscle + +#include "system/MessageTransceiverThread.h" +#include "system/AcceptSocketsThread.h" + +namespace muscle { + +// typedefs for convenience +typedef BThread BMessageTransceiverThread; +typedef BThread BAcceptSocketsThread; + +}; // end namespace muscle + +#endif diff --git a/besupport/ConvertMessages.cpp b/besupport/ConvertMessages.cpp new file mode 100644 index 00000000..83b4642e --- /dev/null +++ b/besupport/ConvertMessages.cpp @@ -0,0 +1,131 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "besupport/ConvertMessages.h" + +namespace muscle { + +status_t ConvertToBMessage(const Message & from, BMessage & to) +{ + to.MakeEmpty(); + to.what = from.what; + + uint32 type; + uint32 count; + bool fixedSize; + + for (MessageFieldNameIterator it = from.GetFieldNameIterator(B_ANY_TYPE, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + const String & n = it.GetFieldName(); + if (from.GetInfo(n, &type, &count, &fixedSize) != B_NO_ERROR) return B_ERROR; + + for (uint32 j=0; jx(), p->y()); + if (to.AddPoint(n(), bpoint) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_RECT_TYPE: + { + const Rect * r = (const Rect *)nextItem; + BRect brect(r->left(), r->top(), r->right(), r->bottom()); + if (to.AddRect(n(), brect) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_MESSAGE_TYPE: + { + MessageRef * msgRef = (MessageRef *) nextItem; + BMessage bmsg; + if (msgRef->GetItemPointer() == NULL) return B_ERROR; + if (ConvertToBMessage(*msgRef->GetItemPointer(), bmsg) != B_NO_ERROR) return B_ERROR; + if (to.AddMessage(n(), &bmsg) != B_NO_ERROR) return B_ERROR; + } + break; + + default: + if (to.AddData(n(), type, nextItem, itemSize, fixedSize, count) != B_NO_ERROR) return B_ERROR; + break; + } + } + } + return B_NO_ERROR; +} + +status_t ConvertFromBMessage(const BMessage & from, Message & to) +{ + to.Clear(); + to.what = from.what; + +#if B_BEOS_VERSION_DANO + const char * name; +#else + char * name; +#endif + + type_code type; + int32 count; + + for (int32 i=0; (from.GetInfo(B_ANY_TYPE, i, &name, &type, &count) == B_NO_ERROR); i++) + { + for (int32 j=0; jx, p->y); + if (to.AddPoint(name, pPoint) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_RECT_TYPE: + { + const BRect * r = (const BRect *)nextItem; + Rect pRect(r->left, r->top, r->right, r->bottom); + if (to.AddRect(name, pRect) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_MESSAGE_TYPE: + { + BMessage bmsg; + if (bmsg.Unflatten((const char *)nextItem) != B_NO_ERROR) return B_ERROR; + Message * newMsg = newnothrow Message; + if (newMsg) + { + MessageRef msgRef(newMsg); + if (ConvertFromBMessage(bmsg, *newMsg) != B_NO_ERROR) return B_ERROR; + if (to.AddMessage(name, msgRef) != B_NO_ERROR) return B_ERROR; + } + else {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + break; + + default: + if (to.AddData(name, type, nextItem, itemSize) != B_NO_ERROR) return B_ERROR; + break; + } + + } + } + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/besupport/ConvertMessages.h b/besupport/ConvertMessages.h new file mode 100644 index 00000000..901a0c2a --- /dev/null +++ b/besupport/ConvertMessages.h @@ -0,0 +1,32 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleConvertMessages_h +#define MuscleConvertMessages_h + +#include +#include "message/Message.h" + +namespace muscle { + +/* + * Functions to convert BMessages to Messages and back. + * This code will only compile under BeOS! + */ + +/** Converts a Message into a BMessage. Only compiles under BeOS! + * @param from the Message you wish to convert + * @param to the BMessage to write the result into + * @return B_NO_ERROR if the conversion succeeded, or B_ERROR if it failed. + */ +status_t ConvertToBMessage(const Message & from, BMessage & to); + +/** Converts a BMessage into a Message. Only compiles under BeOS! + * @param from the BMessage you wish to convert + * @param to the Message to write the result into + * @return B_NO_ERROR if the conversion succeeded, or B_ERROR if it failed. + */ +status_t ConvertFromBMessage(const BMessage & from, Message & to); + +}; // end namespace muscle + +#endif diff --git a/borland/Makefile b/borland/Makefile new file mode 100644 index 00000000..5ca93b0c --- /dev/null +++ b/borland/Makefile @@ -0,0 +1,249 @@ +############################################################################# +# Makefile for building: muscled +# (originally generated by qmake, then hacked by jaf to compile muscled) +# (so if you don't like it, too bad) +############################################################################# + +####### Compiler, tools and options + +CC = bcc32 +CXX = bcc32 +CFLAGS = -O2 -x- -w -w-hid -v -DMUSCLE_SINGLE_THREAD_ONLY -DMUSCLE_ENABLE_ZLIB_ENCODING +CXXFLAGS= -O2 -x- -w -w-hid -v -DMUSCLE_SINGLE_THREAD_ONLY -DMUSCLE_ENABLE_ZLIB_ENCODING +INCPATH = -I"." -I".." -I"../regex/regex" +LINK = ilink32 +LFLAGS = -ap -Gn -Gi -x -Tpe +LIBS = import32.lib cw32.lib +STARTUP = c0x32.obj + + +####### Files + +SOURCES = ..\..\muscle\server\muscled.cpp \ + ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp \ + ..\..\muscle\iogateway\MessageIOGateway.cpp \ + ..\..\muscle\message\Message.cpp \ + ..\..\muscle\reflector\AbstractReflectSession.cpp \ + ..\..\muscle\reflector\DumbReflectSession.cpp \ + ..\..\muscle\reflector\StorageReflectSession.cpp \ + ..\..\muscle\reflector\FilterSessionFactory.cpp \ + ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp \ + ..\..\muscle\reflector\ReflectServer.cpp \ + ..\..\muscle\reflector\ServerComponent.cpp \ + ..\..\muscle\reflector\DataNode.cpp \ + ..\..\muscle\regex\StringMatcher.cpp \ + ..\..\muscle\regex\PathMatcher.cpp \ + ..\..\muscle\regex\QueryFilter.cpp \ + ..\..\muscle\regex\regex\regcomp.c \ + ..\..\muscle\regex\regex\regerror.c \ + ..\..\muscle\regex\regex\regexec.c \ + ..\..\muscle\regex\regex\regfree.c \ + ..\..\muscle\syslog\SysLog.cpp \ + ..\..\muscle\system\GlobalMemoryAllocator.cpp \ + ..\..\muscle\system\SetupSystem.cpp \ + ..\..\muscle\util\ByteBuffer.cpp \ + ..\..\muscle\util\MemoryAllocator.cpp \ + ..\..\muscle\util\MiscUtilityFunctions.cpp \ + ..\..\muscle\util\NetworkUtilityFunctions.cpp \ + ..\..\muscle\util\SocketMultiplexer.cpp \ + ..\..\muscle\util\String.cpp \ + ..\..\muscle\util\PulseNode.cpp \ + ..\..\muscle\zlib/ZLibCodec.cpp \ + ..\..\muscle\zlib/zlib/adler32.c \ + ..\..\muscle\zlib/zlib/deflate.c \ + ..\..\muscle\zlib/zlib/trees.c \ + ..\..\muscle\zlib/zlib/zutil.c \ + ..\..\muscle\zlib/zlib/inflate.c \ + ..\..\muscle\zlib/zlib/inftrees.c \ + ..\..\muscle\zlib/zlib/crc32.c \ + ..\..\muscle\zlib/zlib/compress.c \ + ..\..\muscle\zlib/zlib/inffast.c + +OBJECTS = muscled.obj \ + AbstractMessageIOGateway.obj \ + MessageIOGateway.obj \ + Message.obj \ + AbstractReflectSession.obj \ + DumbReflectSession.obj \ + StorageReflectSession.obj \ + FilterSessionFactory.obj \ + RateLimitSessionIOPolicy.obj \ + ReflectServer.obj \ + DataNode.obj \ + ServerComponent.obj \ + StringMatcher.obj \ + PathMatcher.obj \ + QueryFilter.obj \ + regcomp.obj \ + regerror.obj \ + regexec.obj \ + regfree.obj \ + SysLog.obj \ + GlobalMemoryAllocator.obj \ + SetupSystem.obj \ + ByteBuffer.obj \ + MemoryAllocator.obj \ + MiscUtilityFunctions.obj \ + NetworkUtilityFunctions.obj \ + SocketMultiplexer.obj \ + String.obj \ + PulseNode.obj \ + ZLibCodec.obj \ + adler32.obj \ + deflate.obj \ + trees.obj \ + zutil.obj \ + inflate.obj \ + inftrees.obj \ + crc32.obj \ + compress.obj \ + inffast.obj + +TARGET = muscled.exe + +####### Implicit rules + +.SUFFIXES: .cpp .cxx .cc .c + +.cpp.obj: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o$@ $< + +####### Build rules + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(LINK) $(LFLAGS) $(OBJECTS) $(LIBS) $(STARTUP) + +#$(TARGET): $(OBJECTS) +# $(LINK) @&&| +# $(LFLAGS) $(OBJECTS) $(LIBS) +#| + +# $(LFLAGS) $(OBJECTS) $(TARGET),,$(LIBS) + +clean: + -del *.exe + -del *.lib + -del *.obj + -del *.tds + -del $(TARGET) + +####### Compile + +AbstractMessageIOGateway.obj: ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oAbstractMessageIOGateway.obj ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp + +MessageIOGateway.obj: ..\..\muscle\iogateway\MessageIOGateway.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMessageIOGateway.obj ..\..\muscle\iogateway\MessageIOGateway.cpp + +Message.obj: ..\..\muscle\message\Message.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMessage.obj ..\..\muscle\message\Message.cpp + +AbstractReflectSession.obj: ..\..\muscle\reflector\AbstractReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oAbstractReflectSession.obj ..\..\muscle\reflector\AbstractReflectSession.cpp + +DumbReflectSession.obj: ..\..\muscle\reflector\DumbReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oDumbReflectSession.obj ..\..\muscle\reflector\DumbReflectSession.cpp + +StorageReflectSession.obj: ..\..\muscle\reflector\StorageReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oStorageReflectSession.obj ..\..\muscle\reflector\StorageReflectSession.cpp + +FilterSessionFactory.obj: ..\..\muscle\reflector\FilterSessionFactory.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oFilterSessionFactory.obj ..\..\muscle\reflector\FilterSessionFactory.cpp + +RateLimitSessionIOPolicy.obj: ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oRateLimitSessionIOPolicy.obj ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp + +ReflectServer.obj: ..\..\muscle\reflector\ReflectServer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oReflectServer.obj ..\..\muscle\reflector\ReflectServer.cpp + +DataNode.obj: ..\..\muscle\reflector\DataNode.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oDataNode.obj ..\..\muscle\reflector\DataNode.cpp + +ServerComponent.obj: ..\..\muscle\reflector\ServerComponent.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oServerComponent.obj ..\..\muscle\reflector\ServerComponent.cpp + +StringMatcher.obj: ..\..\muscle\regex\StringMatcher.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oStringMatcher.obj ..\..\muscle\regex\StringMatcher.cpp + +PathMatcher.obj: ..\..\muscle\regex\PathMatcher.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oPathMatcher.obj ..\..\muscle\regex\PathMatcher.cpp + +QueryFilter.obj: ..\..\muscle\regex\QueryFilter.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oQueryFilter.obj ..\..\muscle\regex\QueryFilter.cpp + +regcomp.obj: ..\..\muscle\regex\regex\regcomp.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregcomp.obj ..\..\muscle\regex\regex\regcomp.c + +regerror.obj: ..\..\muscle\regex\regex\regerror.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregerror.obj ..\..\muscle\regex\regex\regerror.c + +regexec.obj: ..\..\muscle\regex\regex\regexec.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregexec.obj ..\..\muscle\regex\regex\regexec.c + +regfree.obj: ..\..\muscle\regex\regex\regfree.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregfree.obj ..\..\muscle\regex\regex\regfree.c + +muscled.obj: ..\..\muscle\server\muscled.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -omuscled.obj ..\..\muscle\server\muscled.cpp + +SysLog.obj: ..\..\muscle\syslog\SysLog.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oSysLog.obj ..\..\muscle\syslog\SysLog.cpp + +GlobalMemoryAllocator.obj: ..\..\muscle\system\GlobalMemoryAllocator.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oGlobalMemoryAllocator.obj ..\..\muscle\system\GlobalMemoryAllocator.cpp + +SetupSystem.obj: ..\..\muscle\system\SetupSystem.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oSetupSystem.obj ..\..\muscle\system\SetupSystem.cpp + +ByteBuffer.obj: ..\..\muscle\util\ByteBuffer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oByteBuffer.obj ..\..\muscle\util\ByteBuffer.cpp + +MemoryAllocator.obj: ..\..\muscle\util\MemoryAllocator.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMemoryAllocator.obj ..\..\muscle\util\MemoryAllocator.cpp + +MiscUtilityFunctions.obj: ..\..\muscle\util\MiscUtilityFunctions.cpp ..\..\muscle\util\string.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMiscUtilityFunctions.obj ..\..\muscle\util\MiscUtilityFunctions.cpp + +NetworkUtilityFunctions.obj: ..\..\muscle\util\NetworkUtilityFunctions.cpp ..\..\muscle\util\string.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oNetworkUtilityFunctions.obj ..\..\muscle\util\NetworkUtilityFunctions.cpp + +SocketMultiplexer.obj: ..\..\muscle\util\SocketMultiplexer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oSocketMultiplexer.obj ..\..\muscle\util\SocketMultiplexer.cpp + +String.obj: ..\..\muscle\util\String.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oString.obj ..\..\muscle\util\String.cpp + +PulseNode.obj: ..\..\muscle\util\PulseNode.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oPulseNode.obj ..\..\muscle\util\PulseNode.cpp + +ZLibCodec.obj: ..\..\muscle\zlib\ZLibCodec.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oZLibCodec.obj ..\..\muscle\zlib\ZLibCodec.cpp + +adler32.obj: ..\..\muscle\zlib\zlib\adler32.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oadler32.obj ..\..\muscle\zlib\zlib\adler32.c + +deflate.obj: ..\..\muscle\zlib\zlib\deflate.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -odeflate.obj ..\..\muscle\zlib\zlib\deflate.c + +trees.obj: ..\..\muscle\zlib\zlib\trees.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -otrees.obj ..\..\muscle\zlib\zlib\trees.c + +zutil.obj: ..\..\muscle\zlib\zlib\zutil.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -ozutil.obj ..\..\muscle\zlib\zlib\zutil.c + +inflate.obj: ..\..\muscle\zlib\zlib\inflate.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oinflate.obj ..\..\muscle\zlib\zlib\inflate.c + +inftrees.obj: ..\..\muscle\zlib\zlib\inftrees.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oinftrees.obj ..\..\muscle\zlib\zlib\inftrees.c + +inffast.obj: ..\..\muscle\zlib\zlib\inffast.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oinffast.obj ..\..\muscle\zlib\zlib\inffast.c + +crc32.obj: ..\..\muscle\zlib\zlib\crc32.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -ocrc32.obj ..\..\muscle\zlib\zlib\crc32.c + +compress.obj: ..\..\muscle\zlib\zlib\compress.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -ocompress.obj ..\..\muscle\zlib\zlib\compress.c diff --git a/borland/README.txt b/borland/README.txt new file mode 100644 index 00000000..16c11e32 --- /dev/null +++ b/borland/README.txt @@ -0,0 +1,13 @@ +This folder contains a Makefile that can be used to compile +the muscle server under Win32 using Borland's free C++Builder5.5 +compiler. Some warnings will be generated; ignore them. + +To compile: + +- Make sure you have C++Builder5.5 installed and set up (you + can download it from Borland's web site, www.borland.com) + +- Open an MS-DOS shell and cd to this folder + +- type make + diff --git a/csharp/README.TXT b/csharp/README.TXT new file mode 100644 index 00000000..fd13ec25 --- /dev/null +++ b/csharp/README.TXT @@ -0,0 +1,48 @@ + +This folder contains a C# implementation of the MUSCLE client API. + +The code in this folder was converted from MUSCLE Java codebase +by Wilson Yeung (wilson@whack.org), and donated back to the MUSCLE +distribution on 6/26/2004. It was last updated on 1/2/2005. + +Please direct all C# related questions to Wilson, as I don't know +anything about that language. :^) + +-Jeremy + +----------------------------------------------------------------------- + +Hi Jeremy, + +Please find attached a zip archive of an initial version of a C# muscle +client. + +It's far from perfect, but it does seem to work. I've tested it using +Mono on Mac OS X and Visual Studio 7.1, although I confess that most of +the testing was on Mac OS X. + +Mostly a line for line port of the Java muscle client library, although +I changed the "MessageTransceiver" class to simply "Client" -- may +change it back to MessageTransceiver later. + +Things still to be done: + +1. The Client class found in Client.cs uses a blocking select loop +(although all sends and receives with the socket are non-blocking). +This causes send latency as the select needs to timeout before it gets +to waiting messages in the send queue. I should fix this by using the +.NET asynchronous send model (ie. Socket.BeginSend, +Socket.BeginReceive, etc), but I've been so swamped at work I haven't +gotten around to it yet. + +2. Better error checking. + +3. More documentation. + +4. More classes in the test directory. More examples in general. An +implementation of portablereflectclient. + +Hope this email finds you well. + +Wilson + diff --git a/csharp/src/AbstractMessageIOGateway.cs b/csharp/src/AbstractMessageIOGateway.cs new file mode 100644 index 00000000..c38bab2d --- /dev/null +++ b/csharp/src/AbstractMessageIOGateway.cs @@ -0,0 +1,36 @@ + +namespace muscle.iogateway { + using muscle.support; + using muscle.message; + + using System.IO; + + ///

+ /// Interface for an object that knows how to translate bytes + /// into Messages, and vice versa. + /// + /// + public interface AbstractMessageIOGateway { + + /// + /// Reads from the input stream until a Message can be assembled and + /// returned. + /// + /// The input stream from which to read. + /// + /// + /// The next assembled Message. + + Message unflattenMessage(Stream inputStream); + + /// + /// Converts the given Message into bytes and sends it out the stream. + /// + /// + /// + /// + /// + + void flattenMessage(Stream outputStream, Message msg); + } +} diff --git a/csharp/src/Client.cs b/csharp/src/Client.cs new file mode 100644 index 00000000..ec7c1b00 --- /dev/null +++ b/csharp/src/Client.cs @@ -0,0 +1,1020 @@ + + +namespace muscle.client + +{ + + using muscle.message; + + using muscle.iogateway; + + + + using System; + + using System.IO; + + using System.Net; + + using System.Net.Sockets; + + using System.Text; + + using System.Collections; + + using System.Diagnostics; + + using System.Threading; + + + + + + public delegate void DisconnectCallback(Client client, + + Exception err, + + object state); + + + + public delegate void MessagesCallback(Message [] messages, + + Client client, + + object state); + + + + public class Client : IDisposable + + { + + const int MAX_RECEIVED_BEFORE_CALLBACK = 200; + + const int RECEIVE_BUFFER_SIZE = 16384; + + + + public Client(string host, int port) : + + this(host, port, MessageIOGateway.MUSCLE_MESSAGE_ENCODING_DEFAULT) + + { + + + + } + + + + public Client(string host, int port, int encoding) + + { + + this.encoding = encoding; + + read_buffer = new byte[RECEIVE_BUFFER_SIZE]; + + write_buffer = null; + + write_pos = 0; + + sendQueue = new Queue(); + + run = true; + + + + socket = new Socket(AddressFamily.InterNetwork, + + SocketType.Stream, + + ProtocolType.Tcp); + + + + IPHostEntry hEntry = Dns.GetHostByName(host); + + IPAddress ipaddress = hEntry.AddressList[0]; + + endPoint = new IPEndPoint(ipaddress, port); + + + + } + + + + public IAsyncResult BeginConnect(AsyncCallback callback, object state) + + { + + return socket.BeginConnect(endPoint, callback, state); + + } + + + + public void Connect() + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + socket.Connect(endPoint); + + socket.Blocking = false; + + + + StartThreads(); + + } + + } + + + + public void EndConnect(IAsyncResult ar) + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + socket.EndConnect(ar); + + socket.Blocking = false; + + + + if (run) + + { + + StartThreads(); + + } + + } + + } + + + + private void StartThreads() + + { + + processThread = new Thread(new ThreadStart(this.ThreadProc)); + + processThread.Start(); + + } + + + + public void RegisterForDisconnect(DisconnectCallback callback, + + object state) + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + disconnectCallback = callback; + + disconnectState = state; + + } + + } + + + + public void RegisterForMessages(MessagesCallback callback, + + object state) + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + messagesCallback = callback; + + messagesState = state; + + } + + } + + + + public void Send(Message message) + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + lock (sendQueue) + + { + + bool notifyAll = false; + + + + if (sendQueue.Count == 0) + + notifyAll = true; + + + + sendQueue.Enqueue(message); + + + + if (notifyAll) + + Monitor.PulseAll(sendQueue); + + } + + } + + } + + + + public void Send(Message [] messages) + + { + + lock (this) + + { + + if (!run) + + { + + throw new ObjectDisposedException("Client already disposed or connection terminated"); + + } + + + + lock (sendQueue) + + { + + bool notifyAll = false; + + + + if (sendQueue.Count == 0) + + notifyAll = true; + + + + foreach (Message message in messages) + + { + + sendQueue.Enqueue(message); + + } + + + + if (notifyAll) + + Monitor.PulseAll(sendQueue); + + } + + } + + } + + + + public void Close() + + { + + Dispose(); + + } + + + + public void Dispose() + + { + + lock (this) + + { + + run = false; + + try + + { + + processThread.Interrupt(); + + } + + catch (Exception) + + { + + processThread.Abort(); + + } + + finally + + { + + sendQueue = null; + + write_buffer = null; + + read_buffer = null; + + + + socket.Close(); + + } + + } + + } + + + + private void DoCheckRead(Socket s, MessageDecoder decoder) + + { + + if (s != null) + + { + + while (run && socket.Available > 0) + + { + + int bytesRead = 0; + + bytesRead = socket.Receive(read_buffer); + + + + decoder.Decode(read_buffer, bytesRead); + + + + if (run && + + (bytesRead < read_buffer.Length || + + decoder.Received.Count >= MAX_RECEIVED_BEFORE_CALLBACK)) + + { + + ArrayList received = decoder.Received; + + Message [] array = + + (Message []) received.ToArray(typeof(Message)); + + received.Clear(); + + + + if (run && messagesCallback != null) + + messagesCallback(array, this, messagesState); + + break; + + } + + } + + } + + } + + + + private void DoCheckWrite(Socket s, MessageEncoder encoder) + + { + + if (s != null) + + { + + if (write_buffer != null) + + { + + while (run) + + { + + int bytes_sent = socket.Send(write_buffer, write_pos, + + write_buffer.Length - write_pos, + + SocketFlags.None); + + if (bytes_sent > 0) + + { + + write_pos += bytes_sent; + + if (write_pos == write_buffer.Length) + + { + + write_buffer = null; + + write_pos = 0; + + break; + + } + + } + + else if (bytes_sent == 0) + + { + + break; + + } + + } + + } + + else + + { + + lock (sendQueue) + + { + + if (sendQueue.Count > 0) + + { + + while (run && sendQueue.Count > 0) + + { + + Message m = (Message) sendQueue.Peek(); + + bool success = encoder.Encode(m); + + if (success) + + sendQueue.Dequeue(); + + else + + break; + + } + + + + write_buffer = encoder.GetAndResetBuffer(); + + } + + } + + } + + } + + } + + + + private void ProcessSocket() + + { + + + + MessageEncoder encoder = new MessageEncoder(); + + MessageDecoder decoder = new MessageDecoder(); + + + + ArrayList list = new ArrayList(); + + list.Add(socket); + + + + while (run) + + { + + ArrayList checkRead = null; + + ArrayList checkWrite = null; + + ArrayList checkError = null; + + + + checkRead = (ArrayList) list.Clone(); + + checkError = (ArrayList) list.Clone(); + + + + lock (sendQueue) + + { + + if (sendQueue.Count > 0 || write_buffer != null) + + checkWrite = (ArrayList) list.Clone(); + + } + + + + Socket.Select(checkRead, checkWrite, checkError, 500000); + + + + if (checkRead != null && checkRead.Count > 0) + + { + + Socket s = (Socket) checkRead[0]; + + if (s.Available == 0) + + throw new Exception("Remote side disconnected"); + + DoCheckRead(s, decoder); + + } + + + + if (checkWrite != null && checkWrite.Count > 0) + + { + + Socket s = (Socket) checkWrite[0]; + + DoCheckWrite(s, encoder); + + } + + } + + } + + + + internal void ThreadProc() + + { + + try + + { + + ProcessSocket(); + + } + + catch (Exception e) + + { + + try + + { + + disconnectCallback(this, e, disconnectState); + + } + + finally + + { + + Dispose(); + + } + + } + + } + + + + + + public Socket Socket + + { + + get { return socket; } + + } + + + + public IPEndPoint IPEndPoint + + { + + get { return endPoint; } + + } + + + + public class MessageDecoder + + { + + public MessageDecoder() + { + receiveList = new ArrayList(); + gw = new MessageIOGateway(); + buffer = new byte[BUFFER_SIZE]; + memStream = new MemoryStream(buffer); + } + + + + public void Decode(byte [] buf, int len) + + { + + int bytesToCopy = (buffer.Length - length < len) ? buffer.Length - length : len; + + int bytesRemaining = (bytesToCopy < len) ? len - bytesToCopy : 0; + + + + Buffer.BlockCopy( buf, 0, buffer, length, bytesToCopy ); + + length += bytesToCopy; + + + + while( bytesRemaining > 0 || length > 0 ) + + { + + if( !haveSize ) // get message size + + { + + if( length < 4 ) + + return; + + + + messageSize = BitConverter.ToInt32(buffer, 0); + + if (!BitConverter.IsLittleEndian) { + + messageSize = (int) + + ((((uint)messageSize) << 24) | + + ((((uint)messageSize) & 0xff00) << 8) | + + ((((uint)messageSize) & 0xff0000) >> 8) | + + ((((uint)messageSize) >> 24))); + + } + + + + messageSize += MessageIOGateway.MESSAGE_HEADER_SIZE; + + if( messageSize > buffer.Length ) + + { + + byte [] tmpArray = buffer; + + buffer = new byte[messageSize]; + + memStream = new MemoryStream(buffer); + + + + Buffer.BlockCopy( tmpArray, 0, buffer, 0, length ); + + } + + haveSize = true; + + } + + else if( length < messageSize ) // build buffer out to at least message size + + { + + int bytesAvailableInBuffer = buffer.Length - length; + + bytesToCopy = (bytesRemaining > bytesAvailableInBuffer) ? bytesAvailableInBuffer : bytesRemaining; + + if( bytesToCopy == 0 ) + + return; + + Buffer.BlockCopy( buf, len - bytesRemaining, buffer, length, bytesToCopy ); + + length += bytesToCopy; + + bytesRemaining -= bytesToCopy; + + } + + else // enough in buffer to process message + + { + + memStream.Seek(0, SeekOrigin.Begin); + + Message m = gw.unflattenMessage( memStream ); + + receiveList.Add(m); + + + + Buffer.BlockCopy( buffer, messageSize, buffer, 0, length - messageSize ); + + length -= messageSize; + + haveSize = false; + + } + + } + + } + + + + public ArrayList Received + + { + + get { return receiveList; } + + set { receiveList = value; } + + } + + + + const int BUFFER_SIZE = 262144; + + + + private byte [] buffer = null; + + private int length = 0; + + private ArrayList receiveList = null; + + private MessageIOGateway gw = null; + + private int messageSize = 0; + + private bool haveSize = false; + + private MemoryStream memStream = null; + + } + + + + public class MessageEncoder + + { + + const int BUFFER_SIZE = 1048576; + + + + public MessageEncoder() : + + this(MessageIOGateway.MUSCLE_MESSAGE_ENCODING_DEFAULT) + + { } + + + + public MessageEncoder(int encoding) + + { + + gw = new MessageIOGateway(); + + buffer = new byte[BUFFER_SIZE]; + + pos = 0; + + memStream = new MemoryStream(buffer, 0, BUFFER_SIZE); + + } + + + + public bool Encode(Message m) + + { + + int required = m.flattenedSize(); + + required += MessageIOGateway.MESSAGE_HEADER_SIZE; + + + + if (buffer.Length - pos < required) + + return false; + + else + + { + + gw.flattenMessage(memStream, m); + + pos += required; + + return true; + + } + + } + + + + public byte [] GetAndResetBuffer() + + { + + byte [] buffer_copy = new byte[pos]; + + Buffer.BlockCopy(buffer, 0, buffer_copy, 0, pos); + + memStream.Seek(0, SeekOrigin.Begin); + + pos = 0; + + + + return buffer_copy; + + } + + + + private byte [] buffer = null; + + private int pos = 0; + + private MessageIOGateway gw = null; + + private MemoryStream memStream = null; + + } + + + + private int encoding; + + + + private Socket socket = null; + + private IPEndPoint endPoint = null; + + + + private Queue sendQueue = null; + + + + private DisconnectCallback disconnectCallback = null; + + private MessagesCallback messagesCallback = null; + + private object disconnectState = null; + + private object messagesState = null; + + + + private bool run = true; + + + + private Thread processThread = null; + + + + byte [] write_buffer = null; + + byte [] read_buffer = null; + + int write_pos = 0; + + } + +} + diff --git a/csharp/src/FieldNotFoundException.cs b/csharp/src/FieldNotFoundException.cs new file mode 100644 index 00000000..183960a8 --- /dev/null +++ b/csharp/src/FieldNotFoundException.cs @@ -0,0 +1,6 @@ +namespace muscle.message { + public class FieldNotFoundException : MessageException { + public FieldNotFoundException(string s) : base(s) { } + public FieldNotFoundException() : base("Message entry not found") { } + } +} diff --git a/csharp/src/FieldTypeMismatchException.cs b/csharp/src/FieldTypeMismatchException.cs new file mode 100644 index 00000000..81dfaf3b --- /dev/null +++ b/csharp/src/FieldTypeMismatchException.cs @@ -0,0 +1,13 @@ +namespace muscle.message { + /// + /// Exception that is thrown if you try to access a field in a Message + /// by the wrong type (e.g. calling getInt() on a string field or somesuch) + /// + + public class FieldTypeMismatchException : MessageException { + public FieldTypeMismatchException(string s) : base(s) { } + + public FieldTypeMismatchException() : + base("Message entry type mismatch") { } + } +} diff --git a/csharp/src/Flattenable.cs b/csharp/src/Flattenable.cs new file mode 100644 index 00000000..19bad6b4 --- /dev/null +++ b/csharp/src/Flattenable.cs @@ -0,0 +1,74 @@ +namespace muscle.support { + using System.IO; + + /// + /// Interface for objects that can be flattened and unflattened + /// from Be-style byte streams. + /// + /// + public abstract class Flattenable : muscle.support.TypeConstants { + + /// + /// Should return true iff every object of this type has a + /// flattened size that is known at compile time. + /// + public abstract bool isFixedSize(); + + /// + /// Should return the type code identifying this type of object. + /// + public abstract int typeCode(); + + /// + /// Should return the number of bytes needed to store this object + /// in its current state. + /// + public abstract int flattenedSize(); + + /// + /// Should return a clone of this object. + /// + public abstract Flattenable cloneFlat(); + + /// + /// Should set this object's state equal to that of (setFromMe), + /// or throw an UnflattenFormatException if it can't be done. + /// + /// + /// + public abstract void setEqualTo(Flattenable setFromMe); + + /// + /// Should store this object's state into (buffer). + /// + /// + /// + /// + public abstract void flatten(BinaryWriter writer); + + /// + /// Should return true iff a buffer with type_code (code) can + /// be used to reconstruct + /// this object's state. + /// + /// A type code ant, e.g. B_RAW_TYPE or B_STRING_TYPE, + /// or something custom. + /// True iff this object can unflatten from a buffer of + /// the given type, false otherwise. + /// + public abstract bool allowsTypeCode(int code); + + /// + /// Should attempt to restore this object's state from the given buffer. + /// + /// The stream to read the object from + /// The number of bytes the object takes up in + /// the stream, or negative if this is unknown. + /// + /// + /// + public abstract void unflatten(BinaryReader reader, int numBytes); + } +} + diff --git a/csharp/src/Message.cs b/csharp/src/Message.cs new file mode 100644 index 00000000..521a571b --- /dev/null +++ b/csharp/src/Message.cs @@ -0,0 +1,1774 @@ +using muscle.support; + +namespace muscle.message { + using System; + using System.Text; + using System.IO; + using System.Collections; + using System.Diagnostics; + using System.Collections.Specialized; + + /// + /// This class is sort of similar to Be's BMessage class. When flattened, + /// the resulting byte stream is compatible with the flattened + /// buffers of MUSCLE's C++ Message class. + /// It only acts as a serializable data container; it does not + /// include any threading capabilities. + /// + + public class Message : Flattenable + { + /// Oldest serialization protocol version parsable by this code's + /// unflatten() methods + public const int + OLDEST_SUPPORTED_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /// Newest serialization protocol version parsable by this code's + /// unflatten() methods, as well as the version of the protocol + /// produce by this code's flatten() methods. + public const int CURRENT_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /// 32 bit 'what' code, for quick identification of message types. + /// Set this however you like. + public int what = 0; + + /// static constructor + static Message() { + _empty = new HybridDictionary(); + } + + /// Default Constructor. + public Message() { } + + /// Constructor. + /// + /// The 'what' member variable will be set to the value you specify here + /// + /// + public Message(int w) + { + what = w; + } + + /// Copy Constructor. + /// + /// the Message to make us a (wholly independant) copy. + /// + public Message(Message copyMe) + { + setEqualTo(copyMe); + } + + /// Returns an independent copy of this Message + public override Flattenable cloneFlat() + { + Message clone = new Message(); + clone.setEqualTo(this); + return clone; + } + + /// Sets this Message equal to (c) + /// What to clone. + /// + public override void setEqualTo(Flattenable c) + { + clear(); + Message copyMe = (Message)c; + what = copyMe.what; + IEnumerator fields = copyMe.fieldNames(); + while(fields.MoveNext()) { + copyMe.copyField((string)fields.Current, this); + } + } + + /// Returns an Enumeration of Strings that are the + /// field names present in this Message + public IEnumerator fieldNames() + { + return (_fieldTable != null) ? + _fieldTable.Keys.GetEnumerator() : _empty.Keys.GetEnumerator(); + } + + /// Returns the given 'what' constant as a human readable 4-byte + /// string, e.g. "BOOL", "BYTE", etc. + /// Any 32-bit value you would like to have turned + /// into a string + public static string whatString(int w) + { + byte [] temp = new byte[4]; + temp[0] = (byte)((w >> 24) & 0xFF); + temp[1] = (byte)((w >> 16) & 0xFF); + temp[2] = (byte)((w >> 8) & 0xFF); + temp[3] = (byte)((w >> 0) & 0xFF); + + Decoder d = Encoding.UTF8.GetDecoder(); + + int charArrayLen = d.GetCharCount(temp, 0, temp.Length); + char [] charArray = new char[charArrayLen]; + + int charsDecoded = d.GetChars(temp, 0, temp.Length, charArray, 0); + + return new string(charArray, 0, charsDecoded - 1); + } + + /// Returns the number of field names of the given type that + /// are present in the Message. + /// + /// The type of field to count, or B_ANY_TYPE to + /// count all field types. + /// + /// The number of matching fields, or zero if there are + /// no fields of the appropriate type. + /// + public int countFields(int type) + { + if (_fieldTable == null) + return 0; + if (type == B_ANY_TYPE) + return _fieldTable.Count; + + int count = 0; + IEnumerator e = _fieldTable.Values.GetEnumerator(); + while(e.MoveNext()) { + MessageField field = (MessageField) e.Current; + if (field.typeCode() == type) + count++; + } + return count; + } + + /// Returns the total number of fields in this Message. + /// + public int countFields() + { + return countFields(B_ANY_TYPE); + } + + /// Returns true iff there are no fields in this Message. + /// + public bool isEmpty() {return (countFields() == 0);} + + + /// Returns a string that is a summary of the contents of this Message. + /// Good for debugging. + /// + public override string ToString() + { + string ret = String.Format("Message: what='{0}' ({1}), countField={2} flattenedSize={3}\n", whatString(what), what, countFields(), flattenedSize()); + + IEnumerator e = this.fieldNames(); + + while(e.MoveNext()) { + string fieldName = (string) e.Current; + ret += String.Format(" {0}: {1}\n", fieldName, (MessageField) _fieldTable[fieldName]); + } + + return ret; + } + + /// Renames a field. + /// If a field with this name already exists, it will be replaced. + /// Old field name to rename + /// New field name to rename + /// + /// + public void renameField(string old_entry, string new_entry) + { + MessageField field = getField(old_entry); + _fieldTable.Remove(old_entry); + _fieldTable.Add(new_entry, field); + } + + /// false as messages can be of varying size. + public override bool isFixedSize() { + return false; + } + + /// Returns B_MESSAGE_TYPE + public override int typeCode() { + return B_MESSAGE_TYPE; + } + + /// Returns The number of bytes it would take to flatten this + /// Message into a byte buffer. + public override int flattenedSize() + { + // 4 bytes for the protocol revision #, 4 bytes for the + // number-of-entries field, 4 bytes for what code + int sum = 4 + 4 + 4; + if (_fieldTable != null) { + IEnumerator e = fieldNames(); + while(e.MoveNext()) { + string fieldName = (string) e.Current; + MessageField field = (MessageField) _fieldTable[fieldName]; + + // 4 bytes for the name length, name data, + // 4 bytes for entry type code, + /// 4 bytes for entry data length, entry data + sum += 4 + (fieldName.Length + 1) + 4 + 4 + field.flattenedSize(); + } + } + return sum; + } + + /// Returns true iff (code) is B_MESSAGE_TYPE + public override bool allowsTypeCode(int code) { + return (code == B_MESSAGE_TYPE); + } + + public override void flatten(BinaryWriter writer) + { + // Format: 0. Protocol revision number + // (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + writer.Write((int) CURRENT_PROTOCOL_VERSION); + writer.Write((int) what); + writer.Write((int) countFields()); + IEnumerator e = fieldNames(); + while(e.MoveNext()) { + string name = (string) e.Current; + MessageField field = (MessageField) _fieldTable[name]; + + byte [] byteArray = Encoding.UTF8.GetBytes(name); + + writer.Write((int) (byteArray.Length + 1)); + writer.Write(byteArray); + writer.Write((byte)0); // terminating NUL byte + + writer.Write(field.typeCode()); + writer.Write(field.flattenedSize()); + field.flatten(writer); + } + } + + public override void unflatten(BinaryReader reader, int numBytes) + { + clear(); + int protocolVersion = reader.ReadInt32(); + + if ((protocolVersion > CURRENT_PROTOCOL_VERSION)|| + (protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) + throw new UnflattenFormatException("Version mismatch error"); + + what = reader.ReadInt32(); + int numEntries = reader.ReadInt32(); + + if (numEntries > 0) + ensureFieldTableAllocated(); + + char [] charArray = null; + + Decoder d = Encoding.UTF8.GetDecoder(); + + for (int i=0; i + /// + /// + public void setBoolean(string name, bool val) + { + MessageField field = getCreateOrReplaceField(name, B_BOOL_TYPE); + bool [] array = (bool[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new bool[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /// Sets the given field name to contain the given boolean values. + /// Any previous field contents are replaced. + /// Note that the array is not copied; rather the passed-in array + /// becomes part of the Message. + /// + /// + public void setBooleans(string name, bool [] vals) + { + setObjects(name, B_BOOL_TYPE, vals, vals.Length); + } + + /// Returns the first boolean value in the given field. + /// + /// The first boolean value in the field. + /// + /// if the field + /// with the given name is not a B_BOOL_TYPE field + /// + public bool getBoolean(string name) + { + return getBooleans(name)[0]; + } + + public bool getBoolean(string name, bool def) { + try { + return getBoolean(name); + } + catch (MessageException) { + return def; + } + } + + public bool[] getBooleans(string name) + { + return (bool []) getData(name, B_BOOL_TYPE); + } + + public void setByte(string name, byte val) + { + MessageField field = getCreateOrReplaceField(name, B_INT8_TYPE); + byte [] array = (byte[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new byte[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setBytes(string name, byte [] vals) + { + setObjects(name, B_INT8_TYPE, vals, vals.Length); + } + + public byte getByte(string name) + { + return getBytes(name)[0]; + } + + public byte getByte(string name, byte def) { + try { + return getByte(name); + } + catch (MessageException) { + return def; + } + } + + public byte[] getBytes(string name) + { + return (byte[]) getData(name, B_INT8_TYPE); + } + + public void setShort(string name, short val) + { + MessageField field = getCreateOrReplaceField(name, B_INT16_TYPE); + short [] array = (short[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new short[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setShorts(string name, short [] vals) + { + setObjects(name, B_INT16_TYPE, vals, vals.Length); + } + + public short getShort(string name) + { + return getShorts(name)[0]; + } + + public short getShort(string name, short def) { + try { + return getShort(name); + } + catch (MessageException) { + return def; + } + } + + public short[] getShorts(string name) + { + return (short[]) getData(name, B_INT16_TYPE); + } + + public void setInt(string name, int val) + { + MessageField field = getCreateOrReplaceField(name, B_INT32_TYPE); + int [] array = (int[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new int[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setInts(string name, int [] vals) + { + setObjects(name, B_INT32_TYPE, vals, vals.Length); + } + + public int getInt(string name) + { + return getInts(name)[0]; + } + + public int getInt(string name, int def) { + try { + return getInt(name); + } catch (MessageException) { + return def; + } + } + + public int[] getInts(string name) + { + return (int[]) getData(name, B_INT32_TYPE); + } + + public int[] getInts(string name, int[] defs) { + try { + return getInts(name); + } + catch (MessageException) { + return defs; + } + } + + public void setLong(string name, long val) + { + MessageField field = getCreateOrReplaceField(name, B_INT64_TYPE); + long [] array = (long[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new long[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setLongs(string name, long [] vals) { + setObjects(name, B_INT64_TYPE, vals, vals.Length); + } + + public long getLong(string name) + { + return getLongs(name)[0]; + } + + public long getLong(string name, long def) { + try { + return getLong(name); + } + catch (MessageException) { + return def; + } + } + + public long[] getLongs(string name) + { + return (long[]) getData(name, B_INT64_TYPE); + } + + public void setFloat(string name, float val) + { + MessageField field = getCreateOrReplaceField(name, B_FLOAT_TYPE); + float [] array = (float[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new float[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setFloats(string name, float [] vals) { + setObjects(name, B_FLOAT_TYPE, vals, vals.Length); + } + + public float getFloat(string name) { + return getFloats(name)[0]; + } + + public float getFloat(string name, float def) { + try { + return getFloat(name); + } + catch (MessageException) { + return def; + } + } + + public float[] getFloats(string name) + { + return (float[]) getData(name, B_FLOAT_TYPE); + } + + public void setDouble(string name, double val) + { + MessageField field = getCreateOrReplaceField(name, B_DOUBLE_TYPE); + double [] array = (double[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new double[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setDoubles(string name, double [] vals) + { + setObjects(name, B_DOUBLE_TYPE, vals, vals.Length); + } + + public double getDouble(string name) + { + return getDoubles(name)[0]; + } + + public double getDouble(string name, double def) { + try { + return getDouble(name); + } + catch (MessageException) { + return def; + } + } + + public double[] getDoubles(string name) + { + return (double[]) getData(name, B_DOUBLE_TYPE); + } + + public void setString(string name, string val) + { + MessageField field = getCreateOrReplaceField(name, B_STRING_TYPE); + string [] array = (string[]) field.getData(); + if ((array == null)||(field.size() != 1)) + array = new string[1]; + array[0] = val; + field.setPayload(array, 1); + } + + public void setStrings(string name, string [] vals) + { + setObjects(name, B_STRING_TYPE, vals, vals.Length); + } + + public string getString(string name) + { + return getStrings(name)[0]; + } + + public string getString(string name, string def) { + try { + return getString(name); + } + catch (MessageException) { + return def; + } + } + + public string[] getStrings(string name) + { + return (string[]) getData(name, B_STRING_TYPE); + } + + public string[] getStrings(string name, string[] def) { + try { + return (string[]) getData(name, B_STRING_TYPE); + } + catch (MessageException) { + return def; + } + } + + /// Sets the given field name to contain a single Message value. + /// Any previous field contents are replaced. + /// Note that a copy of (val) is NOT made; the passed-in mesage + /// object becomes part of this Message. + /// + /// + /// + /// + /// + /// the object whose bytes are to be flattened out and put into + /// this field. + + public void setFlat(string name, Flattenable val) + { + int type = val.typeCode(); + MessageField field = getCreateOrReplaceField(name, type); + + object payload = field.getData(); + + switch(type) { + // For these types, we have explicit support for holding + // the objects in memory, so we'll just clone them + case B_MESSAGE_TYPE: + { + Message [] array = + ((payload != null)&&(((Message[])payload).Length == 1)) ? ((Message[])payload) : new Message[1]; + + array[1] = (Message) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_POINT_TYPE: + { + Point [] array = + ((payload != null)&&(((Point[])payload).Length == 1)) ? + ((Point[])payload) : new Point[1]; + array[1] = (Point) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_RECT_TYPE: + { + Rect [] array = + ((payload != null)&&(((Rect[])payload).Length == 1)) ? + ((Rect[])payload) : new Rect[1]; + array[1] = (Rect) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + // For everything else, we have to store the objects as byte buffers + default: + { + byte [][] array = + ((payload != null)&&(((byte[][])payload).Length == 1)) ? + ((byte[][])payload) : new byte[1][]; + array[0] = flattenToArray(val, array[0]); + field.setPayload(array, 1); + } + break; + } + } + + + /// Sets the given field name to contain the given Flattenable values. + /// Any previous field contents are replaced. + /// Note that if the objects are Messages, Points, or Rects, + /// they will be cloned rather than flattened. + /// + /// + /// Array of Flattenable objects to assign to the field. + /// The objects are all flattened and + /// the flattened data is put into the Message; + /// the objects themselves do not become part of the message. + /// + public void setFlats(string name, Flattenable [] vals) + { + int type = vals[0].typeCode(); + int len = vals.Length; + MessageField field = getCreateOrReplaceField(name, type); + + // For these types, we have explicit support for holding + // the objects in memory, so we'll just clone them + switch(type) { + case B_MESSAGE_TYPE: + { + Message [] array = new Message[len]; + for (int i=0; i + /// + /// A Flattenable object that, on success, will be set to reflect + /// the value held in this field. This object will not be referenced + /// by this Message. + /// + public void getFlat(string name, Flattenable returnObject) + { + MessageField field = getField(name); + if (returnObject.allowsTypeCode(field.typeCode())) + { + object o = field.getData(); + if (o is byte[][]) + unflattenFromArray(returnObject, ((byte[][])o)[0]); + else if (o is Message[]) + returnObject.setEqualTo(((Message[])o)[0]); + else if (o is Point[]) + returnObject.setEqualTo(((Point[])o)[0]); + else if (o is Rect[]) + returnObject.setEqualTo(((Rect[])o)[0]); + else + throw new FieldTypeMismatchException(name + " isn't a flattened-data field"); + } + else + throw new FieldTypeMismatchException("Passed-in object doesn't like typeCode " + whatString(field.typeCode())); + } + + /// Retrieves the contents of the given field as an array of + /// Flattenable values. + /// Name of the field to look for + /// Flattenable values in. + /// Should be an array of pre-allocated + /// Flattenable objects of the correct type. On success, this + /// array's objects will be set to the proper states as determined by + /// the held data in this Message. All the objects should be of the + /// same type. This method will unflatten as many objects as exist or + /// can fit in the array. These objects will not be referenced by + /// this Message. + /// The number of objects in (returnObjects) that were + /// actually unflattened. May be less than (returnObjects.length). + /// + /// + /// + /// + /// + /// + public int getFlats(string name, Flattenable [] returnObjects) + { + MessageField field = getField(name); + if (returnObjects[0].allowsTypeCode(field.typeCode())) + { + object objs = field.getData(); + int num; + if (objs is byte[][]) + { + byte [][] bufs = (byte[][]) objs; + num = (bufs.Length < returnObjects.Length) ? bufs.Length : returnObjects.Length; + for (int i=0; i + /// The type code to give the field. + /// May not be a B_*_TYPE that contains non-byte-buffer + /// data (e.g. B_STRING_TYPE or B_INT32_TYPE) + /// + /// Value that will become the sole value in the + /// specified field. + /// + /// + public void setByteBuffer(string name, int type, byte[] val) + { + checkByteBuffersOkay(type); + MessageField field = getCreateOrReplaceField(name, type); + byte [][] array = (byte[][]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new byte[1][]; + array[0] = val; + field.setPayload(array, 1); + } + + /// Sets the given field name to contain the given byte buffer + /// values. Any previous field contents are replaced. + /// + /// The type code to file the byte buffers under. + /// May not be any a B_*_TYPE that contains non-byte-buffer + /// data (e.g. B_STRING_TYPE or B_INT32_TYPE). + /// Array of byte buffers to assign to the + /// field. Note that the neither the array nor the buffers it + /// contains are copied; rather the all the passed-in data becomes + /// part of the Message. + /// + /// + public void setByteBuffers(string name, int type, byte [][] vals) + { + checkByteBuffersOkay(type); + setObjects(name, type, vals, vals.Length); + } + + /// Returns the first byte buffer value in the given field. + /// Note that the returned data is still held by this Message object. + public byte[] getBuffer(string name, int type) + { + return getBuffers(name,type)[0]; + } + + /** Returns the first byte buffer value in the given field. + * Note that the returned data is still held by this Message object. + * @param def The Default value to return if the field is not found or has the wrong type. + * @param name Name of the field to look for a byte buffer value in. + * @return The first byte buffer value in the field. + */ + public byte[] getBuffer(string name, int type, byte[] def) { + try { + return getBuffer(name, type); + } + catch (MessageException) { + return def; + } + } + + /// Returns the contents of the given field as an array of byte + /// buffer values. + public byte[][] getBuffers(string name, int type) + { + object ret = ((object[])getData(name, type))[0]; + if (ret is byte[][]) + return (byte[][]) ret; + throw new FieldTypeMismatchException(); + } + + //// Utility method to get the data of a field + /// (any type acceptable) with must-be-there semantics + public object getData(string name) + { + return getField(name).getData(); + } + + /// Gets the data of a field, returns def if any exceptions occur. + public object getData(string name, object def) { + try { + return getData(name); + } + catch (MessageException) { + return def; + } + } + + /// Utility method to get the data of a field + /// (of a given type, or B_ANY_TYPE) using standard must-be-there + /// semantics + public object getData(string name, int type) + { + return getField(name, type).getData(); + } + + /// Removes the specified field and its contents from the Message. + /// Has no effect if the field doesn't exist. + public void removeField(string name) + { + if (_fieldTable != null) + _fieldTable.Remove(name); + } + + /// Clears all fields from the Message. */ + public void clear() + { + if (_fieldTable != null) _fieldTable.Clear(); + } + + + /// Returns true iff there is a field with the given name and + /// type present in the Message + public bool hasField(string fieldName, int type) { + return (getFieldIfExists(fieldName, type) != null); + } + + /// Returns true iff this Message contains a field named (fieldName). + public bool hasField(string fieldName) { + return hasField(fieldName, B_ANY_TYPE); + } + + /// Returns the number of items present in the given field, or + /// zero if the field isn't present or is of the wrong type. + /// + public int countItemsInField(string name, int type) + { + MessageField field = getFieldIfExists(name, type); + return (field != null) ? field.size() : 0; + } + + /// Returns the number of items present in the given field, + /// or zero if the field isn't present + /// + public int countItemsInField(string fieldName) { + return countItemsInField(fieldName, B_ANY_TYPE); + } + + /// Returns the B_*_TYPE type code of the given field. + /// + public int getFieldTypeCode(string name) { + return getField(name).typeCode(); + } + + /// Take the data under (name) in this message, and moves it + /// into (moveTo). + /// + public void moveField(string name, Message moveTo) + { + MessageField field = getField(name); + _fieldTable.Remove(name); + moveTo.ensureFieldTableAllocated(); + moveTo._fieldTable.Add(name, field); + } + + /// Take the data under (name) in this message, and copies it + /// into (moveTo). + /// + public void copyField(string name, Message copyTo) + { + MessageField field = getField(name); + copyTo.ensureFieldTableAllocated(); + copyTo._fieldTable.Add(name, field.cloneFlat()); + } + + /// Flattens the given flattenable object into an array and + /// returns it. Pass in an old array to use if you like; + /// it may be reused, or not. */ + private byte[] flattenToArray(Flattenable flat, byte[] optOldBuf) + { + int fs = flat.flattenedSize(); + if ((optOldBuf == null)||(optOldBuf.Length < fs)) + optOldBuf = new byte[fs]; + + using (MemoryStream memStream = new MemoryStream(optOldBuf)) { + BinaryWriter writer = new BinaryWriter(memStream); + flat.flatten(writer); + } + + return optOldBuf; + } + + /// Unflattens the given array into the given object. + /// + /// + private void unflattenFromArray(Flattenable flat, byte [] buf) + { + using (MemoryStream memStream = new MemoryStream(buf)) { + BinaryReader reader = new BinaryReader(memStream); + flat.unflatten(reader, buf.Length); + } + } + + /// Convenience method: when it returns, _fieldTable is guaranteed + /// to be non-null. + private void ensureFieldTableAllocated() + { + if (_fieldTable == null) { + _fieldTable = new HybridDictionary(); + } + } + + /// Sets the contents of a field + private void setObjects(string name, + int type, + System.Array values, + int numValues) { + getCreateOrReplaceField(name, type).setPayload(values, numValues); + } + + /// Utility method to get a field (if it exists), create it if it + /// doesn't, or replace it if it exists but is the wrong type. + /// + /// B_*_TYPE of the field to get or create. + /// B_ANY_TYPE should not be used. + /// The (possibly new, possible pre-existing) + /// MessageField object. + private MessageField getCreateOrReplaceField(string name, int type) + { + ensureFieldTableAllocated(); + MessageField field = (MessageField) _fieldTable[name]; + if (field != null) { + if (field.typeCode() != type) { + _fieldTable.Remove(name); + return getCreateOrReplaceField(name, type); + } + } + else { + field = new MessageField(type); + _fieldTable.Add(name, field); + } + return field; + } + + /// Utility method to get a field (any type acceptable) with + /// must-be-there semantics + private MessageField getField(string name) + { + MessageField field; + if ((_fieldTable != null)&&((field = (MessageField)_fieldTable[name]) != null)) + return field; + throw new FieldNotFoundException("Field " + name + " not found."); + } + + /// Utility method to get a field (of a given type) using standard + /// must-be-there semantics + private MessageField getField(string name, int type) + { + MessageField field; + if ((_fieldTable != null)&&((field = (MessageField)_fieldTable[name]) != null)) + { + if ((type == B_ANY_TYPE)||(type == field.typeCode())) + return field; + throw new FieldTypeMismatchException(String.Format("Field type mismatch in entry {0}. Requested type code: {1} but got type code {2}", name, type, field.typeCode())); + } + else throw new FieldNotFoundException("Field " + name + " not found."); + } + + /// Utility method to get a field using standard + /// may-or-may-not-be-there semantics + private MessageField getFieldIfExists(string name, int type) + { + MessageField field; + return ((_fieldTable != null)&&((field = (MessageField)_fieldTable[name]) != null)&&((type == B_ANY_TYPE)||(type == field.typeCode()))) ? field : null; + } + + /// Throws an exception iff byte-buffers aren't allowed as type (type) + private void checkByteBuffersOkay(int type) + { + switch(type) + { + case B_BOOL_TYPE: case B_DOUBLE_TYPE: case B_FLOAT_TYPE: + case B_INT64_TYPE: case B_INT32_TYPE: case B_INT16_TYPE: + case B_MESSAGE_TYPE: case B_POINT_TYPE: case B_RECT_TYPE: case B_STRING_TYPE: + throw new FieldTypeMismatchException(); + + default: + /* do nothing */ + break; + } + } + + /// Represents a single array of like-typed objects + private class MessageField : Flattenable + { + public MessageField(int type) {_type = type;} + public int size() {return _numItems;} + public object getData() {return _payload;} + public override bool isFixedSize() {return false;} + public override int typeCode() {return _type;} + + /** Returns the number of bytes in a single flattened item, or 0 if our items are variable-size */ + public int flattenedItemSize() + { + switch(_type) { + case B_BOOL_TYPE: + case B_INT8_TYPE: + return 1; + case B_INT16_TYPE: + return 2; + case B_FLOAT_TYPE: + case B_INT32_TYPE: + return 4; + case B_INT64_TYPE: + case B_DOUBLE_TYPE: + case B_POINT_TYPE: + return 8; + case B_RECT_TYPE: + return 16; + default: + return 0; + } + } + + /// Returns the number of bytes the MessageField will take + /// up when it is flattened. + public override int flattenedSize() + { + int ret = 0; + switch(_type) + { + case B_BOOL_TYPE: + case B_INT8_TYPE: + case B_INT16_TYPE: + case B_FLOAT_TYPE: + case B_INT32_TYPE: + case B_INT64_TYPE: + case B_DOUBLE_TYPE: + case B_POINT_TYPE: + case B_RECT_TYPE: + ret += _numItems * flattenedItemSize(); + break; + case B_MESSAGE_TYPE: + { + // there is no number-of-items field for + // B_MESSAGE_TYPE (for historical reasons, sigh) + Message [] array = (Message[]) _payload; + for (int i=0; i<_numItems; i++) + ret += 4 + array[i].flattenedSize(); + // 4 for the size int + } + break; + + case B_STRING_TYPE: + { + ret += 4; // for the number-of-items field + string [] array = (string[]) _payload; + for (int i=0; i<_numItems; i++) { + ret += 4; + ret += Encoding.UTF8.GetByteCount(array[i]) + 1; + // 4 for the size int, 1 for the nul byte + } + } + break; + + default: + { + ret += 4; // for the number-of-items field + byte [][] array = (byte[][]) _payload; + for (int i=0; i<_numItems; i++) + ret += 4 + array[i].Length; + // 4 for the size int + } + break; + } + return ret; + } + + /// Unimplemented, throws a ClassCastException exception + public override void setEqualTo(Flattenable setFromMe) + { + throw new System.InvalidCastException("MessageField.setEqualTo() not supported"); + } + + + /// Flattens our data into the given stream + /// + /// + public override void flatten(BinaryWriter writer) + { + switch(typeCode()) + { + case B_BOOL_TYPE: + { + bool [] array = (bool[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((byte) (array[i] ? 1 : 0)); + } + break; + + case B_INT8_TYPE: + { + byte [] array = (byte[]) _payload; + writer.Write(array, 0, _numItems); // wow, easy! + } + break; + + case B_INT16_TYPE: + { + short [] array = (short[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((short) array[i]); + } + break; + + case B_FLOAT_TYPE: + { + float [] array = (float[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((float) array[i]); + } + break; + + case B_INT32_TYPE: + { + int [] array = (int[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((int) array[i]); + } + break; + + case B_INT64_TYPE: + { + long [] array = (long[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((long) array[i]); + } + break; + + case B_DOUBLE_TYPE: + { + double [] array = (double[]) _payload; + for (int i=0; i<_numItems; i++) + writer.Write((double) array[i]); + } + break; + + case B_POINT_TYPE: + { + Point [] array = (Point[]) _payload; + for (int i=0; i<_numItems; i++) + array[i].flatten(writer); + } + break; + + case B_RECT_TYPE: + { + Rect [] array = (Rect[]) _payload; + for (int i=0; i<_numItems; i++) + array[i].flatten(writer); + } + break; + + case B_MESSAGE_TYPE: + { + Message [] array = (Message[]) _payload; + for (int i=0; i<_numItems; i++) { + writer.Write((int) array[i].flattenedSize()); + array[i].flatten(writer); + } + } + break; + + case B_STRING_TYPE: + { + string [] array = (string[]) _payload; + writer.Write((int) _numItems); + + for (int i=0; i<_numItems; i++) { + byte [] utf8Bytes = Encoding.UTF8.GetBytes(array[i]); + writer.Write(utf8Bytes.Length + 1); + writer.Write(utf8Bytes); + writer.Write((byte) 0); // nul terminator + } + } + break; + + default: + { + byte [][] array = (byte[][]) _payload; + writer.Write((int) _numItems); + + for (int i=0; i<_numItems; i++) { + writer.Write(array[i].Length); + writer.Write(array[i]); + } + } + break; + } + } + + /// Returns true iff (code) equals our type code */ + public override bool allowsTypeCode(int code) { + return (code == typeCode()); + } + + /// Restores our state from the given stream. + /// Assumes that our _type is already set correctly. + /// + /// + public override void unflatten(BinaryReader reader, int numBytes) + { + // For fixed-size types, calculating the number of items + // to read is easy... + int flattenedCount = flattenedItemSize(); + + if (flattenedCount > 0) + _numItems = numBytes / flattenedCount; + + switch(_type) + { + case B_BOOL_TYPE: + { + bool [] array = new bool[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = (reader.ReadByte() > 0) ? true : false; + _payload = array; + } + break; + + case B_INT8_TYPE: + { + byte [] array = reader.ReadBytes(_numItems); + _payload = array; + } + break; + + case B_INT16_TYPE: + { + short [] array = new short[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = reader.ReadInt16(); + _payload = array; + } + break; + + case B_FLOAT_TYPE: + { + float [] array = new float[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = reader.ReadSingle(); + _payload = array; + } + break; + + case B_INT32_TYPE: + { + int [] array = new int[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = reader.ReadInt32(); + _payload = array; + } + break; + + case B_INT64_TYPE: + { + long [] array = new long[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = reader.ReadInt64(); + _payload = array; + } + break; + + case B_DOUBLE_TYPE: + { + double [] array = new double[_numItems]; + for (int i=0; i<_numItems; i++) + array[i] = reader.ReadDouble(); + _payload = array; + } + break; + + case B_POINT_TYPE: + { + Point [] array = new Point[_numItems]; + for (int i=0; i<_numItems; i++) { + Point p = array[i] = new Point(); + p.unflatten(reader, p.flattenedSize()); + } + _payload = array; + } + break; + + case B_RECT_TYPE: + { + Rect [] array = new Rect[_numItems]; + for (int i=0; i<_numItems; i++) { + Rect r = array[i] = new Rect(); + r.unflatten(reader, r.flattenedSize()); + } + _payload = array; + } + break; + + case B_MESSAGE_TYPE: + { + ArrayList temp = new ArrayList(); + while(numBytes > 0) { + Message subMessage = new Message(); + int subMessageSize = reader.ReadInt32(); + subMessage.unflatten(reader, subMessageSize); + temp.Add(subMessage); + numBytes -= (subMessageSize + 4); // 4 for the size int + } + _numItems = temp.Count; + Message [] array = new Message[_numItems]; + for (int j=0; j<_numItems; j++) + array[j] = (Message) temp[j]; + _payload = array; + } + + break; + + case B_STRING_TYPE: + { + Decoder d = Encoding.UTF8.GetDecoder(); + + _numItems = reader.ReadInt32(); + string [] array = new string[_numItems]; + + byte [] byteArray = null; + char [] charArray = null; + for (int i=0; i<_numItems; i++) { + int nextStringLen = reader.ReadInt32(); + byteArray = reader.ReadBytes(nextStringLen); + + int charsRequired = d.GetCharCount(byteArray, + 0, + nextStringLen); + + if (charArray == null || charArray.Length < charsRequired) + charArray = new char[charsRequired]; + + int charsDecoded = d.GetChars(byteArray, + 0, + byteArray.Length, + charArray, + 0); + + array[i] = new string(charArray, 0, charsDecoded - 1); + } + _payload = array; + } + break; + + default: + { + _numItems = reader.ReadInt32(); + byte [][] array = new byte[_numItems][]; + for (int i=0; i<_numItems; i++) { + int length = reader.ReadInt32(); + array[i] = new byte[length]; + array[i] = reader.ReadBytes(length); + } + _payload = array; + } + break; + } + } + + /// Prints some debug info about our state to (out) + public override string ToString() + { + string ret = " Type='"+whatString(_type)+"', " + + _numItems + " items: "; + int pitems = (_numItems < 10) ? _numItems : 10; + switch(_type) + { + case B_BOOL_TYPE: + { + byte [] array = (byte[]) _payload; + for (int i=0; i + /// Base class for the various Exceptions that the Message class may throw. + /// + public class MessageException : System.Exception { + public MessageException(string s) : base(s) { } + + public MessageException() : + base("Error accessing a Message field") { } + } +} diff --git a/csharp/src/MessageIOGateway.cs b/csharp/src/MessageIOGateway.cs new file mode 100644 index 00000000..22906219 --- /dev/null +++ b/csharp/src/MessageIOGateway.cs @@ -0,0 +1,111 @@ + +namespace muscle.iogateway { + using muscle.support; + using muscle.message; + + using System; + using System.IO; + + /// + /// A gateway that converts to and from the 'standard' MUSCLE + /// flattened message byte stream. + /// + + public class MessageIOGateway : AbstractMessageIOGateway { + public MessageIOGateway() : + this(MUSCLE_MESSAGE_ENCODING_DEFAULT) + { + + } + + // + // Constructs a MessageIOGateway whose outgoing encoding + // is one of MUSCLE_MESSAGE_ENCODING_*. + private MessageIOGateway(int encoding) + { + _encoding = encoding; + } + + + /// + /// Reads from the input stream until a Message can be assembled + /// and returned. + /// + /// + /// the input stream from which to read + /// + /// + /// The next assembled Message + /// + public Message unflattenMessage(Stream inputStream) + { + BinaryReader reader = new BinaryReader(inputStream); + int numBytes = reader.ReadInt32(); + int encoding = reader.ReadInt32(); + + int independent = reader.ReadInt32(); + inputStream.Seek(-4, SeekOrigin.Current); + + Message pmsg = new Message(); + pmsg.unflatten(reader, numBytes); + + return pmsg; + } + + /// + /// Converts the given Message into bytes and sends it out the stream. + /// + /// the data stream to send the converted bytes + /// + /// the message to convert + /// + /// + public void flattenMessage(Stream outputStream, Message msg) + { + int flattenedSize = msg.flattenedSize(); + + if (flattenedSize >= 32 && + _encoding >= MUSCLE_MESSAGE_ENCODING_ZLIB_1 && + _encoding <= MUSCLE_MESSAGE_ENCODING_ZLIB_9) { + + // currently do not compress outgoing messages + // do later + BinaryWriter writer = new BinaryWriter(outputStream); + writer.Write((int) flattenedSize); + writer.Write((int) MessageIOGateway.MUSCLE_MESSAGE_ENCODING_DEFAULT); + msg.flatten(writer); + writer.Flush(); + } + else { + BinaryWriter writer = new BinaryWriter(outputStream); + writer.Write((int) flattenedSize); + writer.Write((int) MessageIOGateway.MUSCLE_MESSAGE_ENCODING_DEFAULT); + msg.flatten(writer); + writer.Flush(); + } + } + + private int _encoding; + + public const int MESSAGE_HEADER_SIZE = 8; + + // 'Enc0' -- just plain ol' flattened Message objects, with no + // special encoding + public const int MUSCLE_MESSAGE_ENCODING_DEFAULT = 1164862256; + + // zlib encoding levels + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_1 = 1164862257; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_2 = 1164862258; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_3 = 1164862259; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_4 = 1164862260; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_5 = 1164862261; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_6 = 1164862262; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_7 = 1164862263; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_8 = 1164862264; + public const int MUSCLE_MESSAGE_ENCODING_ZLIB_9 = 1164862265; + public const int MUSCLE_MESSAGE_ENCODING_END_MARKER = 1164862266; + + public const int ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219; + public const int ZLIB_CODEC_HEADER_DEPENDENT = 2053925218; + } +} diff --git a/csharp/src/Point.cs b/csharp/src/Point.cs new file mode 100644 index 00000000..ed2bbcec --- /dev/null +++ b/csharp/src/Point.cs @@ -0,0 +1,128 @@ + +namespace muscle.support { + using System.IO; + + public class Point : Flattenable { + /// Our data members, exposed for convenience + public float x = 0.0f; + public float y = 0.0f; + + /// Default constructor, sets the point to be (0, 0) */ + public Point() { x = 0.0f; y = 0.0f; } + + public Point(float X, float Y) { + set(X, Y); + } + + public override Flattenable cloneFlat() { + return new Point(x, y); + } + + public override void setEqualTo(Flattenable setFromMe) + { + Point p = (Point) setFromMe; + set(p.x, p.y); + } + + public void set(float X, float Y) { + x = X; y = Y; + } + + /// + /// If the point is outside the rectangle specified by the two arguments, + /// it will be moved horizontally and/or vertically until it falls + /// inside the rectangle. + /// + /// Minimum values acceptable for X and Y + /// Maximum values acceptable for X and Y + /// + public void constrainTo(Point topLeft, Point bottomRight) + { + if (x < topLeft.x) + x = topLeft.x; + if (y < topLeft.y) + y = topLeft.y; + if (x > bottomRight.x) + x = bottomRight.x; + if (y > bottomRight.y) + y = bottomRight.y; + } + + public override string ToString() { + return "Point: " + x + " " + y; + } + + public Point add(Point rhs) { + return new Point(x+rhs.x, y+rhs.y); + } + + public Point subtract(Point rhs) { + return new Point(x-rhs.x, y-rhs.y); + } + + public void addToThis(Point p) + { + x += p.x; + y += p.y; + } + + public void subtractFromThis(Point p) + { + x -= p.x; + y -= p.y; + } + + public override bool Equals(object o) + { + if (o is Point) { + Point p = (Point) o; + return ((x == p.x)&&(y == p.y)); + } + + return false; + } + + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + /// + /// Returns true. + /// + public override bool isFixedSize() {return true;} + + /// + /// Returns B_POINT_TYPE. + /// + public override int typeCode() { + return TypeConstants.B_POINT_TYPE; + } + + /// + /// Returns 8 (2*sizeof(float)) + /// + public override int flattenedSize() { + return 8; + } + + public override void flatten(BinaryWriter writer) + { + writer.Write((float) x); + writer.Write((float) y); + } + + /// + /// Returns true iff (code) is B_POINT_TYPE. + /// + public override bool allowsTypeCode(int code) { + return (code == B_POINT_TYPE); + } + + public override void unflatten(BinaryReader reader, int numBytes) + { + x = reader.ReadSingle(); + y = reader.ReadSingle(); + } + } +} diff --git a/csharp/src/Rect.cs b/csharp/src/Rect.cs new file mode 100644 index 00000000..a1db95a6 --- /dev/null +++ b/csharp/src/Rect.cs @@ -0,0 +1,274 @@ +namespace muscle.support { + using System.IO; + + public class Rect : Flattenable { + public float left = 0.0f; + public float top = 0.0f; + public float right = -1.0f; + public float bottom = -1.0f; + + /// + /// Creates a rectangle with upper left point (0,0), and + /// lower right point (-1,-1). + /// Note that this rectangle has a negative area! + /// (that is, it's imaginary) + /// + public Rect() { /* empty */ } + + public Rect(float l, float t, float r, float b) {set(l,t,r,b);} + + public Rect(Rect rhs) {setEqualTo(rhs);} + + public Rect(Point leftTop, Point rightBottom) + { + set(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); + } + + public override Flattenable cloneFlat() { + return new Rect(left, top, right, bottom); + } + + public override void setEqualTo(Flattenable rhs) + { + Rect copyMe = (Rect) rhs; + set(copyMe.left, copyMe.top, copyMe.right, copyMe.bottom); + } + + public void set(float l, float t, float r, float b) + { + left = l; + top = t; + right = r; + bottom = b; + } + + public override string ToString() + { + return "Rect: leftTop=(" + left + "," + top + ") rightBottom=(" + right + "," + bottom + ")"; + } + + + public Point leftTop() + { + return new Point(left, top); + } + + public Point rightBottom() + { + return new Point(right, bottom); + } + + public Point leftBottom() + { + return new Point(left, bottom); + } + + public Point rightTop() + { + return new Point(right, top); + } + + public void setLeftTop(Point p) + { + left = p.x; top = p.y; + } + + public void setRightBottom(Point p) + { + right = p.x; bottom = p.y; + } + + public void setLeftBottom(Point p) + { + left = p.x; bottom = p.y; + } + + public void setRightTop(Point p) + { + right = p.x; top = p.y; + } + + /// + /// Makes the rectangle smaller by the amount specified in both + /// the x and y dimensions + /// + public void insetBy(Point p) {insetBy(p.x, p.y);} + + /// + /// Makes the rectangle smaller by the amount specified in + /// both the x and y dimensions + /// + public void insetBy(float dx, float dy) + { + left += dx; + top += dy; + right -= dx; + bottom -= dy; + } + + /// + /// Translates the rectangle by the amount specified in both the x and y + /// dimensions + /// + public void offsetBy(Point p) {offsetBy(p.x, p.y);} + + /// + /// Translates the rectangle by the amount specified in both + /// the x and y dimensions + /// + public void offsetBy(float dx, float dy) + { + left += dx; + top += dy; + right += dx; + bottom += dy; + } + + /// + /// Translates the rectangle so that its top left corner is at the + /// point specified. + /// + public void offsetTo(Point p) + { + offsetTo(p.x, p.y); + } + + /// + /// Translates the rectangle so that its top left corner is at + /// the point specified. + /// + public void offsetTo(float x, float y) + { + right = x + width(); + bottom = y + height(); + left = x; top = y; + } + + /// + /// Comparison Operator. Returns true iff (r)'s dimensions are + /// exactly the same as this rectangle's. + /// + public override bool Equals(object o) + { + if (o is Rect) { + Rect r = (Rect) o; + return (left == r.left) && (top == r.top) + && (right == r.right) && (bottom == r.bottom); + } + + return false; + } + + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + /// + /// Returns a rectangle whose area is the intersecting subset of + /// this rectangle's and (r)'s + /// + public Rect intersect(Rect r) + { + Rect ret = new Rect(this); + if (ret.left < r.left) + ret.left = r.left; + if (ret.right > r.right) + ret.right = r.right; + if (ret.top < r.top) + ret.top = r.top; + if (ret.bottom > r.bottom) + ret.bottom = r.bottom; + return ret; + } + + /// + /// Returns a rectangle whose area is a superset of the union of + /// this rectangle's and (r)'s + /// + public Rect unify(Rect r) + { + Rect ret = new Rect(this); + + if (r.left < ret.left) + ret.left = r.left; + if (r.right > ret.right) + ret.right = r.right; + if (r.top < ret.top) + ret.top = r.top; + if (r.bottom > ret.bottom) + ret.bottom = r.bottom; + + return ret; + } + + public bool intersects(Rect r) + { + return (r.intersect(this).isValid()); + } + + public bool isValid() + { + return ((width() >= 0.0f)&&(height() >= 0.0f)); + } + + public float width() + { + return right - left; + } + + public float height() + { + return bottom - top; + } + + public bool contains(Point p) + { + return ((p.x >= left)&&(p.x <= right)&&(p.y >= top)&&(p.y <= bottom)); + } + + public bool contains(Rect p) + { + return ((contains(p.leftTop()))&&(contains(p.rightTop()))&& + (contains(p.leftBottom()))&&(contains(p.rightBottom()))); + } + + public override bool isFixedSize() + { + return true; + } + + public override int typeCode() + { + return B_RECT_TYPE; + } + + public override int flattenedSize() + { + return 16; + } + + /// + /// Returns true only if code is B_RECT_TYPE. + /// + public override bool allowsTypeCode(int code) + { + return (code == B_RECT_TYPE); + } + + public override void flatten(BinaryWriter writer) + { + writer.Write(left); + writer.Write(top); + writer.Write(right); + writer.Write(bottom); + } + + public override void unflatten(BinaryReader reader, int numBytes) { + left = reader.ReadSingle(); + top = reader.ReadSingle(); + right = reader.ReadSingle(); + bottom = reader.ReadSingle(); + } + } +} diff --git a/csharp/src/StorageReflectConstants.cs b/csharp/src/StorageReflectConstants.cs new file mode 100644 index 00000000..a96f03f7 --- /dev/null +++ b/csharp/src/StorageReflectConstants.cs @@ -0,0 +1,515 @@ +namespace muscle.client { + + /// + /// Constants that are understood by the StorageReflectSession class + /// of the standard MUSCLE server. + /// + /// + /// See documentation or comments at the end of this file for what + /// they all mean + /// + public abstract class StorageReflectConstants + { + /// Unused dummy value ('!Pc0'), marks the beginning of the reserved command array + public const int BEGIN_PR_COMMANDS = 558916400; + + /// Adds/replaces the given fields in the parameters table + public const int PR_COMMAND_SETPARAMETERS = 558916401; + + /// Returns the current parameter set to the client + public const int PR_COMMAND_GETPARAMETERS = 558916402; + + /// deletes the parameters specified in PR_NAME_KEYS + public const int PR_COMMAND_REMOVEPARAMETERS = 558916403; + + /// Adds/replaces the given message in the data table + public const int PR_COMMAND_SETDATA = 558916404; + + /// Retrieves the given message(s) in the data table + public const int PR_COMMAND_GETDATA = 558916405; + + /// Removes the gives message(s) from the data table + public const int PR_COMMAND_REMOVEDATA = 558916406; + + /// Removes data from outgoing result messages + public const int PR_COMMAND_JETTISONRESULTS = 558916407; + + /// Insert nodes underneath a node, as an ordered list + public const int PR_COMMAND_INSERTORDEREDDATA = 558916408; + + /// Echo this message back to the sending client + public const int PR_COMMAND_PING = 558916409; + + /// Kick matching clients off the server (Requires privilege) + public const int PR_COMMAND_KICK = 558916410; + + /// Add ban patterns to the server's ban list (Requires privilege) + public const int PR_COMMAND_ADDBANS = 558916411; + + /// Remove ban patterns from the server's ban list (Requires privilege) + public const int PR_COMMAND_REMOVEBANS = 558916412; + + /// Submessages under PR_NAME_KEYS are executed in order, as if they + /// came separately + public const int PR_COMMAND_BATCH = 558916413; + + /// Server will ignore this message + public const int PR_COMMAND_NOOP = 558916414; + + /// Move one or more intries in a node index to a different spot in + /// the index + public const int PR_COMMAND_REORDERDATA = 558916415; + + /// Add require patterns to the server's require list + /// (Requires ban privilege) + public const int PR_COMMAND_ADDREQUIRES = 558916416; + + /// Remove require patterns from the server's require list + /// (Requires ban privilege) + public const int PR_COMMAND_REMOVEREQUIRES = 558916417; + + /// Reserved for future expansion + public const int PR_COMMAND_RESERVED11 = 558916418; + + /// Reserved for future expansion + public const int PR_COMMAND_RESERVED12 = 558916419; + + /// Reserved for future expansion + public const int PR_COMMAND_RESERVED13 = 558916420; + + /// Reserved for future expansion + public const int PR_COMMAND_RESERVED14 = 558916421; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED15 = 558916422; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED16 = 558916423; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED17 = 558916424; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED18 = 558916425; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED19 = 558916426; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED20 = 558916427; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED21 = 558916428; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED22 = 558916429; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED23 = 558916430; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED24 = 558916431; + + /// Reserved for future expansion */ + public const int PR_COMMAND_RESERVED25 = 558916432; + + /// Dummy value to indicate the end of the reserved command range + public const int END_PR_COMMANDS = 558916433; + + /// Marks beginning of range of 'what' codes that may be generated + /// by the StorageReflectSession and sent back to the client + public const int BEGIN_PR_RESULTS = 558920240; + + /// Sent to client in response to PR_COMMAND_GETPARAMETERS + public const int PR_RESULT_PARAMETERS = 558920241; + + /// Sent to client in response to PR_COMMAND_GETDATA, or subscriptions + public const int PR_RESULT_DATAITEMS = 558920242; + + /// Sent to client to tell him that we don't know how to process + /// his request message + public const int PR_RESULT_ERRORUNIMPLEMENTED = 558920243; + + /// Notification that an entry has been inserted into an ordered index + public const int PR_RESULT_INDEXUPDATED = 558920244; + + /// Response from a PR_COMMAND_PING message */ + public const int PR_RESULT_PONG = 558920245; + + /// Your client isn't allowed to do something it tried to do + public const int PR_RESULT_ERRORACCESSDENIED = 558920246; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED4 = 558920247; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED5 = 558920248; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED6 = 558920249; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED7 = 558920250; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED8 = 558920251; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED9 = 558920252; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED10 = 558920253; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED11 = 558920254; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED12 = 558920255; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED13 = 558920256; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED14 = 558920257; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED15 = 558920258; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED16 = 558920259; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED17 = 558920260; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED18 = 558920261; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED19 = 558920262; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED20 = 558920263; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED21 = 558920264; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED22 = 558920265; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED23 = 558920266; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED24 = 558920267; + + /// Reserved for future expansion + public const int PR_RESULT_RESERVED25 = 558920268; + + /// Reserved for future expansion + public const int END_PR_RESULTS = 558920269; + + /// Privilege bit, indicates that the client is allowed to kick + /// other clients off the MUSCLE server + public const int PR_PRIVILEGE_KICK = 0; + + /// Privilege bit, indicates that the client is allowed to ban + /// other clients from the MUSCLE server + public const int PR_PRIVILEGE_ADDBANS = 1; + + /// Privilege bit, indicates that the client is allowed to unban + /// other clients from the MUSCLE server + public const int PR_PRIVILEGE_REMOVEBANS = 2; + + /// Number of defined privilege bits + public const int PR_NUM_PRIVILEGES = 3; + + /// Op-code that indicates that an entry was inserted at the + /// given slot index, with the given ID + public const char INDEX_OP_ENTRYINSERTED = 'i'; + + /// Op-code that indicates that an entry was removed from the + /// given slot index, had the given ID + public const char INDEX_OP_ENTRYREMOVED = 'r'; + + /// Op-code that indicates that the index was cleared */ + public const char INDEX_OP_CLEARED = 'c'; + + /// Field name for key-strings (often node paths or regex expressions) + public const string PR_NAME_KEYS = "!SnKy"; + + /// Field name to contains node path strings of removed data items + public const string PR_NAME_REMOVED_DATAITEMS = "!SnRd"; + + /// Field name (any type): If present in a PR_COMMAND_SETPARAMETERS + /// message, disables inital-value-send from new subscriptions + public const string PR_NAME_SUBSCRIBE_QUIETLY = "!SnQs"; + + /// Field name (any type): If present in a PR_COMMAND_SETDATA message, + /// subscribers won't be notified about the change. + public const string PR_NAME_SET_QUIETLY = "!SnQ2"; + + /// Field name (any type): If present in a PR_COMMAND_REMOVEDATA + /// message, subscribers won't be notified about the change. + public const string PR_NAME_REMOVE_QUIETLY = "!SnQ3"; + + // Parameter name holding int32 of MUSCLE_MESSAGE_ENCODING_* used to + // send to client + public const string PR_NAME_REPLY_ENCODING = "!Enc"; + + /// Field name (any type): If set as parameter, include ourself + /// in wildcard matches + public const string PR_NAME_REFLECT_TO_SELF = "!Self"; + + /// Field name (any type): If set as a parameter, disable all + /// subscription update messaging. + public const string PR_NAME_DISABLE_SUBSCRIPTIONS = "!Dsub"; + + /// Field name of int parameter; sets max # of items per + /// PR_RESULT_DATAITEMS message + public const string PR_NAME_MAX_UPDATE_MESSAGE_ITEMS = "!MxUp"; + + /// Field name of string returned in parameter set; + /// contains this session's /host/sessionID path + public const string PR_NAME_SESSION_ROOT = "!Root"; + + /// Field name for Message: In PR_RESULT_ERROR_* messages, + /// holds the client's message that failed to execute. + public const string PR_NAME_REJECTED_MESSAGE = "!Rjct"; + + /// Field name of int32 bitchord of client's PR_PRIVILEGE_* bits. + public const string PR_NAME_PRIVILEGE_BITS = "!Priv"; + + /// Field name of int64 indicating how many more bytes are available + /// for MUSCLE server to use + public const string PR_NAME_SERVER_MEM_AVAILABLE = "!Mav"; + + /// Field name of int64 indicating how many bytes + /// the MUSCLE server currently has allocated */ + public const string PR_NAME_SERVER_MEM_USED = "!Mus"; + + /// Field name of int64 indicating how the maximum number of bytes + /// the MUSCLE server may have allocated at once. + public const string PR_NAME_SERVER_MEM_MAX = "!Mmx"; + + /// Field name of string indicating version of MUSCLE that the server + /// was compiled from + public const string PR_NAME_SERVER_VERSION = "!Msv"; + + /// Field name of int64 indicating how many microseconds have + /// elapsed since the server was started. + public const string PR_NAME_SERVER_UPTIME = "!Mup"; + + /// Field name of int32 indicating how many database nodes may be + /// uploaded by this client (total). + public const string PR_NAME_MAX_NODES_PER_SESSION = "!Mns"; + + /// Field name of a string that the server will replace with the + /// session ID string of your session in any outgoing + /// client-to-client messages. + public const string PR_NAME_SESSION = "session"; + + /// this field name's submessage is the payload of the current node + /// in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + public const string PR_NAME_NODEDATA = "data"; + + /// this field name's submessage represents the children of the + /// current node (recursive) in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + public const string PR_NAME_NODECHILDREN = "kids"; + + /// this field name's submessage represents the index of the current + /// node in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + public const string PR_NAME_NODEINDEX = "index"; + } +} + + // This is a specialization of AbstractReflectSession that adds several + // useful capabilities to the Reflect Server. Abilities include: + // - Messages can specify (via wildcard path matching) which other + // clients they should be reflected to. If the message doesn't specify + // a path, then the session's default path can be used. + // - Clients can upload and store data on the server (in the server's RAM only). + // This data will remain accessible to all sessions until the client disconnects. + // - Clients can "subscribe" to server state information and be automatically + // notified when the information has changed. + // + // CLIENT-TO-SERVER MESSAGE FORMAT SPECIFICATION: + // + // if 'what' is PR_COMMAND_SETPARAMETERS: + // All fields of the message are placed into the session's parameter set. + // Any fields in the previous parameters set with matching names are replaced. + // Currently parsed parameter names are as follows: + // + // "SUBSCRIBE:" : Any parameter name that begins with the prefix SUBSCRIBE: + // is taken to represent a request to monitor the contents of + // all nodes whose pathnames match the path that follows. So, + // for example, the parameter name + // + // SUBSCRIBE:/*/*/Joe + // + // Indicates a request to watch all nodes with paths that match + // the regular expression /*/*/Joe. The value + // these parameters are unimportant, and are not looked at. + // Thus, these parameters may be of any type. Once a SUBSCRIBE + // parameter has been added, any data nodes that match the specified + // path will be returned immediately to the client in a PR_RESULT_DATAITEMS + // message. Furthermore, any time these nodes are modified or deleted, + // or any time a new node is added that matches the path, another + // PR_RESULT_DATAITEMS message will be sent to notify the client of + // the change. + // + // PR_NAME_KEYS : If set, any non-"special" messages without a + // PR_NAME_KEYS field will be reflected to clients + // who match at least one of the set of key-paths + // listed here. (Should be one or more string values) + // + // PR_NAME_REFLECT_TO_SELF : If set, wildcard matches can match the current session. + // Otherwise, wildcard matches with the current session will + // be suppressed (on the grounds that your client already knows + // the value of anything that it uploaded, and doesn't need to + // be reminded of it). This field may be of any type, only + // its existence/non-existence is relevant. + // + // + // if 'what' is PR_COMMAND_GETPARAMETERS: + // Causes a PR_RESULT_PARAMETERS message to be returned to the client. The returned message + // contains the entire current parameter set. + // + // if 'what' is PR_COMMAND_REMOVEPARAMETERS: + // The session looks for PR_NAME_KEYS string entrys. For each string found + // under this entry, any matching field in the parameters message are deleted. + // Wildcards are permitted in these strings. (So e.g. "*" would remove ALL parameters) + // + // if 'what' is PR_COMMAND_SETDATA: + // Scans the message for all fields of type message. Each message field + // should contain only one message. The field's name is parsed as a local + // key-path of the data item (e.g. "myData", or "imageInfo/colors/red"). + // Each contained message will be stored in the local session's data tree under + // that key path. (Note: fields that start with a '/' are not allowed, and + // will be ignored!) + // + // if 'what' is PR_COMMAND_REMOVEDATA: + // Removes all data nodes that match the path(s) in the PR_NAME_KEYS string field. + // Paths should be specified relative to this session's root node (i.e. they should + // not start with a slash) + // + // if 'what' is PR_COMMAND_GETDATA: + // The session looks for one or more strings in the PR_NAME_KEYS field. Each + // string represents a key-path indicating which information the client is + // interested in retrieving. If there is no leading slash, "/*/*/" will be + // implicitely prepended. Here are some valid example key-paths: + // /*/*/color (gets "color" from all hostnames, all session IDs) + // /joe.blow.com/*/shape (gets "shape" from all sessions connected from joe.blow.com) + // /joe.blow.com/19435935093/sound (gets "sound" from a single session) + // /*/*/vehicleTypes/* (gets all vehicle types from all clients) + // j* (equivalent to "/*/*/j*") + // shape/* (equivalent to "/*/*/shape/*") + // The union of all the sets of matching data nodes specified by these paths will be + // added to a single PR_RESULT_DATAITEMS message which is then passed back to the client. + // Each matching message is added with its full path as a field name. + // + // if 'what' is PR_COMMAND_INSERTORDEREDDATA: + // The session looks for one or more messages in the PR_NAME_KEYS field. Each + // string represents a wildpath, rooted at this session's node (read: no leading + // slash should be present) that specifies zero or more data nodes to insert ordered/ + // indexed children under. Each node in the union of these node sets will have new + // ordered/indexed child nodes created underneath it. The names of these new child + // nodes will be chosen algorithmically by the server. There will be one child node + // created for each sub-message in this message. Sub-messages may be added under any + // field name; if the field name happens to be the name of a currently indexed child, + // the new message node will be be inserted *before* the specified child in the index. + // Otherwise, it will be appended to the end of the index. Clients who have subscribed + // to the specified nodes will see the updates to the index; clients who have subscribed + // to the children will get updates of the actual data as well. + // + // if 'what' is PR_COMMAND_PING: + // The session will change the message's 'what' code to PR_RESULT_PONG, and send + // it right back to the client. In this way, the client can find out (a) that + // the server is still alive, (b) how long it takes the server to respond, and + // (c) that any previously sent operations have finished. + // + // if 'what' is PR_COMMAND_KICK: + // The server will look for one or more strings in the PR_NAME_KEYS field. It will + // do a search of the database for any nodes matching one or more of the node paths + // specified by these strings, and will kick any session with matching nodes off + // of the server. Of course, this will only be done if the client who sent the + // PR_COMMAND_KICK field has PR_PRIVILEGE_KICK access. + // + // if 'what' is PR_COMMAND_ADDBANS: + // The server will look for one or more strings in the PR_NAME_KEYS field. Any + // strings that are found will be added to the server's "banned IP list", and + // subsequent connection attempts from IP addresses matching any of these ban strings + // will be denied. Of course, this will only be done if the client who sent the + // PR_COMMAND_ADDBANS field has PR_PRIVILEGE_ADDBANS access. + // + // if 'what' is PR_COMMAND_REMOVEBANS: + // The server will look for one or more strings in the PR_NAME_KEYS field. Any + // strings that are found will be used a pattern-matching strings against the + // current set of "ban" patterns. Any "ban" patterns that are matched by any + // of the PR_NAME_KEYS strings will be removed from the "banned IP patterns" set, + // so that IP addresses who matched those patterns will be able to connect to + // the server again. Of course, this will only be done if the client who sent the + // PR_COMMAND_REMOVEBANS field has PR_PRIVILEGE_REMOVEBANS access. + // + // if 'what' is PR_COMMAND_RESERVED_*: + // The server will change the 'what' code of your message to PR_RESULT_UNIMPLEMENTED, + // and send it back to your client. + // + // if 'what' is PR_RESULT_*: + // The message will be silently dropped. You are not allowed to send PR_RESULT_(*) + // messages to the server, and should be very ashamed of yourself for even thinking + // about it. + // + // All other 'what' codes + // Messages with other 'what' codes are simply reflected to other clients verbatim. + // If a PR_NAME_KEYS string field is found in the message, then it will be parsed as a + // set of key-paths, and only other clients who can match at least one of these key-paths + // will receive the message. If no PR_NAME_KEYS field is found, then the parameter + // named PR_NAME_KEYS will be used as a default value. If that parameter isn't + // found, then the message will be reflected to all clients (except this one). + // + // SERVER-TO-CLIENT MESSAGE FORMAT SPECIFICATION: + // + // if 'what' is PR_RESULT_PARAMETERS: + // The message contains the complete parameter set that is currently associated with + // this client on the server. Parameters may have any field name, and be of any type. + // Certain parameter names are recognized by the StorageReflectSession as having special + // meaning; see the documentation on PR_COMMAND_SETPARAMETERS for information about these. + // + // if 'what' is PR_RESULT_DATAITEMS: + // The message contains information about data that is stored on the server. All stored + // data is stored in the form of Messages. Thus, all data in this message will be + // in the form of a message field, with the field's name being the fully qualified path + // of the node it represents (e.g. "/my.computer.com/5/MyNodeName") and the value being + // the stored data itself. Occasionally it is necessary to inform the client that a data + // node has been deleted; this is done by adding the deceased node's path name as a string + // to the PR_NAME_REMOVED_DATAITEM field. If multiple nodes were removed, there may be + // more than one string present in the PR_NAME_REMOVED_DATAITEM field. + // + // if 'what' is PR_RESULT_INDEXUPDATED: + // The message contains information about index entries that were added (via PR_COMMAND_INSERTORDERREDDATA) + // to a node that the client is subscribed to. Each entry's field name is the fully qualified + // path of a subscribed node, and the value(s) are strings of this format: "%c%lu:%s", %c is + // a single character that is one of the INDEX_OP_* values, the %lu is an index the item was added to + // or removed from, and %s is the key string for the child in question. + // Note that there may be more than one value per field! + // + // if 'what' is PR_RESULT_ERRORUNIMPLEMENTED: + // Your message is being returned to you because it tried to use functionality that + // hasn't been implemented on the server side. This usually happens if you are trying + // to use a new MUSCLE feature with an old MUSCLE server. + // + // if 'what' is PR_RESULT_PONG: + // Your PR_COMMAND_PING message has been returned to you as a PR_RESULT_PONG message. + // + // if 'what' is PR_RESULT_ERRORACCESSDENIED: + // You tried to do something that you don't have permission to do (such as kick, ban, + // or unban another user). + // + // if 'what' is anything else: + // This message was reflected to your client by a neighboring client session. The content + // of the message is not specified by the StorageReflectSession; it just passes any message + // on verbatim. + \ No newline at end of file diff --git a/csharp/src/TypeConstants.cs b/csharp/src/TypeConstants.cs new file mode 100644 index 00000000..18295858 --- /dev/null +++ b/csharp/src/TypeConstants.cs @@ -0,0 +1,51 @@ +namespace muscle.support { + public abstract class TypeConstants { + // 'ANYT', // wild card + public const int B_ANY_TYPE = 1095653716; + + // 'BOOL', + public const int B_BOOL_TYPE = 1112493900; + + // 'DBLE', + public const int B_DOUBLE_TYPE = 1145195589; + + // 'FLOT', + public const int B_FLOAT_TYPE = 1179406164; + + // 'LLNG', // a.k.a. long in C# + public const int B_INT64_TYPE = 1280069191; + + // 'LONG', // a.k.a. int in C# + public const int B_INT32_TYPE = 1280265799; + + // 'SHRT', // a.k.a. short in C# + public const int B_INT16_TYPE = 1397248596; + + // 'BYTE', // a.k.a. byte in C# + public const int B_INT8_TYPE = 1113150533; + + // 'MSGG', + public const int B_MESSAGE_TYPE = 1297303367; + + // 'PNTR', // parsed as int in C# (but not very useful) + public const int B_POINTER_TYPE = 1347310674; + + // 'BPNT', // muscle.support.Point in C# + public const int B_POINT_TYPE = 1112559188; + + // 'RECT', // muscle.support.Rect in C# + public const int B_RECT_TYPE = 1380270932; + + // 'CSTR', // C# string + public const int B_STRING_TYPE = 1129534546; + + // 'OPTR', + public const int B_OBJECT_TYPE = 1330664530; + + // 'RAWT', + public const int B_RAW_TYPE = 1380013908; + + // 'MIME', + public const int B_MIME_TYPE = 1296649541; + } +} diff --git a/csharp/src/UnflattenFormatException.cs b/csharp/src/UnflattenFormatException.cs new file mode 100644 index 00000000..135f9710 --- /dev/null +++ b/csharp/src/UnflattenFormatException.cs @@ -0,0 +1,13 @@ +namespace muscle.support { + using System.IO; + + /// + /// Exception that is thrown when an unflatten() attempt fails, usually + /// due to unrecognized data in the input stream, or a type mismatch. + public class UnflattenFormatException : IOException { + public UnflattenFormatException() : + base("unexpected bytes during Flattenable unflatten") { } + + public UnflattenFormatException(string s) : base(s) { } + } +} diff --git a/csharp/test/ExampleClient.cs b/csharp/test/ExampleClient.cs new file mode 100644 index 00000000..28a1b109 --- /dev/null +++ b/csharp/test/ExampleClient.cs @@ -0,0 +1,82 @@ +namespace muscle.test { + using System; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Text; + using System.Collections; + using System.Diagnostics; + using System.Threading; + + using muscle.message; + using muscle.client; + using muscle.iogateway; + + public class ExampleClient { + static void Main() { + string [] args = Environment.GetCommandLineArgs(); + + string host = "127.0.0.1"; + int port = 2960; + + if (args.Length >= 2) + host = args[1]; + + if (args.Length >= 3) + port = System.Int32.Parse(args[2]); + + new ExampleClient(host, port); + } + + public ExampleClient(string hostName, int port) + { + Client client = new Client(hostName, port); + + client.RegisterForMessages(new MessagesCallback(MessagesCallback), null); + client.RegisterForDisconnect(new DisconnectCallback(DisconnectCallback), + null); + + client.BeginConnect(new AsyncCallback(this.ConnectCallback), client); + + Message m1 = new Message(); + + m1.what = StorageReflectConstants.PR_COMMAND_SETPARAMETERS; + m1.setBoolean("SUBSCRIBE:*", true); + m1.setBoolean("SUBSCRIBE:*/*", true); + m1.setBoolean("SUBSCRIBE:*/*/*", true); + m1.setBoolean("SUBSCRIBE:*/*/*/*", true); + m1.setBoolean("SUBSCRIBE:*/*/*/*/*", true); + + client.Send(m1); + + while (true) { + Thread.Sleep(5000); + } + } + + public void ConnectCallback(IAsyncResult ar) + { + Client client = (Client) ar.AsyncState; + client.EndConnect(ar); + } + + public void DisconnectCallback(Client client, + Exception err, + object state) + { + Console.WriteLine(err.ToString()); + Console.WriteLine("disconnected"); + + } + + public void MessagesCallback(Message [] messages, + Client client, + object state) + { + foreach (Message message in messages) { + System.Console.WriteLine(message.ToString()); + } + } + + } +} diff --git a/csharp/test/TestDecoding.cs b/csharp/test/TestDecoding.cs new file mode 100644 index 00000000..ce748812 --- /dev/null +++ b/csharp/test/TestDecoding.cs @@ -0,0 +1,50 @@ +namespace muscle.test { + using System; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Text; + using System.Collections; + using System.Diagnostics; + using System.Threading; + + using muscle.message; + using muscle.client; + using muscle.iogateway; + + public class TestDecoding : StorageReflectConstants { + const int MESSAGE_COUNT = 50000; + + static void Main() { + MessageIOGateway gw = new MessageIOGateway(); + Client.MessageDecoder d = new Client.MessageDecoder(); + Client.MessageEncoder e = new Client.MessageEncoder(); + + Message m = new Message(); + + m.what = PR_COMMAND_GETDATA; + string text = "*"; + m.setString(PR_NAME_KEYS, text); + + e.Encode(m); + + byte [] buffer = e.GetAndResetBuffer(); + + long start = DateTime.Now.Ticks; + + for (int i = 0; i < MESSAGE_COUNT; ++i) { + d.Decode(buffer, buffer.Length); + } + + long end = DateTime.Now.Ticks; + + long elapsed = end - start; + + DateTime t = new DateTime(elapsed); + + Console.WriteLine("Elapsed time: " + t.ToString()); + Console.WriteLine("Messages flattened and unflattened: " + MESSAGE_COUNT.ToString()); + + } + } +} diff --git a/dataio/AsyncDataIO.cpp b/dataio/AsyncDataIO.cpp new file mode 100644 index 00000000..f769d225 --- /dev/null +++ b/dataio/AsyncDataIO.cpp @@ -0,0 +1,219 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/AsyncDataIO.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +namespace muscle { + +AsyncDataIO :: ~AsyncDataIO() +{ + ShutdownInternalThread(); +} + +int32 AsyncDataIO :: Read(void * buffer, uint32 size) +{ + if (IsInternalThreadRunning() == false) {LogTime(MUSCLE_LOG_ERROR, "StartInternalThread() must be called before calling AsyncDataIO::Read()!\n"); return -1;} + return ReceiveData(GetOwnerWakeupSocket(), buffer, size, false); +} + +int32 AsyncDataIO :: Write(const void * buffer, uint32 size) +{ + if (IsInternalThreadRunning() == false) {LogTime(MUSCLE_LOG_ERROR, "StartInternalThread() must be called before calling AsyncDataIO::Write()!\n"); return -1;} + int32 ret = SendData(GetOwnerWakeupSocket(), buffer, size, false); + if (ret > 0) _mainThreadBytesWritten += ret; // we count the bytes written, so that Seek()/Flush()/Shutdown() commands can be re-spliced into the stream later if necessary + return ret; +} + +status_t AsyncDataIO :: Seek(int64 offset, int whence) +{ + if (IsInternalThreadRunning() == false) {LogTime(MUSCLE_LOG_ERROR, "StartInternalThread() must be called before calling AsyncDataIO::Seek()!\n"); return B_ERROR;} + + status_t ret = B_ERROR; + if (_asyncCommandsMutex.Lock() == B_NO_ERROR) + { + ret = _asyncCommands.AddTail(AsyncCommand(_mainThreadBytesWritten, ASYNC_COMMAND_SEEK, offset, whence)); + _asyncCommandsMutex.Unlock(); + if (ret == B_NO_ERROR) NotifyInternalThread(); + } + return ret; +} + +void AsyncDataIO :: FlushOutput() +{ + if (IsInternalThreadRunning()) + { + if (_asyncCommandsMutex.Lock() == B_NO_ERROR) + { + status_t ret = _asyncCommands.AddTail(AsyncCommand(_mainThreadBytesWritten, ASYNC_COMMAND_FLUSH)); + _asyncCommandsMutex.Unlock(); + if (ret == B_NO_ERROR) NotifyInternalThread(); + } + } + else LogTime(MUSCLE_LOG_ERROR, "StartInternalThread() must be called before calling AsyncDataIO::FlushOutput()!\n"); +} + +void AsyncDataIO :: Shutdown() +{ + if (IsInternalThreadRunning()) + { + if (_asyncCommandsMutex.Lock() == B_NO_ERROR) + { + status_t ret = _asyncCommands.AddTail(AsyncCommand(_mainThreadBytesWritten, ASYNC_COMMAND_SHUTDOWN)); + _asyncCommandsMutex.Unlock(); + if (ret == B_NO_ERROR) NotifyInternalThread(); + } + } + else if (_slaveIO()) + { + _slaveIO()->Shutdown(); + _slaveIO.Reset(); + } +} + +status_t AsyncDataIO :: StartInternalThread() +{ + if (CreateConnectedSocketPair(_mainThreadNotifySocket, _ioThreadNotifySocket) != B_NO_ERROR) return B_ERROR; + return Thread::StartInternalThread(); +} + +void AsyncDataIO :: NotifyInternalThread() +{ + char buf = 'j'; + (void) SendData(_mainThreadNotifySocket, &buf, sizeof(buf), false); +} + +void AsyncDataIO :: InternalThreadEntry() +{ + bool exitWhenDoneWriting = false; + bool keepGoing = true; + uint64 ioThreadBytesWritten = 0; + AsyncCommand curCmd; + + char fromMainThreadBuf[4096]; + uint32 fromMainThreadBufReadIdx = 0; // which byte to read out of (fromMainThreadBuf) next + uint32 fromMainThreadBufNumValid = 0; // how many bytes are currently in (fromMainThreadBuf) (including already-read ones) + + char fromSlaveIOBuf[4096]; + uint32 fromSlaveIOBufReadIdx = 0; // which byte to read out of (fromSlaveIOBuf) next + uint32 fromSlaveIOBufNumValid = 0; // how many bytes are currently in (fromSlaveIOBuf) (including already-read ones) + + SocketMultiplexer multiplexer; + while(keepGoing) + { + int slaveReadFD = _slaveIO()?_slaveIO()->GetReadSelectSocket().GetFileDescriptor():-1; + int slaveWriteFD = _slaveIO()?_slaveIO()->GetWriteSelectSocket().GetFileDescriptor():-1; + int fromMainFD = GetInternalThreadWakeupSocket().GetFileDescriptor(); + int notifyFD = _ioThreadNotifySocket.GetFileDescriptor(); + + if ((slaveReadFD >= 0)&&(fromSlaveIOBufNumValid < sizeof(fromSlaveIOBuf))) multiplexer.RegisterSocketForReadReady(slaveReadFD); + if ((slaveWriteFD >= 0)&&(fromMainThreadBufNumValid > fromMainThreadBufReadIdx)) multiplexer.RegisterSocketForWriteReady(slaveWriteFD); + + if (fromMainFD >= 0) + { + if (fromMainThreadBufNumValid < sizeof(fromMainThreadBuf)) multiplexer.RegisterSocketForReadReady(fromMainFD); + if (fromSlaveIOBufNumValid > fromSlaveIOBufReadIdx) multiplexer.RegisterSocketForWriteReady(fromMainFD); + } + if (notifyFD >= 0) multiplexer.RegisterSocketForReadReady(notifyFD); // always be on the lookout for notifications... + + // we block here, waiting for data availability + if (multiplexer.WaitForEvents() < 0) break; + + // All the notify socket needs to do is make select() return. We just read the junk notify-bytes and ignore them. + char junk[128]; if ((notifyFD >= 0)&&(multiplexer.IsSocketReadyForRead(notifyFD))) (void) ReceiveData(_ioThreadNotifySocket, junk, sizeof(junk), false); + + // Determine how many bytes until the next command in the output stream (we want them to be executed at the same point + // in the I/O thread's output stream as they were called at in the main thread's output stream) + uint32 bytesUntilNextCommand = MUSCLE_NO_LIMIT; + if (_asyncCommandsMutex.Lock() == B_NO_ERROR) + { + if (_asyncCommands.HasItems()) + { + const AsyncCommand & nextCmd = _asyncCommands.Head(); + if (nextCmd.GetStreamLocation() <= ioThreadBytesWritten) + { + bytesUntilNextCommand = 0; + (void) _asyncCommands.RemoveHead(curCmd); + } + else bytesUntilNextCommand = (uint32) (nextCmd.GetStreamLocation()-ioThreadBytesWritten); + } + else if ((exitWhenDoneWriting)&&(fromMainThreadBufReadIdx == fromMainThreadBufNumValid)) keepGoing = false; + _asyncCommandsMutex.Unlock(); + } + + if (bytesUntilNextCommand > 0) + { + // Read the data from the slave FD, into our from-slave buffer + if ((slaveReadFD >= 0)&&(fromSlaveIOBufNumValid < sizeof(fromSlaveIOBuf))&&(multiplexer.IsSocketReadyForRead(slaveReadFD))) + { + int32 bytesRead = SlaveRead(&fromSlaveIOBuf[fromSlaveIOBufNumValid], sizeof(fromSlaveIOBuf)-fromSlaveIOBufNumValid); + if (bytesRead >= 0) fromSlaveIOBufNumValid += bytesRead; + else break; + } + + if (slaveWriteFD >= 0) + { + // Write the data from our from-main-thread buffer, to our slave I/O + uint32 bytesToWriteToSlave = muscleMin(bytesUntilNextCommand, fromMainThreadBufNumValid-fromMainThreadBufReadIdx); + if ((bytesToWriteToSlave > 0)&&(multiplexer.IsSocketReadyForWrite(slaveWriteFD))) + { + int32 bytesWritten = SlaveWrite(&fromMainThreadBuf[fromMainThreadBufReadIdx], bytesToWriteToSlave); + if (bytesWritten >= 0) + { + ioThreadBytesWritten += bytesWritten; + fromMainThreadBufReadIdx += bytesWritten; + if (fromMainThreadBufReadIdx == fromMainThreadBufNumValid) fromMainThreadBufReadIdx = fromMainThreadBufNumValid = 0; + } + else break; + } + if ((fromMainThreadBufNumValid == fromMainThreadBufReadIdx)&&(exitWhenDoneWriting)) break; + } + + if (fromMainFD >= 0) + { + // Read the data from the main thread's socket, into our from-main-thread buffer + if ((fromMainThreadBufNumValid < sizeof(fromMainThreadBuf))&&(multiplexer.IsSocketReadyForRead(fromMainFD))) + { + int32 bytesRead = ReceiveData(GetInternalThreadWakeupSocket(), &fromMainThreadBuf[fromMainThreadBufNumValid], sizeof(fromMainThreadBuf)-fromMainThreadBufNumValid, false); + if (bytesRead >= 0) fromMainThreadBufNumValid += bytesRead; + else exitWhenDoneWriting = true; + } + + // Write the data from our from-slave-IO buffer, to the main thread's socket + if ((fromSlaveIOBufReadIdx < fromSlaveIOBufNumValid)&&(multiplexer.IsSocketReadyForWrite(fromMainFD))) + { + int32 bytesWritten = SendData(GetInternalThreadWakeupSocket(), &fromSlaveIOBuf[fromSlaveIOBufReadIdx], fromSlaveIOBufNumValid-fromSlaveIOBufReadIdx, false); + if (bytesWritten >= 0) + { + fromSlaveIOBufReadIdx += bytesWritten; + if (fromSlaveIOBufReadIdx == fromSlaveIOBufNumValid) fromSlaveIOBufReadIdx = fromSlaveIOBufNumValid = 0; + } + else break; + } + } + } + else + { + switch(curCmd.GetCommand()) + { + case ASYNC_COMMAND_SEEK: + if (_slaveIO()) _slaveIO()->Seek(curCmd.GetOffset(), curCmd.GetWhence()); + break; + + case ASYNC_COMMAND_FLUSH: + if (_slaveIO()) _slaveIO()->FlushOutput(); + break; + + case ASYNC_COMMAND_SHUTDOWN: + if (_slaveIO()) _slaveIO()->Shutdown(); + break; + + default: + LogTime(MUSCLE_LOG_ERROR, "AsyncDataIO: Unknown ASYNC_COMMAND code %u\n", curCmd.GetCommand()); + break; + } + } + } +} + +}; // end namespace muscle diff --git a/dataio/AsyncDataIO.h b/dataio/AsyncDataIO.h new file mode 100644 index 00000000..7b2d634c --- /dev/null +++ b/dataio/AsyncDataIO.h @@ -0,0 +1,99 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAsyncDataIO_h +#define MuscleAsyncDataIO_h + +#include "dataio/DataIO.h" +#include "system/Thread.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +/** + * This class can be used to "wrap" a streaming I/O object (e.g. a FileDataIO) in order to make + * it appear completely non-blocking (even if the wrapped DataIO requires blocking I/O operations). + * It does this by handing the file I/O operations off to a separate internal thread, so that the main + * thread will never block. + */ +class AsyncDataIO : public DataIO, private Thread, private CountedObject +{ +public: + /** + * Constructor. + * @param slaveIO The underlying streaming DataIO object to pass calls on through to. + * Note that this I/O object will be operated on from a separate thread, + * and therefore should generally not be accessed further from the main thread, + * to avoid race conditions. + */ + AsyncDataIO(const DataIORef & slaveIO) : _slaveIO(slaveIO), _mainThreadBytesWritten(0) {/* empty */} + virtual ~AsyncDataIO(); + + /** This must be called before using the AsyncDataIO object! */ + virtual status_t StartInternalThread(); + + virtual int32 Read(void * buffer, uint32 size); + virtual int32 Write(const void * buffer, uint32 size); + virtual status_t Seek(int64 offset, int whence); + + /** AsyncDataIO::GetPosition() always returns -1, since the current position of the I/O is not well-defined outside of the internal I/O thread. */ + virtual int64 GetPosition() const {return -1;} + + /** Will tell the I/O thread to flush, asynchronously. */ + virtual void FlushOutput(); + + /** Will tell the I/O thread to shut down its I/O, asynchronously. */ + virtual void Shutdown(); + + virtual const ConstSocketRef & GetReadSelectSocket() const {return const_cast(*this).GetOwnerWakeupSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return const_cast(*this).GetOwnerWakeupSocket();} + + /** AsyncDataIO::GetLength() always returns -1, since the current length of the I/O is not well-defined outside of the internal I/O thread. */ + virtual int64 GetLength() {return -1;} + + /** Returns a reference to our held slave I/O object. Use with caution, since this object may currently be in use by the internal thread! */ + const DataIORef & GetSlaveIO() const {return _slaveIO;} + +protected: + virtual void InternalThreadEntry(); + +private: + int32 SlaveRead(void * buffer, uint32 size) {return _slaveIO() ? _slaveIO()->Read(buffer, size) : -1;} + int32 SlaveWrite(const void * buffer, uint32 size) {return _slaveIO() ? _slaveIO()->Write(buffer, size) : -1;} + void NotifyInternalThread(); + + enum { + ASYNC_COMMAND_SEEK = 0, + ASYNC_COMMAND_FLUSH, + ASYNC_COMMAND_SHUTDOWN, + NUM_ASYNC_COMMANDS + }; + + class AsyncCommand + { + public: + AsyncCommand() : _streamLocation(0), _offset(0), _whence(0), _cmd(NUM_ASYNC_COMMANDS) {/* empty */} + AsyncCommand(uint64 streamLocation, uint8 cmd) : _streamLocation(streamLocation), _offset(0), _whence(0), _cmd(cmd) {/* empty */} + AsyncCommand(uint64 streamLocation, uint8 cmd, int64 offset, int whence) : _streamLocation(streamLocation), _offset(offset), _whence(whence), _cmd(cmd) {/* empty */} + + uint64 GetStreamLocation() const {return _streamLocation;} + uint8 GetCommand() const {return _cmd;} + int64 GetOffset() const {return _offset;} + int GetWhence() const {return _whence;} + + private: + uint64 _streamLocation; + int64 _offset; + int _whence; + uint8 _cmd; + }; + + ConstSocketRef _mainThreadNotifySocket, _ioThreadNotifySocket; + DataIORef _slaveIO; + uint64 _mainThreadBytesWritten; + Mutex _asyncCommandsMutex; + Queue _asyncCommands; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/ByteBufferDataIO.h b/dataio/ByteBufferDataIO.h new file mode 100644 index 00000000..e3afb134 --- /dev/null +++ b/dataio/ByteBufferDataIO.h @@ -0,0 +1,126 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleByteBufferDataIO_h +#define MuscleByteBufferDataIO_h + +#include "dataio/DataIO.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +/** + * Data I/O class to allow reading from/writing to a ByteBuffer object (as if it was an I/O device) + * The ByteBuffer will behave much like a file would (automatically being resized larger when you + * Write() past the end of it, etc), except of course it's all in memory, not on disk. + */ +class ByteBufferDataIO : public DataIO, private CountedObject +{ +public: + /** Constructor. + * @param buf Reference to the byte buffer to read from. If not specified (or specified + * as a NULL reference), you will need to call SetBuffer() before using this ByteBufferDataIO. + */ + ByteBufferDataIO(const ByteBufferRef & buf = ByteBufferRef()) : _buf(buf), _seekPos(0) {/* empty */} + + /** Virtual Destructor, to keep C++ honest */ + virtual ~ByteBufferDataIO() {/* empty */} + + /** Sets our held buffer to a different value. Note that this call will not change the seek + * position of this ByteBufferDataIO, so you may want to call Seek() also. + */ + void SetBuffer(const ByteBufferRef & buf) {_buf = buf;} + + /** Returns the current byte buffer reference we are using. */ + const ByteBufferRef & GetBuffer() const {return _buf;} + + /** + * Copies bytes from our ByteBuffer into (buffer). If we have no buffer, returns -1. + * @param buffer Points to a buffer to read bytes into. + * @param size Number of bytes in the buffer. + * @return zero. + */ + virtual int32 Read(void * buffer, uint32 size) + { + if (_buf()) + { + int32 copyBytes = muscleMin((int32)size, muscleMax((int32)0, (int32)(_buf()->GetNumBytes()-_seekPos))); + memcpy(buffer, _buf()->GetBuffer()+_seekPos, copyBytes); + _seekPos += copyBytes; + return copyBytes; + } + return -1; + } + + /** + * Writes bytes into our write buffer. If we have no write buffer, or we cannot allocate more memory for the write buffer, returns -1. + * @param buffer Points to a buffer to write bytes from. + * @param size Number of bytes in the buffer. + * @return (size). + */ + virtual int32 Write(const void * buffer, uint32 size) + { + if (_buf() == NULL) return -1; + + uint32 oldBufSize = _buf()->GetNumBytes(); + uint32 pastOffset = muscleMax(oldBufSize, _seekPos+size); + if (pastOffset > oldBufSize) + { + uint32 preallocBytes = (pastOffset*2); // exponential resize to avoid too many reallocs + if (_buf()->SetNumBytes(preallocBytes, true) != B_NO_ERROR) return -1; // allocate the memory + memset(_buf()->GetBuffer()+oldBufSize, 0, preallocBytes-oldBufSize); // make sure newly alloc'd memory is zeroed out! + (void) _buf()->SetNumBytes(pastOffset, true); // guaranteed not to fail + } + + memcpy(_buf()->GetBuffer()+_seekPos, buffer, size); + _seekPos += size; + return size; + } + + /** Seeks to the specified point in our ByteBuffer. + * Note that only 32-bit seeks are supported in this implementation. + * @param offset Where to seek to. + * @param whence IO_SEEK_SET, IO_SEEK_CUR, or IO_SEEK_END. + * @return B_NO_ERROR on success, B_ERROR on failure + */ + virtual status_t Seek(int64 offset, int whence) + { + uint32 fileLen = _buf() ? _buf()->GetNumBytes() : 0; + int32 o = (int32) offset; + int32 newSeekPos = -1; + switch(whence) + { + case IO_SEEK_SET: newSeekPos = o; break; + case IO_SEEK_CUR: newSeekPos = _seekPos+o; break; + case IO_SEEK_END: newSeekPos = fileLen-o; break; + default: return B_ERROR; + } + if (newSeekPos < 0) return B_ERROR; + _seekPos = newSeekPos; + return B_NO_ERROR; + } + + virtual int64 GetPosition() const {return _seekPos;} + + /** + * No-op method. + * This method doesn't do anything at all. + */ + virtual void FlushOutput() {/* empty */} + + /** Disable us! */ + virtual void Shutdown() {_buf.Reset();} + + /** Can't select on this one, sorry */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return GetNullSocket();} + + /** Can't select on this one, sorry */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetNullSocket();} + +private: + ByteBufferRef _buf; + int32 _seekPos; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/ChildProcessDataIO.cpp b/dataio/ChildProcessDataIO.cpp new file mode 100644 index 00000000..46d48668 --- /dev/null +++ b/dataio/ChildProcessDataIO.cpp @@ -0,0 +1,636 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include // for PATH_MAX +#include "dataio/ChildProcessDataIO.h" +#include "util/MiscUtilityFunctions.h" // for ExitWithoutCleanup() +#include "util/NetworkUtilityFunctions.h" // SendData() and ReceiveData() + +#if defined(WIN32) || defined(__CYGWIN__) +# include // for _beginthreadex() +# define USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION +#else +# if defined(__linux__) +# include // for forkpty() on Linux +# else +# include // for forkpty() on MacOS/X +# endif +# include +# include // for SIGHUP, etc +# include // for waitpid() +#endif + +#include "util/NetworkUtilityFunctions.h" +#include "util/String.h" +#include "util/StringTokenizer.h" + +namespace muscle { + +ChildProcessDataIO :: ChildProcessDataIO(bool blocking) : _blocking(blocking), _killChildOkay(true), _maxChildWaitTime(0), _signalNumber(-1), _childProcessInheritFileDescriptors(false), _childProcessIsIndependent(false) +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + , _readFromStdout(INVALID_HANDLE_VALUE), _writeToStdin(INVALID_HANDLE_VALUE), _ioThread(INVALID_HANDLE_VALUE), _wakeupSignal(INVALID_HANDLE_VALUE), _childProcess(INVALID_HANDLE_VALUE), _childThread(INVALID_HANDLE_VALUE), _requestThreadExit(false) +#else + , _childPID(-1) +#endif +{ + // empty +} + +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION +static void SafeCloseHandle(::HANDLE & h) +{ + if (h != INVALID_HANDLE_VALUE) + { + CloseHandle(h); + h = INVALID_HANDLE_VALUE; + } +} +#endif + +status_t ChildProcessDataIO :: LaunchChildProcess(const Queue & argq, uint32 launchBits, const char * optDirectory) +{ + uint32 numItems = argq.GetNumItems(); + if (numItems == 0) return B_ERROR; + + const char ** argv = newnothrow_array(const char *, numItems+1); + if (argv == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + for (uint32 i=0; i tmpQ; (void) tmpQ.EnsureSize(argc); + for (int i=0; i=0)?(((const char **)args)[0]):NULL, (char *)cmd(), NULL, NULL, TRUE, 0, NULL, optDirectory, &siStartInfo, &piProcInfo)) + { + _childProcess = piProcInfo.hProcess; + _childThread = piProcInfo.hThread; + + if (_blocking) return B_NO_ERROR; // done! + else + { + // For non-blocking, we must have a separate proxy thread do the I/O for us :^P + _wakeupSignal = CreateEvent(0, false, false, 0); + if ((_wakeupSignal != INVALID_HANDLE_VALUE)&&(CreateConnectedSocketPair(_masterNotifySocket, _slaveNotifySocket, false) == B_NO_ERROR)) + { + DWORD junkThreadID; + typedef unsigned (__stdcall *PTHREAD_START) (void *); + if ((_ioThread = (::HANDLE) _beginthreadex(NULL, 0, (PTHREAD_START)IOThreadEntryFunc, this, 0, (unsigned *) &junkThreadID)) != INVALID_HANDLE_VALUE) return B_NO_ERROR; + } + } + } + } + SafeCloseHandle(childStdinRead); // cleanup + SafeCloseHandle(childStdinWrite); // cleanup + } + } + SafeCloseHandle(childStdoutRead); // cleanup + SafeCloseHandle(childStdoutWrite); // cleanup + } + Close(); // free all allocated object state we may have + return B_ERROR; +#else + // First, set up our arguments array here in the parent process, since the child process won't be able to do any dynamic allocations. + Queue scratchChildArgQ; // holds the strings that the pointers in the argv buffer will point to + bool isParsed = (argc<0); + if (argc < 0) + { + if (ParseArgs(String((const char *)args), scratchChildArgQ) != B_NO_ERROR) return B_ERROR; + argc = scratchChildArgQ.GetNumItems(); + } + + ByteBuffer scratchChildArgv; // the child process's argv array, NULL terminated + if (scratchChildArgv.SetNumBytes((argc+1)*sizeof(char *), false) != B_NO_ERROR) return B_ERROR; + + // Populate the argv array for our child process to use + const char ** argv = (const char **) scratchChildArgv.GetBuffer(); + if (isParsed) for (int i=0; i 0) _handle = GetConstSocketRefFromPool(masterFD); + else if (pid == 0) + { + // Turn off the echo, we don't want to see that back on stdout + struct termios tios; + if (tcgetattr(STDIN_FILENO, &tios) >= 0) + { + tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + tios.c_oflag &= ~(ONLCR); /* also turn off NL to CR/NL mapping on output */ + (void) tcsetattr(STDIN_FILENO, TCSANOW, &tios); + } + } +#endif + } + else + { + // Old-fashioned fork() implementation + ConstSocketRef masterSock, slaveSock; + if (CreateConnectedSocketPair(masterSock, slaveSock, true) != B_NO_ERROR) return B_ERROR; + pid = fork(); + if (pid > 0) _handle = masterSock; + else if (pid == 0) + { + int fd = slaveSock()->GetFileDescriptor(); + if (((launchBits & CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDIN) == 0)&&(dup2(fd, STDIN_FILENO) < 0)) ExitWithoutCleanup(20); + if (((launchBits & CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDOUT) == 0)&&(dup2(fd, STDOUT_FILENO) < 0)) ExitWithoutCleanup(20); + if (((launchBits & CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDERR) == 0)&&(dup2(fd, STDERR_FILENO) < 0)) ExitWithoutCleanup(20); + } + } + + if (pid < 0) return B_ERROR; // fork failure! + else if (pid == 0) + { + // we are the child process + (void) signal(SIGHUP, SIG_DFL); // FogBugz #2918 + + // Close any file descriptors leftover from the parent process + if (_childProcessInheritFileDescriptors == false) + { + int fdlimit = sysconf(_SC_OPEN_MAX); + for (int i=STDERR_FILENO+1; i= 0); +#endif +} + +status_t ChildProcessDataIO :: KillChildProcess() +{ + TCHECKPOINT; + +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + if ((_childProcess != INVALID_HANDLE_VALUE)&&(TerminateProcess(_childProcess, 0))) return B_NO_ERROR; +#else + if ((_childPID >= 0)&&(kill(_childPID, SIGKILL) == 0)) + { + (void) waitpid(_childPID, NULL, 0); // avoid creating a zombie process + _childPID = -1; + return B_NO_ERROR; + } +#endif + + return B_ERROR; +} + +status_t ChildProcessDataIO :: SignalChildProcess(int sigNum) +{ + TCHECKPOINT; + +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + (void) sigNum; // to shut the compiler up + return B_ERROR; // Not implemented under Windows! +#else + // Yes, kill() is a misnomer. Silly Unix people! + return ((_childPID >= 0)&&(kill(_childPID, sigNum) == 0)) ? B_NO_ERROR : B_ERROR; +#endif +} + +void ChildProcessDataIO :: Close() +{ + TCHECKPOINT; + +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + if (_ioThread != INVALID_HANDLE_VALUE) // if this is valid, _wakeupSignal is guaranteed valid too + { + _requestThreadExit = true; // set the "Please go away" flag + SetEvent(_wakeupSignal); // wake the thread up so he'll check the flag + WaitForSingleObject(_ioThread, INFINITE); // then wait for him to go away + ::CloseHandle(_ioThread); // fix handle leak + _ioThread = INVALID_HANDLE_VALUE; + } + _masterNotifySocket.Reset(); + _slaveNotifySocket.Reset(); + SafeCloseHandle(_wakeupSignal); + SafeCloseHandle(_readFromStdout); + SafeCloseHandle(_writeToStdin); + if ((_childProcess != INVALID_HANDLE_VALUE)&&(_childProcessIsIndependent == false)) DoGracefulChildShutdown(); // Windows can't double-fork, so in the independent case we just won't wait for him + SafeCloseHandle(_childProcess); + SafeCloseHandle(_childThread); +#else + _handle.Reset(); + if (_childPID >= 0) DoGracefulChildShutdown(); + _childPID = -1; +#endif +} + +void ChildProcessDataIO :: DoGracefulChildShutdown() +{ + if (_signalNumber >= 0) (void) SignalChildProcess(_signalNumber); + if ((WaitForChildProcessToExit(_maxChildWaitTime) == false)&&(_killChildOkay)) (void) KillChildProcess(); +} + +bool ChildProcessDataIO :: WaitForChildProcessToExit(uint64 maxWaitTimeMicros) +{ +#ifdef WIN32 + return ((_childProcess == INVALID_HANDLE_VALUE)||(WaitForSingleObject(_childProcess, (maxWaitTimeMicros==MUSCLE_TIME_NEVER)?INFINITE:((DWORD)(maxWaitTimeMicros/1000))) == WAIT_OBJECT_0)); +#else + if (_childPID < 0) return true; // a non-existent child process is an exited child process, if you ask me. + if (maxWaitTimeMicros == MUSCLE_TIME_NEVER) return (waitpid(_childPID, NULL, 0) == _childPID); + else + { + // The tricky case... waiting for the child process to exit, with a timeout. + // I'm implementing it via a polling loop, which is a sucky way to implement + // it but the only alternative would involve mucking about with signal handlers, + // and doing it that way would be unreliable in multithreaded environments. + uint64 endTime = GetRunTime64()+maxWaitTimeMicros; + uint64 pollInterval = 0; // we'll start quickly, and work our way up. + while(1) + { + int r = waitpid(_childPID, NULL, WNOHANG); // WNOHANG should guarantee that this call will not block + if (r == _childPID) return true; // yay, he exited! + else if (r == -1) return false; // fail on error + + int64 microsLeft = endTime-GetRunTime64(); + if (microsLeft <= 0) return false; // we're out of time! + + // At this point, r was probably zero because the child wasn't ready to exit + if (pollInterval < (200*1000)) pollInterval += (10*1000); + Snooze64(muscleMin(pollInterval, (uint64)microsLeft)); + } + } +#endif +} + +int32 ChildProcessDataIO :: Read(void *buf, uint32 len) +{ + TCHECKPOINT; + + if (IsChildProcessAvailable()) + { +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + if (_blocking) + { + DWORD actual_read; + if (ReadFile(_readFromStdout, buf, len, &actual_read, NULL)) return actual_read; + } + else + { + int32 ret = ReceiveData(_masterNotifySocket, buf, len, _blocking); + if (ret >= 0) SetEvent(_wakeupSignal); // wake up the thread in case he has more data to give us + return ret; + } +#else + int r = read_ignore_eintr(_handle.GetFileDescriptor(), buf, len); + return _blocking ? r : ConvertReturnValueToMuscleSemantics(r, len, _blocking); +#endif + } + return -1; +} + +int32 ChildProcessDataIO :: Write(const void *buf, uint32 len) +{ + TCHECKPOINT; + + if (IsChildProcessAvailable()) + { +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + if (_blocking) + { + DWORD actual_write; + if (WriteFile(_writeToStdin, buf, len, &actual_write, 0)) return actual_write; + } + else + { + int32 ret = SendData(_masterNotifySocket, buf, len, _blocking); + if (ret > 0) SetEvent(_wakeupSignal); // wake up the thread so he'll check his socket for our new data + return ret; + } +#else + return ConvertReturnValueToMuscleSemantics(write_ignore_eintr(_handle.GetFileDescriptor(), buf, len), len, _blocking); +#endif + } + return -1; +} + +void ChildProcessDataIO :: FlushOutput() +{ + // not implemented +} + +void ChildProcessDataIO :: ChildProcessReadyToRun() +{ + // empty +} + +const ConstSocketRef & ChildProcessDataIO :: GetChildSelectSocket() const +{ +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + return _blocking ? GetNullSocket() : _masterNotifySocket; +#else + return _handle; +#endif +} + +void ChildProcessDataIO :: Shutdown() +{ + Close(); +} + +#ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION + +const uint32 CHILD_BUFFER_SIZE = 1024; + +// Used as a temporary holding area for data in transit +class ChildProcessBuffer +{ +public: + ChildProcessBuffer() : _length(0), _index(0) {/* empty */} + + char _buf[CHILD_BUFFER_SIZE]; + uint32 _length; // how many bytes in _buf are actually valid + uint32 _index; // Index of the next byte to process +}; + +// to be called from within the I/O thread only! +void ChildProcessDataIO :: IOThreadAbort() +{ + // If we read zero bytes, that means EOF! Child process has gone away! + _slaveNotifySocket.Reset(); + _requestThreadExit = true; // this will cause the I/O thread to go away now +} + +void ChildProcessDataIO :: IOThreadEntry() +{ + bool childProcessExited = false; + + ChildProcessBuffer inBuf; // bytes from the child process's stdout, waiting to go to the _slaveNotifySocket + ChildProcessBuffer outBuf; // bytes from the _slaveNotifySocket, waiting to go to the child process's stdin + + ::HANDLE events[] = {_wakeupSignal, _childProcess}; + while(_requestThreadExit == false) + { + // IOThread <-> UserThread i/o handling here + { + // While we have any data in inBuf, send as much of it as possible back to the user thread. This won't block. + while(inBuf._index < inBuf._length) + { + int32 bytesToWrite = inBuf._length-inBuf._index; + int32 bytesWritten = (bytesToWrite > 0) ? SendData(_slaveNotifySocket, &inBuf._buf[inBuf._index], bytesToWrite, false) : 0; + if (bytesWritten > 0) + { + inBuf._index += bytesWritten; + if (inBuf._index == inBuf._length) inBuf._index = inBuf._length = 0; + } + else + { + if (bytesWritten < 0) IOThreadAbort(); // use thread connection closed!? + break; // can't write any more, for now + } + } + + // While we have room in our outBuf, try to read some more data into it from the slave socket. This won't block. + while(outBuf._length < sizeof(outBuf._buf)) + { + int32 maxLen = sizeof(outBuf._buf)-outBuf._length; + int32 ret = ReceiveData(_slaveNotifySocket, &outBuf._buf[outBuf._length], maxLen, false); + if (ret > 0) outBuf._length += ret; + else + { + if (ret < 0) IOThreadAbort(); // user thread connection closed!? + break; // no more to read, for now + } + } + } + + // IOThread <-> ChildProcess i/o handling (and blocking) here + { + if (childProcessExited) + { + if (inBuf._index == inBuf._length) IOThreadAbort(); + break; + } + + // block here until an event happens... gotta poll because + // the Window anonymous pipes system doesn't allow me to + // to check for events on the pipe using WaitForMultipleObjects(). + // It may be worth it to use named pipes some day to get around this... + int evt = WaitForMultipleObjects(ARRAYITEMS(events)-(childProcessExited?1:0), events, false, 250)-WAIT_OBJECT_0; + if (evt == 1) childProcessExited = true; + + int32 numBytesToRead; + while((numBytesToRead = sizeof(inBuf._buf)-inBuf._length) > 0) + { + // See if there is actually any data available for reading first + DWORD pipeSize; + if (PeekNamedPipe(_readFromStdout, NULL, 0, NULL, &pipeSize, NULL)) + { + if (pipeSize > 0) + { + DWORD numBytesRead; + if (ReadFile(_readFromStdout, &inBuf._buf[inBuf._length], numBytesToRead, &numBytesRead, NULL)) + { + inBuf._length += numBytesRead; + } + else + { + IOThreadAbort(); // child process exited? + break; + } + } + else break; + } + else + { + IOThreadAbort(); // child process exited? + break; + } + } + + int32 numBytesToWrite; + while((numBytesToWrite = outBuf._length-outBuf._index) > 0) + { + DWORD bytesWritten; + if (WriteFile(_writeToStdin, &outBuf._buf[outBuf._index], numBytesToWrite, &bytesWritten, 0)) + { + if (bytesWritten > 0) + { + outBuf._index += bytesWritten; + if (outBuf._index == outBuf._length) outBuf._index = outBuf._length = 0; + } + else break; // no more space to write to, for now + } + else IOThreadAbort(); // wtf? + } + } + } +} +#endif + +status_t ChildProcessDataIO :: System(int argc, const char * argv[], uint32 launchBits, uint64 maxWaitTimeMicros, const char * optDirectory) +{ + ChildProcessDataIO cpdio(false); + if (cpdio.LaunchChildProcess(argc, argv, launchBits, optDirectory) == B_NO_ERROR) + { + cpdio.WaitForChildProcessToExit(maxWaitTimeMicros); + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t ChildProcessDataIO :: System(const Queue & argq, uint32 launchBits, uint64 maxWaitTimeMicros, const char * optDirectory) +{ + uint32 numItems = argq.GetNumItems(); + if (numItems == 0) return B_ERROR; + + const char ** argv = newnothrow_array(const char *, numItems+1); + if (argv == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + for (uint32 i=0; i & argv, bool inheritFileDescriptors, const char * optDirectory) +{ + ChildProcessDataIO cpdio(true); + cpdio._childProcessIsIndependent = true; // so the cpdio dtor won't block waiting for the child to exit + cpdio.SetChildProcessInheritFileDescriptors(inheritFileDescriptors); + cpdio.SetChildProcessShutdownBehavior(false); + return cpdio.LaunchChildProcess(argv, false, optDirectory); +} + +}; // end namespace muscle diff --git a/dataio/ChildProcessDataIO.h b/dataio/ChildProcessDataIO.h new file mode 100644 index 00000000..e7d680ba --- /dev/null +++ b/dataio/ChildProcessDataIO.h @@ -0,0 +1,311 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef ChildProcessDataIO_h +#define ChildProcessDataIO_h + +#include +#include "dataio/DataIO.h" +#include "util/Queue.h" + +namespace muscle { + +// Bits that can be passed as a bit-chord to LaunchChildProcess() +enum { + CHILD_PROCESS_LAUNCH_BIT_USE_FORKPTY = 0x01, // if set, we'll use forkpty() instead of fork() (ignored under Windows) + CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDIN = 0x02, // if set, we won't redirect from the child process's stdin (supported by fork() implementation only, for now) + CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDOUT = 0x04, // if set, we won't capture and return output from the child process's stdout (supported by fork() implementation only, for now) + CHILD_PROCESS_LAUNCH_BIT_EXCLUDE_STDERR = 0x08, // if set, we won't capture and return output from the child process's stderr (supported by fork() implementation only, for now) +}; + +#ifndef MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS +# define MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS (CHILD_PROCESS_LAUNCH_BIT_USE_FORKPTY) +#endif + +/** This DataIO class is a handy cross-platform way to spawn + * and talk to a child process. Any data that the child process + * prints to stdout can be read from this object, and any data + * that is written to this object will be send to the child process's + * stdin. Note that this class is currently only guaranteed to work + * under Windows, MacOS/X, BeOS, and Linux. + */ +class ChildProcessDataIO : public DataIO, private CountedObject +{ +public: + /** Constructor. + * @param blocking If true, I/O will be blocking; else non-blocking. + * @note that you will need to call LaunchChildProcess() to actually start the child process going. + */ + ChildProcessDataIO(bool blocking); + + /** Destructor */ + virtual ~ChildProcessDataIO(); + + /** Launch the child process. Note that this method should only be called once! + * @param argc The argc variable to be passed to the child process + * @param argv The argv variable to be passed to the child process + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @return B_NO_ERROR on success, or B_ERROR if the launch failed. + */ + status_t LaunchChildProcess(int argc, const char * argv[], uint32 launchBits = MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS, const char * optDirectory = NULL) {return LaunchChildProcessAux(muscleMax(0,argc), argv, launchBits, optDirectory);} + + /** As above, but the program name and all arguments are specified as a single string. + * @param cmd String to launch the child process with + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @return B_NO_ERROR on success, or B_ERROR if the launch failed. + */ + status_t LaunchChildProcess(const char * cmd, uint32 launchBits = MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS, const char * optDirectory = NULL) {return LaunchChildProcessAux(-1, cmd, launchBits, optDirectory);} + + /** Convenience method. Launches a child process using an (argc,argv) that is constructed from the passed in argument list. + * @param argv A list of strings to construct the (argc,argv) from. The first string should be the executable name, the second string + * should be the first argument to the executable, and so on. + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @return B_NO_ERROR on success, or B_ERROR if the launch failed. + */ + status_t LaunchChildProcess(const Queue & argv, uint32 launchBits = MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS, const char * optDirectory = NULL); + + /** Read data from the child process's stdout stream. + * @param buffer The read bytes will be placed here + * @param size Maximum number of bytes that may be placed into (buffer). + * @returns The number of bytes placed into (buffer), or a negative value if there was an error. + */ + virtual int32 Read(void * buffer, uint32 size); + + /** Write data to the child process's stdin stream. + * @param buffer The bytes to write to the child process's stdin. + * @param size Maximum number of bytes to read from (buffer) and written to the child process's stdin. + * @returns The number of bytes written, or a negative value if there was an error. + */ + virtual int32 Write(const void * buffer, uint32 size); + + /** Always returns B_ERROR, since you can't seek on a child process! */ + virtual status_t Seek(int64, int) {return B_ERROR;} + + /** Always returns -1, since a child process has no position to speak of */ + virtual int64 GetPosition() const {return -1;} + + /** Doesn't return until all outgoing have been sent */ + virtual void FlushOutput(); + + /** Kills the child process, using the sequence described at SetChildProcessShutdownBehavior(). */ + virtual void Shutdown(); + + /** Returns a socket that can be select()'d on for notifications of read availability from the + * child's stdout or stderr streams. + * Even works under Windows (in non-blocking mode, anyway), despite Microsoft's best efforts + * to make such a thing impossible :^P Note that you should only pass this socket to select(); + * to read from the child process, call Read() on this object but don't try to recv()/etc on + * this socket directly! + */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return GetChildSelectSocket();} + + /** Returns a socket that can be select()'d on for notifications of write availability to the + * child's stdin stream. + * Even works under Windows (in non-blocking mode, anyway), despite Microsoft's best efforts + * to make such a thing impossible :^P Note that you should only pass this socket to select(); + * to write to the child process, call Write() on this object but don't try to send()/etc on + * this socket directly! + */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetChildSelectSocket();} + + /** Returns true iff the child process is available (i.e. if startup succeeded). */ + bool IsChildProcessAvailable() const; + + /** Specify the series of actions we should take to gracefully shutdown the child process + * when this object is closed. + * + * The shutdown sequence we use is the following: + * 1) Close the socket to the child process + * 2) (optional) send a signal to the child process (note: this step is not available under Windows) + * 3) (optional) wait up to a specified amount of time for the child process to exit voluntarily + * 4) (optional) forcibly kill the child process if it hasn't exited by this time + * + * @param okayToKillChild If true, we are allowed to violently terminate the child if he still + * hasn't exited by the time we are done waiting for him to exit. If false, + * we will just let the child continue running if he feels he must. + * @param sendSignalNumber If non-negative, we will prompt the child process to exit by + * sending him this signal before beginning to wait. Note that + * this argument is not used under Windows. + * Defaults to -1. + * @param maxWaitTimeMicros If specified, this is the maximum amount of time (in microseconds) + * that we should wait for the child process to exit before continuing. + * Defaults to MUSCLE_TIME_NEVER, meaning that we will wait indefinitely + * for the child to exit, if necessary. + * + * Note that if this method is never called, then the default behavior is to immediately + * kill the child (with no signal sent and no wait time elapsed). + */ + void SetChildProcessShutdownBehavior(bool okayToKillChild, int sendSignalNumber = -1, uint64 maxWaitTimeMicros = MUSCLE_TIME_NEVER); + + /** Set whether or not the child process we spawn should inherit our + * open file descriptors. Default value is false. + */ + void SetChildProcessInheritFileDescriptors(bool cpifds) {_childProcessInheritFileDescriptors = cpifds;} + + /** Returns true iff the child process we spawn will inherit our + * open file descriptors. Default value is false. + */ + bool GetChildProcessInheritFileDescriptors() const {return _childProcessInheritFileDescriptors;} + + /** Called within the child process, just before the child process's + * executable image is loaded in. Default implementation is a no-op. + * @note This method is not called when running under Windows! + */ + virtual void ChildProcessReadyToRun(); + + /** Returns the process ID of the child process. Not available under Windows. */ +#if defined(WIN32) || defined(CYGWIN) + uint32 GetChildProcessID() const {return (uint32)GetProcessId(_childProcess);} +#else + pid_t GetChildProcessID() const {return _childPID;} +#endif + + /** Tries to forcibly kill the child process immediately. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t KillChildProcess(); + + /** Sends the specified signal to the child process. + * Note that this method is not currently implemented under Windows, + * and thus under Windows this method is a no-op that just returns B_ERROR. + * @param sigNum a signal number, e.g. SIGINT or SIGHUP. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SignalChildProcess(int sigNum); + + /** Will not return until our child process has exited. + * If the child process is not currently running, returns immediately. + * @param maxWaitTime The maximum amount of time to wait, in microseconds. + * Defaults to MUSCLE_TIME_NEVER, indicating no timeout. + * @returns true iff the child process is known to be gone, or false + * otherwise (e.g. our timeout period elapsed and the child + * process still hadn't exited) + */ + bool WaitForChildProcessToExit(uint64 maxWaitTime = MUSCLE_TIME_NEVER); + + /** Convenience method: acts similar to the POSIX system() call, but + * implemented internally via a ChildProcessDataIO object. In particular, + * this static method will launch the specified process and not return + * until that process has completed. + * @param argc Number of items in the (argv) array + * @param argv A standard argv array for the child process to use + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @param maxWaitTimeMicros If specified, this is the maximum amount of time (in microseconds) + * that we should wait for the child process to exit before continuing. + * Defaults to MUSCLE_TIME_NEVER, meaning that we will wait indefinitely + * for the child to exit, if necessary. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @returns B_NO_ERROR if the child process was launched, or B_ERROR + * if the child process could not be launched. + */ + static status_t System(int argc, const char * argv[], uint32 launchBits=true, uint64 maxWaitTimeMicros = MUSCLE_TIME_NEVER, const char * optDirectory = NULL); + + /** Convenience method: acts similar to the POSIX system() call, but + * implemented internally via a ChildProcessDataIO object. In particular, + * this static method will launch the specified process and not return + * until that process has completed. + * @param argv A list of strings to construct the (argc,argv) from. The first string should be the executable name, the second string + * should be the first argument to the executable, and so on. + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @param maxWaitTimeMicros If specified, this is the maximum amount of time (in microseconds) + * that we should wait for the child process to exit before continuing. + * Defaults to MUSCLE_TIME_NEVER, meaning that we will wait indefinitely + * for the child to exit, if necessary. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @return B_NO_ERROR on success, or B_ERROR if the launch failed. + */ + static status_t System(const Queue & argv, uint32 launchBits = MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_BITS, uint64 maxWaitTimeMicros = MUSCLE_TIME_NEVER, const char * optDirectory = NULL); + + /** Convenience method: acts similar to the POSIX system() call, but + * implemented internally via a ChildProcessDataIO object. In particular, + * this static method will launch the specified process and not return + * until that process has completed. + * @param cmdLine The command string to launch (as if typed into a shell) + * @param launchBits A bit-chord of CHILD_PROCESS_LAUNCH_BIT_* bit values. + * @returns B_NO_ERROR if the child process was launched, or B_ERROR + * if the child process could not be launched. + * @param maxWaitTimeMicros If specified, this is the maximum amount of time (in microseconds) + * that we should wait for the child process to exit before continuing. + * Defaults to MUSCLE_TIME_NEVER, meaning that we will wait indefinitely + * for the child to exit, if necessary. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + */ + static status_t System(const char * cmdLine, uint32 launchBits=true, uint64 maxWaitTimeMicros = MUSCLE_TIME_NEVER, const char * optDirectory = NULL); + + /** Convenience method: launches a child process that will be completely independent of the current process. + * @param argc Number of items in the (argv) array + * @param argv A standard argv array for the child process to use + * @param inheritFileDescriptors If true, the child process will inherit all file descriptors from this process. + * If false (the default), the child process will not inherit any file descriptors from this process. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @returns B_NO_ERROR if the child process was launched, or B_ERROR if the child process could not be launched. + */ + static status_t LaunchIndependentChildProcess(int argc, const char * argv[], bool inheritFileDescriptors=false, const char * optDirectory = NULL); + + /** Convenience method: launches a child process that will be completely independent of the current process. + * @param argv A list of strings to construct the (argc,argv) from. The first string should be the executable name, the second string + * should be the first argument to the executable, and so on. + * @param inheritFileDescriptors If true, the child process will inherit all file descriptors from this process. + * If false (the default), the child process will not inherit any file descriptors from this process. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @return B_NO_ERROR on success, or B_ERROR if the launch failed. + */ + static status_t LaunchIndependentChildProcess(const Queue & argv, bool inheritFileDescriptors=false, const char * optDirectory = NULL); + + /** Convenience method: launches a child process that will be completely independent of the current process. + * @param cmdLine The command string to launch (as if typed into a shell) + * @param inheritFileDescriptors If true, the child process will inherit all file descriptors from this process. + * If false (the default), the child process will not inherit any file descriptors from this process. + * @param optDirectory Optional directory path to set the child process's current directory to. + * Defaults to NULL, which will cause the child process to inherit this process's current directory. + * @returns B_NO_ERROR if the child process was launched, or B_ERROR if the child process could not be launched. + */ + static status_t LaunchIndependentChildProcess(const char * cmdLine, bool inheritFileDescriptors=false, const char * optDirectory = NULL); + +private: + void Close(); + status_t LaunchChildProcessAux(int argc, const void * argv, uint32 launchBits, const char * optDirectory); + void DoGracefulChildShutdown(); + const ConstSocketRef & GetChildSelectSocket() const; + + bool _blocking; + + bool _killChildOkay; + uint64 _maxChildWaitTime; + int _signalNumber; + + bool _childProcessInheritFileDescriptors; + bool _childProcessIsIndependent; + +#if defined(WIN32) || defined(CYGWIN) + void IOThreadEntry(); + void IOThreadAbort(); + static DWORD WINAPI IOThreadEntryFunc(LPVOID This) {((ChildProcessDataIO*)This)->IOThreadEntry(); return 0;} + ::HANDLE _readFromStdout; + ::HANDLE _writeToStdin; + ::HANDLE _ioThread; + ::HANDLE _wakeupSignal; + ::HANDLE _childProcess; + ::HANDLE _childThread; + ConstSocketRef _masterNotifySocket; + ConstSocketRef _slaveNotifySocket; + volatile bool _requestThreadExit; +#else + ConstSocketRef _handle; + pid_t _childPID; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/DataIO.h b/dataio/DataIO.h new file mode 100644 index 00000000..a99276a7 --- /dev/null +++ b/dataio/DataIO.h @@ -0,0 +1,193 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleDataIO_h +#define MuscleDataIO_h + +#include "util/Socket.h" +#include "util/TimeUtilityFunctions.h" // for MUSCLE_TIME_NEVER +#include "util/CountedObject.h" + +namespace muscle { + +/** Abstract base class for a byte-stream Data I/O interface, similar to Be's BDataIO. */ +class DataIO : public RefCountable, private CountedObject +{ +public: + /** Values to pass in to DataIO::Seek()'s second parameter */ + enum { + IO_SEEK_SET = 0, /**< Tells Seek that its value specifies bytes-after-beginning-of-stream */ + IO_SEEK_CUR, /**< Tells Seek that its value specifies bytes-after-current-stream-position */ + IO_SEEK_END, /**< Tells Seek that its value specifies bytes-after-end-of-stream (you'll usually specify a non-positive seek value with this) */ + NUM_IO_SEEKS /**< A guard value */ + }; + + /** Default Constructor */ + DataIO() {/* empty */} + + /** Virtual destructor, to keep C++ honest. */ + virtual ~DataIO() {/* empty */} + + /** Tries to place (size) bytes of new data into (buffer). Returns the + * actual number of bytes placed, or a negative value if there + * was an error. + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + */ + virtual int32 Read(void * buffer, uint32 size) = 0; + + /** Takes (size) bytes from (buffer) and pushes them in to the + * outgoing I/O stream. Returns the actual number of bytes + * read from (buffer) and pushed, or a negative value if there + * was an error. + * @param buffer Buffer to read the bytes from. + * @param size Number of bytes in the buffer. + * @return Number of bytes written, or -1 on error. + */ + virtual int32 Write(const void * buffer, uint32 size) = 0; + + /** + * Seek to a given position in the I/O stream. + * May not be supported by a DataIO subclass, in + * which case B_ERROR will always be returned. + * @param offset Byte offset to seek to or by (depending on the next arg) + * @param whence Set this to IO_SEEK_SET if you want the offset to + * be relative to the start of the stream; or to + * IO_SEEK_CUR if it should be relative to the current + * stream position, or IO_SEEK_END if it should be + * relative to the end of the stream. + * @return B_NO_ERROR on success, or B_ERROR on failure or if unsupported. + */ + virtual status_t Seek(int64 offset, int whence) = 0; + + /** + * Should return the current position, in bytes, of the stream from + * its start position, or -1 if the current position is not known. + */ + virtual int64 GetPosition() const = 0; + + /** + * Returns the max number of microseconds to allow + * for an output stall, before presuming that the I/O is hosed. + * Default implementation returns MUSCLE_TIME_NEVER, aka no limit. + */ + virtual uint64 GetOutputStallLimit() const {return MUSCLE_TIME_NEVER;} + + /** + * Flushes the output buffer, if possible. For some implementations, + * this is a no-op. For others (e.g. TCPSocketDataIO) this can be + * called to reduced latency of outgoing data blocks. + */ + virtual void FlushOutput() = 0; + + /** + * Closes the connection. After calling this method, the + * DataIO object should not be used any more. + */ + virtual void Shutdown() = 0; + + /** + * This method should return a ConstSocketRef object containing a file descriptor + * that can be passed to the readSet argument of select(), so that select() can + * return when there is data available to be read from this DataIO (via Read()). + * + * If this DataIO cannot provide a socket that will notify select() about + * data-ready-to-be-read, then this method should return GetNullSocket(). + * + * Note that the only thing you are allowed to do with this returned file descriptor + * is pass it to select()'s readSet. For all other operations, use the appropriate + * methods in the DataIO interface. If you attempt to do any other I/O operations + * on this file descriptor directly, the results are undefined. + */ + virtual const ConstSocketRef & GetReadSelectSocket() const = 0; + + /** + * This method should return a ConstSocketRef object containing a file descriptor + * that can be passed to the writeSet argument of select(), so that select() can + * return when there is buffer space available to Write() to this DataIO. + * + * If this DataIO cannot provide a socket that will notify select() about + * space-ready-to-be-written-to, then this method should return GetNullSocket(). + * + * Note that the only thing you are allowed to do with this returned file descriptor + * is pass it to select()'s writeSet. For all other operations, use the appropriate + * methods in the DataIO interface. If you attempt to do any other I/O operations + * on this file descriptor directly, the results are undefined. + */ + virtual const ConstSocketRef & GetWriteSelectSocket() const = 0; + + /** + * Optional interface for returning information on when a given byte + * returned by the previous Read() call was received. Not implemented + * by default, and not implemented by any of the standard MUSCLE DataIO + * subclasses. (Used by an LCS dataIO class that needs precision timing) + * @param whichByte Index of the byte in the previously returned + * read-buffer that you are interested in. + * @param retStamp On success, this value is set to the timestamp + * of the byte. + * @return B_NO_ERROR if a timestamp was written into (retStamp), + * otherwise B_ERROR. Default implementation + * always returns B_ERROR. + */ + virtual status_t GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const {(void) whichByte; (void) retStamp; return B_ERROR;} + + /** + * Optional: If your DataIO subclass is holding buffered data that it wants + * to output as soon as possible but hasn't been able to yet, + * then override this method to return true, and that will cause + * WriteBufferedOutput() to be called ASAP. Default implementation + * always returns false. + */ + virtual bool HasBufferedOutput() const {return false;} + + /** + * Optional: If this DataIO is holding any buffered output data, this method should + * be implemented to Write() as much of that data as possible. Default + * implementation is a no-op. + */ + virtual void WriteBufferedOutput() {/* empty */} + + /** + * Optional: If this DataIO represents a device doing packet-style communication (e.g UDP) + * where a short read results in the "extra" bytes being irretrievably lost, + * then this method should be overridden to return the maximum + * number of bytes that can fit into a single packet (e.g. the MTU size). + * For the more common "stream" style of I/O (where a short read leaves the + * remaining bytes in a buffer to be read later), this method should return 0. + * The default implementation of this method always returns 0. + */ + virtual uint32 GetPacketMaximumSize() const {return 0;} + + /** Convenience method: Calls Write() in a loop until the entire buffer is written, or + * until an error occurs. This method should only be used in conjunction with + * blocking I/O; it will not work reliably with non-blocking I/O. + * @param buffer Pointer to the first byte of the buffer to write data from. + * @param size Number of bytes to write + * @return The number of bytes that were actually written. On success, + * This will be equal to (size). On failure, it will be a smaller value. + */ + uint32 WriteFully(const void * buffer, uint32 size); + + /** Convenience method: Calls Read() in a loop until the entire buffer is written, or + * until an error occurs. This method should only be used in conjunction with + * blocking I/O; it will not work reliably with non-blocking I/O. + * @param buffer Pointer to the first byte of the buffer to place the read data into. + * @param size Number of bytes to read + * @return The number of bytes that were actually read. On success, + * This will be equal to (size). On failure, it will be a smaller value. + */ + uint32 ReadFully(void * buffer, uint32 size); + + /** Convenience method: Determines the length of this DataIO stream by Seek()'ing + * to the end of the stream, recording the current seek position, and then + * Seek()'ing back to the previous position in the stream. Of course this only + * works with DataIOs that support seeking and have a fixed length. + * @returns The total length of this DataIO in bytes, or -1 on error. + */ + virtual int64 GetLength(); +}; +DECLARE_REFTYPES(DataIO); + +}; // end namespace muscle + +#endif diff --git a/dataio/FailoverDataIO.h b/dataio/FailoverDataIO.h new file mode 100644 index 00000000..3015b156 --- /dev/null +++ b/dataio/FailoverDataIO.h @@ -0,0 +1,135 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleFailoverDataIO_h +#define MuscleFailoverDataIO_h + +#include "dataio/DataIO.h" +#include "util/Queue.h" + +namespace muscle { + +class FailoverDataIO; + +/** This class represents any object that can receive a callback notification that a failover has occurred. */ +class IFailoverNotifyTarget +{ +public: + /** Default ctor */ + IFailoverNotifyTarget() {/* empty */} + + /** Destructor */ + virtual ~IFailoverNotifyTarget() {/* empty */} + + /** Called by a FailoverDataIO object when a failover occurs. + * @param source The calling FailoverDataIO object. + */ + virtual void DataIOFailover(const FailoverDataIO & source) = 0; +}; + +/** This DataIO holds a list of one or more other DataIO objects, and uses the only + * first one until an error occurs. If an error occurs, it will discard the first + * held DataIO and start using the second one instead (and so on). This is useful + * for providing automatic failover/redundancy for important connections. + */ +class FailoverDataIO : public DataIO, private CountedObject +{ +public: + /** Default Constructor. Be sure to add some child DataIOs to our Queue of + * DataIOs (as returned by GetChildDataIOs()) so that this object will do something useful! + * @param logErrorLevel Error level to give the log message that is generated when a failover occurs. + * Defaults to MUSCLE_LOG_NONE, so that by default no log message is generated. + */ + FailoverDataIO(int logErrorLevel = MUSCLE_LOG_NONE) : _logErrorLevel(logErrorLevel), _target(NULL) {/* empty */} + + /** Virtual destructor, to keep C++ honest. */ + virtual ~FailoverDataIO() {/* empty */} + + virtual int32 Read(void * buffer, uint32 size) + { + while(HasChild()) + { + int32 ret = GetChild()->Read(buffer, size); + if (ret >= 0) return ret; + else Failover(); + } + return -1; + } + + virtual int32 Write(const void * buffer, uint32 size) + { + while(HasChild()) + { + int32 ret = GetChild()->Write(buffer, size); + if (ret >= 0) return ret; + else Failover(); + } + return -1; + } + + virtual status_t Seek(int64 offset, int whence) + { + while(HasChild()) + { + status_t ret = GetChild()->Seek(offset, whence); + if (ret == B_NO_ERROR) return ret; + else Failover(); + } + return B_ERROR; + } + + virtual int64 GetPosition() const {return HasChild() ? GetChild()->GetPosition() : -1;} + + virtual uint64 GetOutputStallLimit() const {return HasChild() ? GetChild()->GetOutputStallLimit() : MUSCLE_TIME_NEVER;} + + virtual void FlushOutput() {if (HasChild()) GetChild()->FlushOutput();} + + virtual void Shutdown() {_childIOs.Clear();} + + virtual const ConstSocketRef & GetReadSelectSocket() const {return HasChild() ? GetChild()->GetReadSelectSocket() : GetNullSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return HasChild() ? GetChild()->GetWriteSelectSocket() : GetNullSocket();} + + virtual status_t GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const {return HasChild() ? GetChild()->GetReadByteTimeStamp(whichByte, retStamp) : B_ERROR;} + + virtual bool HasBufferedOutput() const {return HasChild() ? GetChild()->HasBufferedOutput() : false;} + + virtual void WriteBufferedOutput() {if (HasChild()) GetChild()->WriteBufferedOutput();} + + virtual uint32 GetPacketMaximumSize() const {return (HasChild()) ? GetChild()->GetPacketMaximumSize() : 0:} + + /** Returns a read-only reference to our list of child DataIO objects. */ + const Queue & GetChildDataIOs() const {return _childIOs;} + + /** Returns a read/write reference to our list of child DataIO objects. */ + Queue & GetChildDataIOs() {return _childIOs;} + + /** Called whenever a child DataIO reports an error. Default implementation + * removes the first DataIORef from the queue, prints an error to the log, + * and calls DataIOFailover() on the current failover notification target + * (if any; see SetFailoverNotifyTarget() for details) + */ + virtual void Failover() + { + (void) _childIOs.RemoveHead(); + if (HasChild()) LogTime(_logErrorLevel, "FailoverDataIO: Child IO errored out, failing over to next child ("UINT32_FORMAT_SPEC" children left)!\n", _childIOs.GetNumItems()); + else LogTime(_logErrorLevel, "FailoverDataIO: Child IO errored out, no backup children left!\n"); + if (_target) _target->DataIOFailover(*this); + } + + /** Call this to set the object that we should call DataIOFailover() on when a failover occurs. */ + void SetFailoverNotifyTarget(IFailoverNotifyTarget * t) {_target = t;} + + /** Returns the current failover notification target. Default is NULL (i.e. no target set) */ + IFailoverNotifyTarget * GetFailoverNotifyTarget() const {return _target;} + +private: + bool HasChild() const {return (_childIOs.HasItems());} + DataIO * GetChild() const {return (_childIOs.Head()());} + + Queue _childIOs; + int _logErrorLevel; + IFailoverNotifyTarget * _target; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/FileDataIO.h b/dataio/FileDataIO.h new file mode 100644 index 00000000..c70be167 --- /dev/null +++ b/dataio/FileDataIO.h @@ -0,0 +1,132 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleFileDataIO_h +#define MuscleFileDataIO_h + +#include "dataio/DataIO.h" + +namespace muscle { + +/** + * Data I/O to and from a stdio FILE. + */ +class FileDataIO : public DataIO, private CountedObject +{ +public: + /** Constructor. + * @param file File to read from or write to. Becomes property of this FileDataIO object, + * and will be fclose()'d when this object is deleted. Defaults to NULL. + */ + FileDataIO(FILE * file = NULL) : _file(file) {/* empty */} + + /** Destructor. + * Calls fclose() on the held file. + */ + virtual ~FileDataIO() {if (_file) fclose(_file);} + + /** Reads bytes from our file and places them into (buffer). + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + * @see DataIO::Read() + */ + virtual int32 Read(void * buffer, uint32 size) + { + if (_file) + { + int32 ret = (int32) fread(buffer, 1, size, _file); + return (ret > 0) ? ret : -1; // EOF is an error, and it's returned as zero + } + else return -1; + } + + /** Takes bytes from (buffer) and writes them out to our file. + * @param buffer Buffer to read the bytes from. + * @param size Number of bytes in the buffer. + * @return Number of bytes written, or -1 on error. + * @see DataIO::Write() + */ + virtual int32 Write(const void * buffer, uint32 size) + { + if (_file) + { + int32 ret = (int32) fwrite(buffer, 1, size, _file); + return (ret > 0) ? ret : -1; // zero is an error + } + else return -1; + } + + /** Seeks to the specified point in the file. + * @note this subclass only supports 32-bit offsets. + * @param offset Where to seek to. + * @param whence IO_SEEK_SET, IO_SEEK_CUR, or IO_SEEK_END. + * @return B_NO_ERROR on success, B_ERROR on failure. + */ + virtual status_t Seek(int64 offset, int whence) + { + if (_file) + { + switch(whence) + { + case IO_SEEK_SET: whence = SEEK_SET; break; + case IO_SEEK_CUR: whence = SEEK_CUR; break; + case IO_SEEK_END: whence = SEEK_END; break; + default: return B_ERROR; + } + if (fseek(_file, (long) offset, whence) == 0) return B_NO_ERROR; + } + return B_ERROR; + } + + /** Returns our current position in the file. + * @note this subclass only supports 32-bit offsets. + */ + virtual int64 GetPosition() const + { + return _file ? (int64) ftell(_file) : -1; + } + + /** Flushes the file output by calling fflush() */ + virtual void FlushOutput() {if (_file) fflush(_file);} + + /** Calls fclose() on the held file descriptor (if any) and forgets it */ + virtual void Shutdown() + { + if (_file) + { + fclose(_file); + _file = NULL; + } + } + + /** + * Releases control of the contained FILE object to the calling code. + * After this method returns, this object no longer owns or can + * use or close the file descriptor descriptor it once held. + */ + void ReleaseFile() {_file = NULL;} + + /** + * Returns the FILE object held by this object, or NULL if there is none. + */ + FILE * GetFile() const {return _file;} + + /** + * Sets our file pointer to the specified handle, closing any previously held file handle first. + * @param fp The new file handle. If non-NULL, this FileDataIO becomes the owner of (fp). + */ + void SetFile(FILE * fp) {Shutdown(); _file = fp;} + + /** Returns a NULL reference; (can't select on this one, sorry) */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return GetNullSocket();} + + /** Returns a NULL reference; (can't select on this one, sorry) */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetNullSocket();} + +private: + FILE * _file; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/FileDescriptorDataIO.cpp b/dataio/FileDescriptorDataIO.cpp new file mode 100644 index 00000000..547e5857 --- /dev/null +++ b/dataio/FileDescriptorDataIO.cpp @@ -0,0 +1,131 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef WIN32 // Windows can't handle file descriptors! + +#if defined(__linux__) +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 //make sure it's using large file access +# endif +# include +# include +# if !defined(ANDROID) +# if !__GLIBC_PREREQ(2,2) +# define MUSCLE_USE_LLSEEK + _syscall5(int, _llseek, uint, fd, ulong, hi, ulong, lo, loff_t *, res, uint, wh); // scary --jaf +# endif +# endif +#endif + +#include "dataio/FileDescriptorDataIO.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" // for read_ignore_eintr() and write_ignore_eintr() + +namespace muscle { + +FileDescriptorDataIO :: +FileDescriptorDataIO(const ConstSocketRef & fd, bool blocking) : _fd(fd), _dofSyncOnClose(false) +{ + SetBlockingIOEnabled(blocking); +} + +FileDescriptorDataIO :: +~FileDescriptorDataIO() +{ + if (_dofSyncOnClose) + { + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) (void) fsync(fd); + } +} + +int32 FileDescriptorDataIO :: Read(void * buffer, uint32 size) +{ + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) + { + int r = read_ignore_eintr(fd, buffer, size); + return _blocking ? r : ConvertReturnValueToMuscleSemantics(r, size, _blocking); + } + else return -1; +} + +int32 FileDescriptorDataIO :: Write(const void * buffer, uint32 size) +{ + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) + { + int w = write_ignore_eintr(fd, buffer, size); + return _blocking ? w : ConvertReturnValueToMuscleSemantics(w, size, _blocking); + } + else return -1; +} + +void FileDescriptorDataIO :: FlushOutput() +{ + // empty +} + +status_t FileDescriptorDataIO :: SetBlockingIOEnabled(bool blocking) +{ + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) + { + if (fcntl(fd, F_SETFL, blocking ? 0 : O_NONBLOCK) == 0) + { + _blocking = blocking; + return B_NO_ERROR; + } + else + { + perror("FileDescriptorDataIO:SetBlockingIO failed"); + return B_ERROR; + } + } + else return B_ERROR; +} + +void FileDescriptorDataIO :: Shutdown() +{ + _fd.Reset(); +} + +status_t FileDescriptorDataIO :: Seek(int64 offset, int whence) +{ + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) + { + switch(whence) + { + case IO_SEEK_SET: whence = SEEK_SET; break; + case IO_SEEK_CUR: whence = SEEK_CUR; break; + case IO_SEEK_END: whence = SEEK_END; break; + default: return B_ERROR; + } +#ifdef MUSCLE_USE_LLSEEK + loff_t spot; + return (_llseek(fd, (uint32)((offset >> 32) & 0xFFFFFFFF), (uint32)(offset & 0xFFFFFFFF), &spot, whence) >= 0) ? B_NO_ERROR : B_ERROR; +#else + return (lseek(fd, (off_t) offset, whence) >= 0) ? B_NO_ERROR : B_ERROR; +#endif + } + return B_ERROR; +} + +int64 FileDescriptorDataIO :: GetPosition() const +{ + int fd = _fd.GetFileDescriptor(); + if (fd >= 0) + { +#ifdef MUSCLE_USE_LLSEEK + loff_t spot; + return (_llseek(fd, 0, 0, &spot, SEEK_CUR) == 0) ? spot : -1; +#else + return lseek(fd, 0, SEEK_CUR); +#endif + } + return -1; +} + +}; // end namespace muscle + +#endif diff --git a/dataio/FileDescriptorDataIO.h b/dataio/FileDescriptorDataIO.h new file mode 100644 index 00000000..54608235 --- /dev/null +++ b/dataio/FileDescriptorDataIO.h @@ -0,0 +1,104 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleFileDescriptorDataIO_h +#define MuscleFileDescriptorDataIO_h + +// Might as well include there here, since any code using FileDescriptorDataIO is probably going to need them +#ifndef WIN32 +# include +# include +# include +# include +#endif + +#include "dataio/DataIO.h" + +namespace muscle { + +/** + * Data I/O to and from a file descriptor (useful for talking to Linux device drivers and the like) + */ +class FileDescriptorDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param fd The file descriptor to use. Becomes property of this FileDescriptorDataIO object. + * @param blocking determines whether to use blocking or non-blocking I/O. + * If you will be using this object with a AbstractMessageIOGateway, + * and/or select(), then it's usually better to set blocking to false. + */ + FileDescriptorDataIO(const ConstSocketRef & fd, bool blocking); + + /** Destructor. + * close()'s the held file descriptor. + */ + virtual ~FileDescriptorDataIO(); + + /** Reads bytes from the file descriptor and places them into (buffer). + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + * @see DataIO::Read() + */ + virtual int32 Read(void * buffer, uint32 size); + + /** + * Reads bytes from (buffer) and sends them out to the file descriptor. + * @param buffer Buffer to read the bytes from. + * @param size Number of bytes in the buffer. + * @return Number of bytes written, or -1 on error. + * @see DataIO::Write() + */ + virtual int32 Write(const void * buffer, uint32 size); + + /** + * Implemented as a no-op (I don't believe file descriptors need flushing?) + */ + virtual void FlushOutput(); + + /** + * Enables or disables blocking I/O on this file descriptor. + * If this object is to be used by an AbstractMessageIOGateway, + * then non-blocking I/O is usually better to use. + * @param blocking If true, file descriptor is set to blocking I/O mode. Otherwise, non-blocking I/O. + * @return B_NO_ERROR on success, B_ERROR on error. + */ + status_t SetBlockingIOEnabled(bool blocking); + + /** Returns true iff this object is using blocking I/O mode. */ + bool IsBlockingIOEnabled() const {return _blocking;} + + /** Clears our held ConstSocketRef. */ + virtual void Shutdown(); + + /** Seeks to the specified point in the file stream. + * @param offset Where to seek to. + * @param whence IO_SEEK_SET, IO_SEEK_CUR, or IO_SEEK_END. + * @return B_NO_ERROR on success, B_ERROR on failure. + */ + virtual status_t Seek(int64 offset, int whence); + + /** Returns our current position in the file */ + virtual int64 GetPosition() const; + + virtual const ConstSocketRef & GetReadSelectSocket() const {return _fd;} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _fd;} + + /** Set whether or not this object should call fsync() on our file descriptor in the FileDescriptorDataIO destructor. Defaults to false. + * @param doFsyncOnClose If true, fsync(fd) will be called in our destructor. If false (the default), it won't be. + */ + void SetFSyncOnClose(bool doFsyncOnClose) {_dofSyncOnClose = doFsyncOnClose;} + + /** Returns whether or not this object should call fsync() on our file descriptor in the FileDescriptorDataIO destructor. Defaults to false. */ + bool IsFSyncOnClose() const {return _dofSyncOnClose;} + +private: + ConstSocketRef _fd; + bool _blocking; + bool _dofSyncOnClose; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/MultiDataIO.cpp b/dataio/MultiDataIO.cpp new file mode 100644 index 00000000..c7ba7744 --- /dev/null +++ b/dataio/MultiDataIO.cpp @@ -0,0 +1,92 @@ +#include "dataio/MultiDataIO.h" + +namespace muscle { + +int32 MultiDataIO :: Read(void * buffer, uint32 size) +{ + if (HasChildren()) + { + int32 ret = GetFirstChild()->Read(buffer, size); + if (ret < 0) + { + if ((_absorbPartialErrors)&&(_childIOs.GetNumItems() > 1)) + { + (void) _childIOs.RemoveHead(); + return Read(buffer, size); // try again with the new first child + } + else return -1; + } + else if (ret > 0) return (SeekAll(1, ret, IO_SEEK_CUR)==B_NO_ERROR) ? ret : -1; + } + return 0; +} + +int32 MultiDataIO :: Write(const void * buffer, uint32 size) +{ + int64 newSeekPos = -1; // just to shut the compiler up + uint32 maxWrittenBytes = 0; + uint32 minWrittenBytes = MUSCLE_NO_LIMIT; + for (int32 i=_childIOs.GetNumItems()-1; i>=0; i--) + { + int32 childRet = _childIOs[i]()->Write(buffer, muscleMin(size, minWrittenBytes)); + if (childRet < 0) + { + if ((_absorbPartialErrors)&&(_childIOs.GetNumItems() > 1)) (void) _childIOs.RemoveItemAt(i); + else return -1; + } + else + { + if ((uint32)childRet < minWrittenBytes) + { + minWrittenBytes = childRet; + newSeekPos = _childIOs[i]()->GetPosition(); + } + maxWrittenBytes = muscleMax(maxWrittenBytes, (uint32)childRet); + } + } + + if (minWrittenBytes < maxWrittenBytes) + { + // Oh dear, some children wrote more bytes than others. To make their seek-positions equal again, + // we are going to seek everybody to the seek-position of the child that wrote the fewest bytes. + if (SeekAll(0, newSeekPos, IO_SEEK_CUR) != B_NO_ERROR) return -1; + } + + return (maxWrittenBytes > 0) ? minWrittenBytes : 0; // the conditional is there in case minWrittenBytes is still MUSCLE_NO_LIMIT +} + +void MultiDataIO :: FlushOutput() +{ + for (int32 i=_childIOs.GetNumItems()-1; i>=0; i--) _childIOs[i]()->FlushOutput(); +} + +void MultiDataIO :: WriteBufferedOutput() +{ + for (int32 i=_childIOs.GetNumItems()-1; i>=0; i--) _childIOs[i]()->WriteBufferedOutput(); +} + +uint32 MultiDataIO :: GetPacketMaximumSize() const +{ + return (HasChildren()) ? GetFirstChild()->GetPacketMaximumSize() : 0; +} + +bool MultiDataIO :: HasBufferedOutput() const +{ + for (int32 i=_childIOs.GetNumItems()-1; i>=0; i--) if (_childIOs[i]()->HasBufferedOutput()) return true; + return false; +} + +status_t MultiDataIO :: SeekAll(uint32 first, int64 offset, int whence) +{ + for (int32 i=_childIOs.GetNumItems()-1; i>=(int32)first; i--) + { + if (_childIOs[i]()->Seek(offset, whence) != B_NO_ERROR) + { + if ((_absorbPartialErrors)&&(_childIOs.GetNumItems() > 1)) (void) _childIOs.RemoveItemAt(i); + else return B_ERROR; + } + } + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/dataio/MultiDataIO.h b/dataio/MultiDataIO.h new file mode 100644 index 00000000..328840ec --- /dev/null +++ b/dataio/MultiDataIO.h @@ -0,0 +1,93 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleMultiDataIO_h +#define MuscleMultiDataIO_h + +#include "dataio/DataIO.h" +#include "util/Queue.h" + +namespace muscle { + +/** This DataIO holds a list of one or more other DataIO objects, and passes any + * calls made to all the sub-DataIOs. If an error occurs on any of the sub-objects, + * the call will error out. This class can be useful when implementing RAID-like behavior. + */ +class MultiDataIO : public DataIO, private CountedObject +{ +public: + /** Default Constructor. Be sure to add some child DataIOs to our Queue of + * DataIOs (as returned by GetChildDataIOs()) so that this object will do something useful! + */ + MultiDataIO() : _absorbPartialErrors(false) {/* empty */} + + /** Virtual destructor, to keep C++ honest. */ + virtual ~MultiDataIO() {/* empty */} + + /** Implemented to read data from the first held DataIO only. The other DataIOs will + * have Seek() called on them instead, to simulate a read without actually having + * to read their data (since we only have one memory-buffer to place it in anyway). + * @param buffer A buffer to read data into + * @param size The number of bytes that (buffer) points to + * @returns the number of bytes read, or -1 on error. + */ + virtual int32 Read(void * buffer, uint32 size); + + /** Calls Write() on all our held sub-DataIOs. + * @param buffer Pointer to a buffer of data to write + * @param size Number of bytes pointed to by (buffer). + * @returns The number of bytes written, or -1 on error. If different sub-DataIOs write different + * amounts, the minimum amount written will be returned, and the sub-DataIOs will be Seek()'d + * to the position of the sub-DataIO that wrote the fewest bytes. + */ + virtual int32 Write(const void * buffer, uint32 size); + + /** Calls Seek() on all our held sub-DataIOs. */ + virtual status_t Seek(int64 offset, int whence) {return SeekAll(0, offset, whence);} + + virtual int64 GetPosition() const {return HasChildren() ? GetFirstChild()->GetPosition() : -1;} + virtual uint64 GetOutputStallLimit() const {return HasChildren() ? GetFirstChild()->GetOutputStallLimit() : MUSCLE_TIME_NEVER;} + + virtual void FlushOutput() ; + + virtual void Shutdown() {_childIOs.Clear();} + + virtual const ConstSocketRef & GetReadSelectSocket() const {return HasChildren() ? GetFirstChild()->GetReadSelectSocket() : GetNullSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return HasChildren() ? GetFirstChild()->GetWriteSelectSocket() : GetNullSocket();} + + virtual status_t GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const {return HasChildren() ? GetFirstChild()->GetReadByteTimeStamp(whichByte, retStamp) : B_ERROR;} + + virtual bool HasBufferedOutput() const; + virtual void WriteBufferedOutput(); + virtual uint32 GetPacketMaximumSize() const; + + /** Returns a read-only reference to our list of child DataIO objects. */ + const Queue & GetChildDataIOs() const {return _childIOs;} + + /** Returns a read/write reference to our list of child DataIO objects. */ + Queue & GetChildDataIOs() {return _childIOs;} + + /** Sets whether an error condition in a child should be handled simply by removing the child, + * or whether the error should be immediately propagated upwards. Default value is false. + * @param ape If true, any child DataIO object that reports an error will be silently removed + * from the list of child DataIOs. No errors will be reported from this MultiDataIO + * object unless/until the child DataIO list is reduced to a single child DataIO, and + * that child DataIO errors out. If false (the default state), then any error from + * any child DataIO will cause this MultiDataIO object to return an error immediately. + */ + void SetAbsorbPartialErrors(bool ape) {_absorbPartialErrors = ape;} + + /** Returns true iff the absorb-partial-errors flag has been set. */ + bool IsAbsorbPartialErrors() const {return _absorbPartialErrors;} + +private: + bool HasChildren() const {return (_childIOs.HasItems());} + DataIO * GetFirstChild() const {return (_childIOs.Head()());} + status_t SeekAll(uint32 first, int64 offset, int whence); + + Queue _childIOs; + bool _absorbPartialErrors; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/NullDataIO.h b/dataio/NullDataIO.h new file mode 100644 index 00000000..782b66ab --- /dev/null +++ b/dataio/NullDataIO.h @@ -0,0 +1,74 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleNullDataIO_h +#define MuscleNullDataIO_h + +#include "dataio/DataIO.h" + +namespace muscle { + +/** + * Data I/O equivalent to /dev/null. + */ +class NullDataIO : public DataIO, private CountedObject +{ +public: + /** Constructor. + * @param readSelectSocket Optional ConstSocketRef to return in GetReadSelectSocket(). Defaults to a NULL ref. + * @param writeSelectSocket Optional ConstSocketRef to return in GetWriteSelectSocket(). Defaults to a NULL ref. + */ + NullDataIO(const ConstSocketRef & readSelectSocket = ConstSocketRef(), const ConstSocketRef & writeSelectSocket = ConstSocketRef()) : _readSelectSocket(readSelectSocket), _writeSelectSocket(writeSelectSocket), _shutdown(false) {/* empty */} + + /** Virtual Destructor, to keep C++ honest */ + virtual ~NullDataIO() {/* empty */} + + /** + * No-op method, always returns zero (except if Shutdown() was called). + * @param buffer Points to a buffer to read bytes into (ignored). + * @param size Number of bytes in the buffer (ignored). + * @return zero. + */ + virtual int32 Read(void * buffer, uint32 size) {(void) buffer; (void) size; return _shutdown ? -1 : 0;} + + /** + * No-op method, always returns (size) (except if Shutdown() was called). + * @param buffer Points to a buffer to write bytes from (ignored). + * @param size Number of bytes in the buffer (ignored). + * @return (size). + */ + virtual int32 Write(const void * buffer, uint32 size) {(void) buffer; return _shutdown ? -1 : (int32)size;} + + /** + * This method always returns B_ERROR. + */ + virtual status_t Seek(int64 /*seekOffset*/, int /*whence*/) {return B_ERROR;} + + /** + * This method always return -1. + */ + virtual int64 GetPosition() const {return -1;} + + /** + * No-op method. + * This method doesn't do anything at all. + */ + virtual void FlushOutput() {/* empty */} + + /** Disable us! */ + virtual void Shutdown() {_shutdown = true;} + + /** Returns the read socket specified in our constructor (if any) */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return _readSelectSocket;} + + /** Returns the write socket specified in our constructor (if any) */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _writeSelectSocket;} + +private: + ConstSocketRef _readSelectSocket; + ConstSocketRef _writeSelectSocket; + bool _shutdown; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/PacketizedDataIO.cpp b/dataio/PacketizedDataIO.cpp new file mode 100644 index 00000000..6f49d12d --- /dev/null +++ b/dataio/PacketizedDataIO.cpp @@ -0,0 +1,108 @@ +#include "dataio/PacketizedDataIO.h" + +namespace muscle { + +PacketizedDataIO :: PacketizedDataIO(const DataIORef & slaveIO, uint32 maxTransferUnit) : _slaveIO(slaveIO), _maxTransferUnit(maxTransferUnit), _inputBufferSize(0), _inputBufferSizeBytesRead(0), _inputBufferBytesRead(0), _outputBufferBytesSent(0) +{ + // empty +} + +int32 PacketizedDataIO :: Read(void * buffer, uint32 size) +{ + int32 ret = 0; + + if (_inputBufferSizeBytesRead < sizeof(uint32)) + { + uint8 * ip = (uint8 *) &_inputBufferSize; + int32 numSizeBytesRead = SlaveRead(&ip[_inputBufferSizeBytesRead], sizeof(uint32)-_inputBufferSizeBytesRead); + if (numSizeBytesRead < 0) return -1; + _inputBufferSizeBytesRead += numSizeBytesRead; + if (_inputBufferSizeBytesRead == sizeof(uint32)) + { + _inputBufferSize = B_LENDIAN_TO_HOST_INT32(_inputBufferSize); + if (_inputBufferSize > _maxTransferUnit) + { + LogTime(MUSCLE_LOG_ERROR, "PacketizedDataIO: Error, incoming packet with size " UINT32_FORMAT_SPEC ", max transfer unit is set to " UINT32_FORMAT_SPEC "\n", _inputBufferSize, _maxTransferUnit); + return -1; + } + if (_inputBuffer.SetNumBytes(_inputBufferSize, false) != B_NO_ERROR) return -1; + _inputBufferBytesRead = 0; + + // Special case for empty packets + if (_inputBufferSize == 0) _inputBufferSizeBytesRead = 0; + } + } + + uint32 inBufSize = _inputBuffer.GetNumBytes(); + if ((_inputBufferSizeBytesRead == sizeof(uint32))&&(_inputBufferBytesRead < inBufSize)) + { + int32 numBytesRead = SlaveRead(_inputBuffer.GetBuffer()+_inputBufferBytesRead, inBufSize-_inputBufferBytesRead); + if (numBytesRead < 0) return -1; + + _inputBufferBytesRead += numBytesRead; + if (_inputBufferBytesRead == inBufSize) + { + uint32 copyBytes = muscleMin(size, inBufSize); + if (size < inBufSize) LogTime(MUSCLE_LOG_WARNING, "PacketizedDataIO: Truncating incoming packet (" UINT32_FORMAT_SPEC " bytes available, only " UINT32_FORMAT_SPEC " bytes in user buffer)\n", inBufSize, size); + memcpy(buffer, _inputBuffer.GetBuffer(), copyBytes); + ret = copyBytes; + + _inputBufferSizeBytesRead = _inputBufferBytesRead = 0; + _inputBuffer.Clear(inBufSize>(64*1024)); // free up memory after a large packet recv + } + } + return ret; +} + +int32 PacketizedDataIO :: Write(const void * buffer, uint32 size) +{ + if (size > _maxTransferUnit) + { + LogTime(MUSCLE_LOG_ERROR, "PacketizedDataIO: Error, tried to send packet with size " UINT32_FORMAT_SPEC ", max transfer unit is set to " UINT32_FORMAT_SPEC "\n", size, _maxTransferUnit); + return -1; + } + + // Only accept more data if we are done sending the data we already have buffered up + bool tryAgainAfter = false; + int32 ret = 0; + if (HasBufferedOutput()) tryAgainAfter = true; + else + { + // No data buffered? + _outputBufferBytesSent = 0; + + if (_outputBuffer.SetNumBytes(sizeof(uint32)+size, false) != B_NO_ERROR) return 0; + *((uint32 *)_outputBuffer.GetBuffer()) = B_HOST_TO_LENDIAN_INT32(size); + memcpy(_outputBuffer.GetBuffer()+sizeof(uint32), buffer, size); + ret = size; + } + + if (WriteBufferedOutputAux() != B_NO_ERROR) + { + return -1; + } + return ((tryAgainAfter)&&(HasBufferedOutput() == false)) ? Write(buffer, size) : ret; +} + +status_t PacketizedDataIO :: WriteBufferedOutputAux() +{ + // Now try to send as much of our buffered output data as we can + uint32 bufSize = _outputBuffer.GetNumBytes(); + if (_outputBufferBytesSent < bufSize) + { + int32 bytesSent = SlaveWrite(_outputBuffer.GetBuffer()+_outputBufferBytesSent, bufSize-_outputBufferBytesSent); + if (bytesSent >= 0) + { + _outputBufferBytesSent += bytesSent; + if (_outputBufferBytesSent == bufSize) + { + _outputBuffer.Clear(bufSize>(64*1024)); // free up memory after a large packet send + _outputBufferBytesSent = 0; + } + } + else return B_ERROR; + } + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/dataio/PacketizedDataIO.h b/dataio/PacketizedDataIO.h new file mode 100644 index 00000000..4890ddab --- /dev/null +++ b/dataio/PacketizedDataIO.h @@ -0,0 +1,78 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MusclePacketizedDataIO_h +#define MusclePacketizedDataIO_h + +#include "dataio/DataIO.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +/** + * This class can be used to "wrap" a streaming I/O object (for example a TCPSocketDataIO) in order to make + * it appear like a packet-based I/O object (e.g. a UDPSocketDataIO) to the calling code. + * + * It does this by inserting message-length fields into the outgoing byte stream, and parsing + * message-length fields from the incoming byte stream, so that data will be returned in Read() + * in the same chunk sizes that it was originally passed in to Write() on the other end. Note + * that this does change the underlying byte-stream protocol, so the receiver must be aware + * of the change (typically by having the receiver wrap his DataIO object in a PacketizedDataIO + * object also) + * + * You might use this class to simulate a lossless UDP connection by "tunneling" UDP over TCP. + */ +class PacketizedDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param slaveIO The underlying streaming DataIO object to pass calls on through to. + * @param maxTransferUnit the maximum "packet size" we should support. Calls to + * Read() or Write() with more than this many bytes specified will be truncated, + * analogous to the way too-large packets are handled by UDP. + */ + PacketizedDataIO(const DataIORef & slaveIO, uint32 maxTransferUnit = MUSCLE_NO_LIMIT); + + /** Returns a reference to our underlying DataIO object. */ + const DataIORef & GetSlaveIO() const {return _slaveIO;} + + /** Sets our underlying Data I/O object. Use with caution! */ + void SetSlaveIO(const DataIORef & sio) {_slaveIO = sio;} + + /** Returns the maximum "packet size" we will be willing to send or receive. Defaults to MUSCLE_NO_LIMIT. */ + uint32 GetMaxTransferUnit() const {return _maxTransferUnit;} + + virtual int32 Read(void * buffer, uint32 size); + virtual int32 Write(const void * buffer, uint32 size); + virtual status_t Seek(int64 offset, int whence) {return _slaveIO() ? _slaveIO()->Seek(offset, whence) : B_ERROR;} + virtual int64 GetPosition() const {return _slaveIO() ? _slaveIO()->GetPosition() : -1;} + virtual uint64 GetOutputStallLimit() const {return _slaveIO() ? _slaveIO()->GetOutputStallLimit() : MUSCLE_TIME_NEVER;} + virtual void FlushOutput() {if (_slaveIO()) _slaveIO()->FlushOutput();} + virtual void Shutdown() {if (_slaveIO()) _slaveIO()->Shutdown(); _slaveIO.Reset(); _outputBuffer.Clear(true); _inputBuffer.Clear(true); _inputBufferSizeBytesRead = 0;} + virtual const ConstSocketRef & GetReadSelectSocket() const {return _slaveIO() ? _slaveIO()->GetReadSelectSocket() : GetNullSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _slaveIO() ? _slaveIO()->GetWriteSelectSocket() : GetNullSocket();} + virtual int64 GetLength() {return _slaveIO() ? _slaveIO()->GetLength() : -1;} + + virtual bool HasBufferedOutput() const {return (_outputBufferBytesSent < _outputBuffer.GetNumBytes());} + virtual void WriteBufferedOutput() {(void) WriteBufferedOutputAux();} + +private: + int32 SlaveRead(void * buffer, uint32 size) {return _slaveIO() ? _slaveIO()->Read(buffer, size) : -1;} + int32 SlaveWrite(const void * buffer, uint32 size) {return _slaveIO() ? _slaveIO()->Write(buffer, size) : -1;} + status_t WriteBufferedOutputAux(); + + DataIORef _slaveIO; + uint32 _maxTransferUnit; + + ByteBuffer _inputBuffer; + uint32 _inputBufferSize; + uint32 _inputBufferSizeBytesRead; + uint32 _inputBufferBytesRead; + + ByteBuffer _outputBuffer; + uint32 _outputBufferBytesSent; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/RS232DataIO.cpp b/dataio/RS232DataIO.cpp new file mode 100644 index 00000000..87db384c --- /dev/null +++ b/dataio/RS232DataIO.cpp @@ -0,0 +1,575 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/RS232DataIO.h" + +#if defined(__APPLE__) +# include +# include +# include +# include +#endif + +#if defined(WIN32) || defined(__CYGWIN__) +# include // for _beginthreadex() +# include "util/Queue.h" +# define USE_WINDOWS_IMPLEMENTATION +#else +# include +# include +#endif + +#include "util/NetworkUtilityFunctions.h" + +namespace muscle { + +RS232DataIO :: RS232DataIO(const char * port, uint32 baudRate, bool blocking) : _blocking(blocking) +#ifdef USE_WINDOWS_IMPLEMENTATION + , _handle(INVALID_HANDLE_VALUE) + , _ioThread(INVALID_HANDLE_VALUE) + , _wakeupSignal(INVALID_HANDLE_VALUE) + , _requestThreadExit(false) +#endif +{ + bool okay = false; + +#ifdef USE_WINDOWS_IMPLEMENTATION + memset(&_ovWait, 0, sizeof(_ovWait)); + memset(&_ovRead, 0, sizeof(_ovRead)); + memset(&_ovWrite, 0, sizeof(_ovWrite)); + _handle = CreateFileA(port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + if (_handle != INVALID_HANDLE_VALUE) + { + SetupComm(_handle, 32768, 32768); + + DCB dcb; + dcb.DCBlength = sizeof(DCB); + GetCommState((void *)_handle, &dcb); + + char modebuf[128]; sprintf(modebuf, "%s baud="UINT32_FORMAT_SPEC" parity=N data=8 stop=1", port, baudRate); + if (BuildCommDCBA(modebuf, &dcb)) + { + dcb.fBinary = 1; + dcb.fOutX = 0; + dcb.fInX = 0; + dcb.fErrorChar = 0xfe; + dcb.fTXContinueOnXoff = 0; + dcb.fOutxCtsFlow = 0; + dcb.fOutxDsrFlow = 0; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fDsrSensitivity = 0; + dcb.fNull = 0; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + dcb.fAbortOnError = 0; + if (SetCommState(_handle, &dcb)) + { + COMMTIMEOUTS tmout; + tmout.ReadIntervalTimeout = MAXDWORD; + tmout.ReadTotalTimeoutMultiplier = 0; + tmout.ReadTotalTimeoutConstant = 0; + tmout.WriteTotalTimeoutMultiplier = 0; + tmout.WriteTotalTimeoutConstant = 0; + if ((SetCommTimeouts(_handle, &tmout))&&(SetCommMask(_handle, EV_TXEMPTY|EV_RXCHAR))) + { + if (_blocking == false) + { + // Oops, in non-blocking mode we'll need to spawn a separate thread to manage the I/O. Fun! + _wakeupSignal = CreateEvent(0, false, false, 0); + _ovWait.hEvent = CreateEvent(0, true, false, 0); + _ovRead.hEvent = CreateEvent(0, true, false, 0); + _ovWrite.hEvent = CreateEvent(0, true, false, 0); + if ((_wakeupSignal != INVALID_HANDLE_VALUE)&&(_ovWait.hEvent != INVALID_HANDLE_VALUE)&&(_ovRead.hEvent != INVALID_HANDLE_VALUE)&&(_ovWrite.hEvent != INVALID_HANDLE_VALUE)&&(CreateConnectedSocketPair(_masterNotifySocket, _slaveNotifySocket, false) == B_NO_ERROR)) + { + DWORD junkThreadID; + typedef unsigned (__stdcall *PTHREAD_START) (void *); + if ((_ioThread = (::HANDLE) _beginthreadex(NULL, 0, (PTHREAD_START)IOThreadEntryFunc, this, 0, (unsigned *) &junkThreadID)) != INVALID_HANDLE_VALUE) okay = true; + } + } + else okay = true; + } + } + } + } +#else +# if defined(__BEOS__) || defined(__HAIKU__) + _handle = GetConstSocketRefFromPool(open(port, O_RDWR | O_NONBLOCK)); +# else + _handle = GetConstSocketRefFromPool(open(port, O_RDWR | O_NOCTTY)); +# endif + if (SetSocketBlockingEnabled(_handle, _blocking) == B_NO_ERROR) + { + okay = true; + + int fd = _handle.GetFileDescriptor(); + + struct termios t; + tcgetattr(fd, &t); + switch(baudRate) + { + case 1200: + cfsetospeed(&t, B1200); + cfsetispeed(&t, B1200); + break; + case 9600: + cfsetospeed(&t, B9600); + cfsetispeed(&t, B9600); + break; + case 19200: + cfsetospeed(&t, B19200); + cfsetispeed(&t, B19200); + break; + case 38400: + cfsetospeed(&t, B38400); + cfsetispeed(&t, B38400); + break; + case 57600: + cfsetospeed(&t, B57600); + cfsetispeed(&t, B57600); + break; + case 115200: + cfsetospeed(&t, B115200); + cfsetispeed(&t, B115200); + break; + default: + okay = false; // unknown baud rate! + break; + } + if (okay) + { + t.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN); + t.c_iflag &= ~(INPCK | ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON); +#if !defined(__BEOS__) && !defined(__HAIKU__) + t.c_iflag &= ~(IMAXBEL); +#endif + t.c_iflag |= (IGNBRK); + t.c_cflag &= ~(HUPCL | PARENB | CRTSCTS | CSIZE); + t.c_cflag |= (CS8 | CLOCAL); + t.c_oflag &= ~OPOST; + tcsetattr(fd, TCSANOW, &t); + } + } +#endif + + if (okay == false) Close(); +} + +RS232DataIO :: ~RS232DataIO() +{ + Close(); +} + +bool RS232DataIO :: IsPortAvailable() const +{ +#ifdef USE_WINDOWS_IMPLEMENTATION + return (_handle != INVALID_HANDLE_VALUE); +#else + return (_handle.GetFileDescriptor() >= 0); +#endif +} + +void RS232DataIO :: Close() +{ +#ifdef USE_WINDOWS_IMPLEMENTATION + if (_ioThread != INVALID_HANDLE_VALUE) // if this is valid, _wakeupSignal is guaranteed valid too + { + _requestThreadExit = true; // set the "Please go away" flag + SetEvent(_wakeupSignal); // wake the thread up so he'll check the bool + WaitForSingleObject(_ioThread, INFINITE); // then wait for him to go away + ::CloseHandle(_ioThread); // fix handle leak + _ioThread = INVALID_HANDLE_VALUE; + } + _masterNotifySocket.Reset(); + _slaveNotifySocket.Reset(); + if (_wakeupSignal != INVALID_HANDLE_VALUE) {CloseHandle(_wakeupSignal); _wakeupSignal = INVALID_HANDLE_VALUE;} + if (_handle != INVALID_HANDLE_VALUE) {CloseHandle(_handle); _handle = INVALID_HANDLE_VALUE;} + if (_ovWait.hEvent != INVALID_HANDLE_VALUE) {CloseHandle(_ovWait.hEvent); _ovWait.hEvent = INVALID_HANDLE_VALUE;} + if (_ovRead.hEvent != INVALID_HANDLE_VALUE) {CloseHandle(_ovRead.hEvent); _ovRead.hEvent = INVALID_HANDLE_VALUE;} + if (_ovWrite.hEvent != INVALID_HANDLE_VALUE) {CloseHandle(_ovWrite.hEvent); _ovWrite.hEvent = INVALID_HANDLE_VALUE;} +#else + _handle.Reset(); +#endif +} + +int32 RS232DataIO :: Read(void *buf, uint32 len) +{ + if (IsPortAvailable()) + { +#ifdef USE_WINDOWS_IMPLEMENTATION + if (_blocking) + { + DWORD actual_read; + if (ReadFile(_handle, buf, len, &actual_read, 0)) return actual_read; + } + else + { + int32 ret = ReceiveData(_masterNotifySocket, buf, len, _blocking); + if (ret >= 0) SetEvent(_wakeupSignal); // wake up the thread in case he has more data to give us + return ret; + } +#else + return ReadData(_handle, buf, len, _blocking); +#endif + } + return -1; +} + +int32 RS232DataIO :: Write(const void *buf, uint32 len) +{ + if (IsPortAvailable()) + { +#ifdef USE_WINDOWS_IMPLEMENTATION + if (_blocking) + { + DWORD actual_write; + if (WriteFile(_handle, buf, len, &actual_write, 0)) return actual_write; + } + else + { + int32 ret = SendData(_masterNotifySocket, buf, len, _blocking); + if (ret > 0) SetEvent(_wakeupSignal); // wake up the thread so he'll check his socket for our new data + return ret; + } +#else + return WriteData(_handle, buf, len, _blocking); +#endif + } + return -1; +} + +void RS232DataIO :: FlushOutput() +{ + if (IsPortAvailable()) + { +#ifdef USE_WINDOWS_IMPLEMENTATION + // not implemented yet! +#else + int fd = _handle.GetFileDescriptor(); + if (fd >= 0) tcdrain(fd); +#endif + } +} + +const ConstSocketRef & RS232DataIO :: GetSerialSelectSocket() const +{ +#ifdef USE_WINDOWS_IMPLEMENTATION + return _blocking ? GetNullSocket() : _masterNotifySocket; +#else + return _handle; +#endif +} + +void RS232DataIO :: Shutdown() +{ + Close(); +} + +status_t RS232DataIO :: GetAvailableSerialPortNames(Queue & retList) +{ +#ifdef USE_WINDOWS_IMPLEMENTATION + // This implementation stolen from PJ Naughter's (pjn@indigo.ie) post + // at http://www.codeproject.com/system/enumports.asp + // + // Under NT-based versions of Windows, use the QueryDosDevice API, since it's more efficient + OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (GetVersionEx(&osvi)&&(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)) + { + char szDevices[65535]; + DWORD dwChars = QueryDosDeviceA(NULL, szDevices, 65535); + if (dwChars) + { + for (uint32 i=0; szDevices[i] != '\0';) + { + const char * pszCurrentDevice = &szDevices[i]; + if (strncmp(pszCurrentDevice, "COM", 3) == 0) retList.AddTail(pszCurrentDevice); + + // Go to next NULL character + while(szDevices[i] != '\0') i++; + i++; // Bump pointer to the beginning of the next string + } + return B_NO_ERROR; + } + return B_ERROR; + } + else + { + // On 95/98 open up each port to determine their existence + // Up to 255 COM ports are supported so we iterate through all of them seeing + // if we can open them or if we fail to open them, get an access denied or general error error. + // Both of these cases indicate that there is a COM port at that number. + for (uint32 i=1; i<256; i++) + { + // Try to open the port + char buf[128]; sprintf(buf, "COM"UINT32_FORMAT_SPEC, i); + ::HANDLE hPort = CreateFileA(buf, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); + if (hPort == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + if ((err == ERROR_ACCESS_DENIED)||(err == ERROR_GEN_FAILURE)) retList.AddTail(buf); // acceptable errors + } + else + { + retList.AddTail(buf); // success! + CloseHandle(hPort); + } + } + return B_NO_ERROR; + } +#else +# if defined(__APPLE__) + mach_port_t masterPort; + if (IOMasterPort(MACH_PORT_NULL, &masterPort) == KERN_SUCCESS) + { + CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (classesToMatch) + { + CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type)); + io_iterator_t serialPortIterator; + if (IOServiceGetMatchingServices(masterPort, classesToMatch, &serialPortIterator) == KERN_SUCCESS) + { + io_object_t modemService; + while((modemService = IOIteratorNext(serialPortIterator)) != MACH_PORT_NULL) + { + CFTypeRef bsdPathAsCFString = IORegistryEntryCreateCFProperty(modemService, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); + if (bsdPathAsCFString) + { + char bsdPath[256] = ""; + if (CFStringGetCString((CFStringRef)bsdPathAsCFString, bsdPath, sizeof(bsdPath), kCFStringEncodingASCII)) retList.AddTail(bsdPath); + CFRelease(bsdPathAsCFString); + } + (void) IOObjectRelease(modemService); + } + IOObjectRelease(serialPortIterator); + return B_NO_ERROR; + } + } + } + return B_ERROR; // if we got here, it didn't work +# else + for (int i=0; /*empty*/; i++) + { + char buf[64]; +# if defined(__BEOS__) || defined(__HAIKU__) + sprintf(buf, "/dev/ports/serial%i", i+1); + int temp = open(buf, O_RDWR | O_NONBLOCK); +# else + sprintf(buf, "/dev/ttyS%i", i); + int temp = open(buf, O_RDWR | O_NOCTTY); +# endif + if (temp >= 0) + { + close(temp); + retList.AddTail(buf); + } + else break; + } + return B_NO_ERROR; +# endif +#endif +} + + +#ifdef USE_WINDOWS_IMPLEMENTATION + +const uint32 SERIAL_BUFFER_SIZE = 1024; + +// Used as a temporary holding area for data (since we can't just block, or the serial port +// buffers will overflow and data will be lost!) +class SerialBuffer +{ +public: + SerialBuffer() : _length(0), _index(0) {/* empty */} + + char _buf[SERIAL_BUFFER_SIZE]; + uint32 _length; // how many bytes in _buf are actually valid + uint32 _index; // Index of the next byte to process +}; + +// Called when we have finished reading some bytes in from the serial port. Adds the bytes to +// our queue of incoming serial bytes. (numBytesRead) MUST be <= SERIAL_BUFFER_SIZE ! +static void ProcessReadBytes(Queue & inQueue, const char * inBytes, uint32 numBytesRead) +{ + MASSERT(numBytesRead <= SERIAL_BUFFER_SIZE, "ProcessReadBytes: numBytesRead was too large!"); + + SerialBuffer * buf = (inQueue.HasItems()) ? inQueue.Tail() : NULL; + if ((buf)&&((sizeof(buf->_buf)-buf->_length) >= numBytesRead)) + { + // this buffer has enough room left to just add the extra bytes into it. + // No need to allocate a new SerialBuffer! + memcpy(&buf->_buf[buf->_length], inBytes, numBytesRead); + buf->_length += numBytesRead; + } + else + { + // Oops, not enough room. We'll allocate a new SerialBuffer object instead. + buf = newnothrow SerialBuffer; + if ((buf)&&(inQueue.AddTail(buf) == B_NO_ERROR)) + { + memcpy(&buf->_buf, inBytes, numBytesRead); + buf->_length = numBytesRead; + } + else + { + WARN_OUT_OF_MEMORY; + delete buf; + } + } +} + +// Called when we have finished writing some bytes out to the serial port. +static void ProcessWriteBytes(SerialBuffer & buf, uint32 numBytesWritten) +{ + buf._index += numBytesWritten; + if (buf._index == buf._length) buf._index = buf._length = 0; // the buffer is done, so reset it +} + +void RS232DataIO :: IOThreadEntry() +{ + SerialBuffer inBuf; // bytes from the serial port, waiting to go into the inQueue + SerialBuffer outBuf; // bytes from the user socket, waiting to go to the serial port + Queue inQueue; // bytes from inBuf, waiting to go to the user socket + + uint32 pendingReadBytes = 0; + uint32 pendingWriteBytes = 0; + bool isWaiting = false; + bool checkRead = false; + ::HANDLE events[] = {_ovWait.hEvent, _ovRead.hEvent, _ovWrite.hEvent, _wakeupSignal}; // order is important!!! + while(_requestThreadExit == false) + { + if (isWaiting == false) + { + // Tell the system to start any I/O... + DWORD eventMask; + if (WaitCommEvent(_handle, &eventMask, &_ovWait)) + { + if (eventMask & EV_RXCHAR) checkRead = true; + } + else + { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) isWaiting = true; + else LogTime(MUSCLE_LOG_ERROR, "WaitCommEvent() failed! errorCode="INT32_FORMAT_SPEC"\n", err); + } + } + + bool doResetEvent = false; + switch(WaitForMultipleObjects(ARRAYITEMS(events), events, false, INFINITE)-WAIT_OBJECT_0) + { + case 0: // ovWait + { + isWaiting = false; + doResetEvent = true; + DWORD eventMask; + if ((GetCommMask(_handle,&eventMask))&&(eventMask & EV_RXCHAR)) checkRead = true; + } + break; + + case 1: // ovRead + { + if (pendingReadBytes > 0) + { + ProcessReadBytes(inQueue, inBuf._buf, pendingReadBytes); + pendingReadBytes = 0; + } + ResetEvent(_ovRead.hEvent); + } + break; + + case 2: // ovWrite + { + if (pendingWriteBytes >= 0) + { + ProcessWriteBytes(outBuf, pendingWriteBytes); + pendingWriteBytes = 0; + } + ResetEvent(_ovWrite.hEvent); + } + break; + + case 3: // wakeupSignal + // empty + break; + } + + + // Dump serial data into inQueue as much as possible... + if ((pendingReadBytes == 0)&&(checkRead)) + { + while(true) + { + int32 numBytesToRead = sizeof(inBuf._buf); + DWORD numBytesRead; + if (ReadFile(_handle, inBuf._buf, numBytesToRead, &numBytesRead, &_ovRead)) + { + if (numBytesRead > 0) ProcessReadBytes(inQueue, inBuf._buf, numBytesRead); + else break; + } + else + { + if (GetLastError() == ERROR_IO_PENDING) pendingReadBytes = numBytesToRead; + break; + } + } + checkRead = false; + } + + // Dump inQueue into our slave socket as much as possible (this will signal the user thread, too) + while(inQueue.HasItems()) + { + SerialBuffer * buf = inQueue.Head(); + int32 bytesToWrite = buf->_length-buf->_index; + int32 bytesWritten = (bytesToWrite > 0) ? SendData(_slaveNotifySocket, &buf->_buf[buf->_index], bytesToWrite, false) : 0; + if (bytesWritten > 0) + { + buf->_index += bytesWritten; + if (buf->_index == buf->_length) + { + (void) inQueue.RemoveHead(); + delete buf; + } + } + else break; + } + + // Dump outgoing data to serial buffer as much as possible + if (pendingWriteBytes == 0) + { + while(1) + { + bool keepGoing = false; + + // fill up the outBuf with as many more bytes as possible... + int32 numBytesToRead = sizeof(outBuf._buf)-outBuf._length; + int32 numBytesRead = (numBytesToRead > 0) ? ReceiveData(_slaveNotifySocket, &outBuf._buf[outBuf._length], numBytesToRead, false) : 0; + if (numBytesRead > 0) outBuf._length += numBytesRead; + + // Try to write the bytes from outBuf to the serial port + int32 numBytesToWrite = outBuf._length-outBuf._index; + if (numBytesToWrite > 0) + { + DWORD numBytesWritten; + if (WriteFile(_handle, &outBuf._buf[outBuf._index], numBytesToWrite, &numBytesWritten, &_ovWrite)) + { + if (numBytesWritten > 0) + { + ProcessWriteBytes(outBuf, numBytesWritten); + keepGoing = true; // see if we can write some more.... + } + } + else + { + if (GetLastError() == ERROR_IO_PENDING) pendingWriteBytes = numBytesToWrite; + else LogTime(MUSCLE_LOG_ERROR, "RS232SerialDataIO: WriteFile() failed! err="INT32_FORMAT_SPEC"\n", GetLastError()); + } + } + if (keepGoing == false) break; + } + } + + if (doResetEvent) ResetEvent(_ovWait.hEvent); + } + + // Clean up! + for (int32 i=inQueue.GetNumItems()-1; i>=0; i--) delete inQueue[i]; +} +#endif + +}; // end namespace muscle diff --git a/dataio/RS232DataIO.h b/dataio/RS232DataIO.h new file mode 100644 index 00000000..d345f500 --- /dev/null +++ b/dataio/RS232DataIO.h @@ -0,0 +1,95 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef RS232DataIO_h +#define RS232DataIO_h + +#include +#include "dataio/DataIO.h" +#include "util/String.h" +#include "util/Queue.h" + +namespace muscle { + +/** A serial port DataIO for serial communications. Note that + * this class currently only works under Windows, OS/X and Linux, and offers + * only minimal control of the serial parameters (baud rate only at the moment). + * On the plus side, it provides a serial-port-socket for use with select(), even under Windows. + */ +class RS232DataIO : public DataIO, private CountedObject +{ +public: + /** Constructor. + * @param portName The port to open for this IO gateway to use. + * @param baudRate The baud rate to communicate at. + * @param blocking If true, I/O will be blocking; else non-blocking. + */ + RS232DataIO(const char * portName, uint32 baudRate, bool blocking); + + /** Destructor */ + virtual ~RS232DataIO(); + + virtual int32 Read(void * buffer, uint32 size); + + virtual int32 Write(const void * buffer, uint32 size); + + /** Always returns B_ERROR, since you can't seek on a serial port! */ + virtual status_t Seek(int64, int) {return B_ERROR;} + + /** Always returns -1, since a serial port has no position to speak of */ + virtual int64 GetPosition() const {return -1;} + + /** Doesn't return until all outgoing serial bytes have been sent */ + virtual void FlushOutput(); + + /** Closes our held serial port */ + virtual void Shutdown(); + + /** Returns a socket that can be select()'d on for notifications of read availability. + * Even works under Windows (in non-blocking mode, anyway), despite Microsoft's best efforts + * to make such a thing impossible :^P Note that you should only use this socket with select(); + * to read from the serial port, call Read() instead. + */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return GetSerialSelectSocket();} + + /** Returns a socket that can be select()'d on for notifications of write availability. + * Even works under Windows (in non-blocking mode, anyway), despite Microsoft's best efforts + * to make such a thing impossible :^P Note that you should only use this socket with select(); + * to write to the serial port, call Write() instead. + */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetSerialSelectSocket();} + + /** Returns true iff we have a valid serial port to communicate through */ + bool IsPortAvailable() const; + + /** Returns a list of serial port names that are present on this machine. + * These names may be passed in to the constructor of this class verbatim. + * @param retList On success, this list will contain serial port names. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + static status_t GetAvailableSerialPortNames(Queue & retList); + +private: + void Close(); + const ConstSocketRef & GetSerialSelectSocket() const; + + bool _blocking; +#if defined(WIN32) || defined(CYGWIN) + void IOThreadEntry(); + static DWORD WINAPI IOThreadEntryFunc(LPVOID This) {((RS232DataIO*)This)->IOThreadEntry(); return 0;} + ::HANDLE _handle; + ::HANDLE _ioThread; + ::HANDLE _wakeupSignal; + OVERLAPPED _ovWait; + OVERLAPPED _ovRead; + OVERLAPPED _ovWrite; + ConstSocketRef _masterNotifySocket; + ConstSocketRef _slaveNotifySocket; + volatile bool _requestThreadExit; +#else + ConstSocketRef _handle; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/SSLSocketDataIO.cpp b/dataio/SSLSocketDataIO.cpp new file mode 100644 index 00000000..ce37a7fd --- /dev/null +++ b/dataio/SSLSocketDataIO.cpp @@ -0,0 +1,201 @@ +#include "dataio/FileDataIO.h" +#include "dataio/SSLSocketDataIO.h" +#include "dataio/SSLSocketDataIO.h" + +// keep these AFTER the MUSCLE includes, or Windows throws a fit +#include +#include +#include + +namespace muscle { + +#define CAST_SSL ((SSL*)_ssl) +#define CAST_CTX ((SSL_CTX*)_ctx) + +SSLSocketDataIO :: SSLSocketDataIO(const ConstSocketRef & sockfd, bool blocking, bool accept) : TCPSocketDataIO(sockfd, blocking), _sslState(0), _forceReadReady(false) +{ + bool ok = false; + ConstSocketRef tempSocket; // yes, it's intentional that this socket will be closed as soon as we live this scope + if (CreateConnectedSocketPair(tempSocket, _alwaysReadableSocket) == B_NO_ERROR) + { + _ctx = SSL_CTX_new(SSLv3_method()); // Using SSLv3_method() instead of SSLv23_method to avoid errors from SSL_pending() + if (_ctx) + { + if (!blocking) SSL_CTX_set_mode(CAST_CTX, SSL_MODE_ENABLE_PARTIAL_WRITE); + + _ssl = SSL_new(CAST_CTX); + if (_ssl) + { + BIO * sbio = BIO_new_socket(sockfd.GetFileDescriptor(), BIO_NOCLOSE); + if (sbio) + { + BIO_set_nbio(sbio, !blocking); + SSL_set_bio(CAST_SSL, sbio, sbio); + + if (accept) SSL_set_accept_state(CAST_SSL); + else SSL_set_connect_state(CAST_SSL); + + ERR_print_errors_fp(stderr); + + ok = true; + } + else LogTime(MUSCLE_LOG_ERROR, "SSLSocketDataIO: BIO_new_socket() failed!\n"); + } + else LogTime(MUSCLE_LOG_ERROR, "SSLSocketDataIO: SSL_new() failed!\n"); + } + else LogTime(MUSCLE_LOG_ERROR, "SSLSocketDataIO: SSL_CTX_new() failed!\n"); + } + else LogTime(MUSCLE_LOG_ERROR, "SSLSocketDataIO: Error setting up dummy socket pair!\n"); + + if (ok == false) Shutdown(); // might as well clean up now +} + +SSLSocketDataIO :: ~SSLSocketDataIO() +{ + Shutdown(); +} + +void SSLSocketDataIO :: Shutdown() +{ + if (_ssl) {SSL_shutdown(CAST_SSL); SSL_free(CAST_SSL); _ssl = NULL;} + if (_ctx) {SSL_CTX_free(CAST_CTX); _ctx = NULL;} + TCPSocketDataIO::Shutdown(); +} + +status_t SSLSocketDataIO :: SetPrivateKey(const char * path) +{ + return ((_ssl)&&(SSL_use_PrivateKey_file(CAST_SSL, path, SSL_FILETYPE_PEM) == 1)) ? B_NO_ERROR : B_ERROR; +} + +status_t SSLSocketDataIO :: SetPrivateKey(const uint8 * bytes, uint32 numBytes) +{ + if (_ssl == NULL) return B_ERROR; + + status_t ret = B_ERROR; + + BIO * in = BIO_new_mem_buf((void *)bytes, numBytes); + if (in) + { + EVP_PKEY * pkey = PEM_read_bio_PrivateKey(in, NULL, CAST_SSL->ctx->default_passwd_callback, CAST_SSL->ctx->default_passwd_callback_userdata); + if (pkey) + { + if (SSL_use_PrivateKey(CAST_SSL, pkey) == 1) ret = B_NO_ERROR; + EVP_PKEY_free(pkey); + } + BIO_free(in); + } + return ret; +} + +status_t SSLSocketDataIO :: SetPublicKeyCertificate(const char * path) +{ + if (_ssl == NULL) return B_ERROR; + + FileDataIO fdio(fopen(path, "rb")); + if (fdio.GetFile() == NULL) return B_ERROR; + + ByteBufferRef buf = GetByteBufferFromPool((uint32)fdio.GetLength()); + return ((buf())&&(fdio.ReadFully(buf()->GetBuffer(), buf()->GetNumBytes()) == buf()->GetNumBytes())) ? SetPublicKeyCertificate(buf) : B_ERROR; +} + +status_t SSLSocketDataIO :: SetPublicKeyCertificate(const uint8 * bytes, uint32 numBytes) +{ + return _ssl ? SetPublicKeyCertificate(GetByteBufferFromPool(numBytes, bytes)) : B_ERROR; +} + +status_t SSLSocketDataIO :: SetPublicKeyCertificate(const ConstByteBufferRef & buf) +{ + if (buf() == NULL) return B_ERROR; + + status_t ret = B_ERROR; + + BIO * in = BIO_new_mem_buf((void *)buf()->GetBuffer(), buf()->GetNumBytes()); + if (in) + { + X509 * x509Cert = PEM_read_bio_X509(in, NULL, CAST_SSL->ctx->default_passwd_callback, CAST_SSL->ctx->default_passwd_callback_userdata); + if (x509Cert) + { + if (SSL_use_certificate(CAST_SSL, x509Cert) == 1) + { + _publicKey = buf; + ret = B_NO_ERROR; + } + X509_free(x509Cert); + } + BIO_free(in); + } + return ret; +} + +int32 SSLSocketDataIO :: Read(void *buffer, uint32 size) +{ + if (_ssl == NULL) return -1; + + int32 bytes = SSL_read(CAST_SSL, buffer, size); + if (bytes > 0) _sslState &= ~(SSL_STATE_READ_WANTS_READABLE_SOCKET | SSL_STATE_READ_WANTS_WRITEABLE_SOCKET); + else if (bytes == 0) return -1; // connection was terminated + else + { + int err = SSL_get_error(CAST_SSL, bytes); + if (err == SSL_ERROR_WANT_WRITE) + { + // We have to wait until our socket is writeable, and then repeat our SSL_read() call. + _sslState &= ~SSL_STATE_READ_WANTS_READABLE_SOCKET; + _sslState |= SSL_STATE_READ_WANTS_WRITEABLE_SOCKET; + bytes = 0; + } + else if (err == SSL_ERROR_WANT_READ) + { + // We have to wait until our socket is readable, and then repeat our SSL_read() call. + _sslState |= SSL_STATE_READ_WANTS_READABLE_SOCKET; + _sslState &= ~SSL_STATE_READ_WANTS_WRITEABLE_SOCKET; + bytes = 0; + } + else + { + fprintf(stderr, "SSL_read() ERROR: "); + ERR_print_errors_fp(stderr); + } + } + return bytes; +} + +int32 SSLSocketDataIO :: Write(const void *buffer, uint32 size) +{ + if (_ssl == NULL) return -1; + + int32 bytes = SSL_write(CAST_SSL, buffer, size); + if (bytes > 0) _sslState &= ~(SSL_STATE_WRITE_WANTS_READABLE_SOCKET | SSL_STATE_WRITE_WANTS_WRITEABLE_SOCKET); + else if (bytes == 0) return -1; // connection was terminated + else + { + int err = SSL_get_error(CAST_SSL, bytes); + if (err == SSL_ERROR_WANT_READ) + { + // We have to wait until our socket is readable, and then repeat our SSL_write() call. + _sslState |= SSL_STATE_WRITE_WANTS_READABLE_SOCKET; + _sslState &= ~SSL_STATE_WRITE_WANTS_WRITEABLE_SOCKET; + bytes = 0; + } + else if (err == SSL_ERROR_WANT_WRITE) + { + // We have to wait until our socket is writeable, and then repeat our SSL_write() call. + _sslState &= ~SSL_STATE_WRITE_WANTS_READABLE_SOCKET; + _sslState |= SSL_STATE_WRITE_WANTS_WRITEABLE_SOCKET; + bytes = 0; + } + else + { + fprintf(stderr,"SSL_write() ERROR!"); + ERR_print_errors_fp(stderr); + } + } + return bytes; +} + +const ConstSocketRef & SSLSocketDataIO :: GetReadSelectSocket() const +{ + return ((_forceReadReady)||((_ssl)&&(SSL_pending(CAST_SSL)>0))) ? _alwaysReadableSocket : TCPSocketDataIO::GetReadSelectSocket(); +} + +}; // end namespace muscle diff --git a/dataio/SSLSocketDataIO.h b/dataio/SSLSocketDataIO.h new file mode 100644 index 00000000..e5c3c60c --- /dev/null +++ b/dataio/SSLSocketDataIO.h @@ -0,0 +1,116 @@ +#ifndef MuscleSSLSocketDataIO_h +#define MuscleSSLSocketDataIO_h + +#include "dataio/TCPSocketDataIO.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +class SSLSocketAdapterGateway; + +/** This class lets you communicate over a TCP socket with SSL encryption enabled on it. + * @note In most cases when using this class you will want to wrap your MessageIOGateway + * in a SSLSocketAdapterGateway object; otherwise OpenSSL's internal state machine + * will not be able to work properly! + * @note If you simply want to enable SSL on all your TCP connections, the easiest way + * to do that is to recompile MUSCLE with the -DMUSCLE_ENABLE_SSL compiler argument, + * and then call either SetSSLPublicKeyCertificate() or SetSSLPrivateKey() on your + * ReflectServer or MessageTransceiverThread object. Then the MUSCLE event loop + * will transparently insert the SSL layer for you. Alternatively, creating the + * SSLSocketDataIO objects and SSLSocketAdapterGateway objects explicitly in your + * own code will also work and may be necessary in cases where only certain sessions + * should use SSL. + */ +class SSLSocketDataIO : public TCPSocketDataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param sockfd The socket to use. + * @param blocking If true, the socket will be set to blocking mode; otherwise non-blocking + * @param accept True iff the SSL logic should be put into "accept-connection" mode, or false for "outgoing-connection" mode. + */ + SSLSocketDataIO(const ConstSocketRef & sockfd, bool blocking, bool accept); + + virtual ~SSLSocketDataIO(); + + /** Adds a certification to use for this session. + * @param certFilePath File path of the certificate file to use. + * @returns B_NO_ERROR on success, or B_ERROR on failure (couldn't find file?) + */ + status_t SetPublicKeyCertificate(const char * certFilePath); + + /** Same as above, except instead of reading the certificate from a + * file, the certificate is read from memory. + * @param bytes The array containing the certificate (i.e. the contents of a .pub file) + * @param numBytes The number of bytes that (bytes) points to. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetPublicKeyCertificate(const uint8 * bytes, uint32 numBytes); + + /** Same as above, except instead of reading from a raw array we read from a ConstByteBufferRef. + * @param publicKeyFile The bytes to read from. We will retain a reference to this buffer. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetPublicKeyCertificate(const ConstByteBufferRef & publicKeyFile); + + /** Contents of our current public key file */ + ConstByteBufferRef GetPublicKeyCertificate() const {return _publicKey;} + + /** Adds a private key to use for this session. + * @param privateKeyFilePath File path of the private key file to use. + * @returns B_NO_ERROR on success, or B_ERROR on failure (couldn't find file?) + * @note Typically on the server side you'll want to call both SetPrivateKey() *and* + * SetPublicKeyCertificate() on your SSLSocketDataIO object. The same + * file data can be passed to both (assuming the file in question contains + * both a PRIVATE key section and a CERTIFICATE section) + */ + status_t SetPrivateKey(const char * privateKeyFilePath); + + /** Same as above, except instead of reading the private key from a + * file, the private key is read from memory. + * @param bytes The array containing the private key (i.e. the contents of a .pem file) + * @param numBytes The number of bytes that (bytes) points to. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + * @note Typically on the server side you'll want to call both SetPrivateKey() *and* + * SetPublicKeyCertificate() on your SSLSocketDataIO object. The same + * file data can be passed to both (assuming the file in question contains + * both a PRIVATE key section and a CERTIFICATE section) + */ + status_t SetPrivateKey(const uint8 * bytes, uint32 numBytes); + + /** Overridden to return a dummy (always-ready-for-read) socket when necessary, + * as there are times when we need gateway->DoInput() to be called when even when there + * aren't any actual bytes present to be read from our TCP socket. + * See http://www.rtfm.com/openssl-examples/part2.pdf (Figure 8) for details. + */ + virtual const ConstSocketRef & GetReadSelectSocket() const; + + virtual int32 Read(void *buffer, uint32 size); + virtual int32 Write(const void *buffer, uint32 size); + virtual void Shutdown(); + +private: + friend class SSLSocketAdapterGateway; + + enum { + SSL_STATE_READ_WANTS_READABLE_SOCKET = 0x01, + SSL_STATE_READ_WANTS_WRITEABLE_SOCKET = 0x02, + SSL_STATE_WRITE_WANTS_READABLE_SOCKET = 0x04, + SSL_STATE_WRITE_WANTS_WRITEABLE_SOCKET = 0x08, + NUM_SSL_STATES + }; + uint32 _sslState; + + bool _forceReadReady; + ConstSocketRef _alwaysReadableSocket; // this dummy socket will ALWAYS select as ready-for-read! + + ConstByteBufferRef _publicKey; + + void * _ctx; // actually (SSL_CTX*) but declared (void *) here so I don't have to #include openssl headers + void * _ssl; // actually (SSL*) but declared (void *) here so I don't have to #include openssl headers +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/StdinDataIO.cpp b/dataio/StdinDataIO.cpp new file mode 100644 index 00000000..00cbfe8a --- /dev/null +++ b/dataio/StdinDataIO.cpp @@ -0,0 +1,194 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/StdinDataIO.h" +#include "util/Hashtable.h" + +#if defined(WIN32) || defined(CYGWIN) +# define USE_WIN32_STDINDATAIO_IMPLEMENTATION +# include // for _beginthreadex() +#endif + +namespace muscle { + +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION +enum { + STDIN_THREAD_STATUS_UNINITIALIZED = 0, + STDIN_THREAD_STATUS_RUNNING, + STDIN_THREAD_STATUS_EXITED, + NUM_STDIN_THREAD_STATUSES +}; +static Mutex _slaveSocketsMutex; // serializes access to these static variables +static Hashtable _slaveSockets; +static uint32 _slaveSocketTagCounter = 0; +static uint32 _stdinThreadStatus = STDIN_THREAD_STATUS_UNINITIALIZED; +static ::HANDLE _slaveThread = INVALID_HANDLE_VALUE; +static ::HANDLE _stdinHandle = INVALID_HANDLE_VALUE; + +static unsigned __stdcall StdinThreadEntryFunc(void *) +{ + if (_stdinHandle != INVALID_HANDLE_VALUE) + { + DWORD oldConsoleMode = 0; + GetConsoleMode(_stdinHandle, &oldConsoleMode); + SetConsoleMode(_stdinHandle, oldConsoleMode | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); + + // The only time this thread is allowed to exit is if stdin is closed. Otherwise it runs until the + // process terminates, because Windows has no way to unblock the call to ReadFile()! + Hashtable temp; // declared out here only to avoid reallocations on every loop iteration + char buf[4096]; + DWORD numBytesRead; + while(ReadFile(_stdinHandle, buf, sizeof(buf), &numBytesRead, NULL)) + { + // Grab a temporary copy of the listeners-set. That we we don't risk blocking in SendData() while holding the mutex. + if (_slaveSocketsMutex.Lock() == B_NO_ERROR) + { + temp = _slaveSockets; + _slaveSocketsMutex.Unlock(); + } + + // Now send the data we read from stdin to all the registered sockets + bool trim = false; + for (HashtableIterator iter(temp); iter.HasData(); iter++) if (SendData(iter.GetValue(), buf, numBytesRead, true) != numBytesRead) {trim = true; iter.GetValue().Reset();} + + // Lastly, remove from the registered-sockets-set any sockets that SendData() errored out on. + // This will cause the socket connection to be closed and the master thread(s) to be notified. + if ((trim)&&(_slaveSocketsMutex.Lock() == B_NO_ERROR)) + { + for (HashtableIterator iter(_slaveSockets); iter.HasData(); iter++) + { + const ConstSocketRef * v = temp.Get(iter.GetKey()); + if ((v)&&(v->IsValid() == false)) (void) _slaveSockets.Remove(iter.GetKey()); + } + _slaveSocketsMutex.Unlock(); + } + + temp.Clear(); // it's important not to have extra Refs hanging around in case the process exits! + } + + // Restore the old console mode before we leave + SetConsoleMode(_stdinHandle, oldConsoleMode); + } + + // Oops, stdin failed... clear the slave sockets table so that the client objects will know to close up shop + if (_slaveSocketsMutex.Lock() == B_NO_ERROR) + { + _stdinThreadStatus = STDIN_THREAD_STATUS_EXITED; + _slaveSockets.Clear(); + + // We'll close our own handle, thankyouverymuch + if (_slaveThread != INVALID_HANDLE_VALUE) + { + CloseHandle(_slaveThread); + _slaveThread = INVALID_HANDLE_VALUE; + } + + if (_stdinHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(_stdinHandle); + _stdinHandle = INVALID_HANDLE_VALUE; + } + + _slaveSocketsMutex.Unlock(); + } + return 0; +} +#else +static Socket _stdinSocket(STDIN_FILENO, false); // we generally don't want to close stdin +#endif + +StdinDataIO :: StdinDataIO(bool blocking) : _stdinBlocking(blocking) +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION + , _slaveSocketTag(0) +#else + , _fdIO(ConstSocketRef(&_stdinSocket, false), true) +#endif +{ +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION + if (_stdinBlocking == false) + { + // For non-blocking I/O, we need to handle stdin in a separate thread. + // note that I freopen stdin to "nul" so that other code (read: Python) + // won't try to muck about with stdin and interfere with StdinDataIO's + // operation. I don't know of any good way to restore it again after, + // though... so a side effect of StdinDataIO under Windows is that + // stdin gets redirected to nul... once you've created one non-blocking + // StdinDataIO, you'll need to continue accessing stdin only via + // non-blocking StdinDataIOs. + bool okay = false; + ConstSocketRef slaveSocket; + if ((CreateConnectedSocketPair(_masterSocket, slaveSocket, false) == B_NO_ERROR)&&(SetSocketBlockingEnabled(slaveSocket, true) == B_NO_ERROR)&&(_slaveSocketsMutex.Lock() == B_NO_ERROR)) + { + bool threadCreated = false; + if (_stdinThreadStatus == STDIN_THREAD_STATUS_UNINITIALIZED) + { + DWORD junkThreadID; + _stdinThreadStatus = ((DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), GetCurrentProcess(), &_stdinHandle, 0, false, DUPLICATE_SAME_ACCESS))&&(freopen("nul", "r", stdin) != NULL)&&((_slaveThread = (::HANDLE) _beginthreadex(NULL, 0, StdinThreadEntryFunc, NULL, CREATE_SUSPENDED, (unsigned *) &junkThreadID)) != 0)) ? STDIN_THREAD_STATUS_RUNNING : STDIN_THREAD_STATUS_EXITED; + threadCreated = (_stdinThreadStatus == STDIN_THREAD_STATUS_RUNNING); + } + if ((_stdinThreadStatus == STDIN_THREAD_STATUS_RUNNING)&&(_slaveSockets.Put(_slaveSocketTag = (++_slaveSocketTagCounter), slaveSocket) == B_NO_ERROR)) okay = true; + else LogTime(MUSCLE_LOG_ERROR, "StdinDataIO: Could not start stdin thread!\n"); + _slaveSocketsMutex.Unlock(); + + // We don't start the thread running until here, that way there's no chance of race conditions if the thread exits immediately + if (threadCreated) ResumeThread(_slaveThread); + } + else LogTime(MUSCLE_LOG_ERROR, "StdinDataIO: Error setting up I/O sockets!\n"); + + if (okay == false) Close(); + } +#endif +} + +StdinDataIO :: +~StdinDataIO() +{ + Close(); +} + +void StdinDataIO :: Shutdown() +{ + Close(); +} + +void StdinDataIO :: Close() +{ +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION + if ((_stdinBlocking == false)&&(_slaveSocketsMutex.Lock() == B_NO_ERROR)) + { + _slaveSockets.Remove(_slaveSocketTag); + _slaveSocketsMutex.Unlock(); + // Note that I deliberately let the Stdin thread keep running, since there's no clean way to stop it from another thread. + } +#else + _fdIO.Shutdown(); +#endif +} + +int32 StdinDataIO :: Read(void * buffer, uint32 size) +{ +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION + if (_stdinBlocking) + { + DWORD actual_read; + return ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, size, &actual_read, 0) ? actual_read : -1; + } + else return ReceiveData(_masterSocket, buffer, size, _stdinBlocking); +#else + // Turn off stdin's blocking I/O mode only during the Read() call. + if (_stdinBlocking == false) (void) _fdIO.SetBlockingIOEnabled(false); + int32 ret = _fdIO.Read(buffer, size); + if (_stdinBlocking == false) (void) _fdIO.SetBlockingIOEnabled(true); + return ret; +#endif +} + +const ConstSocketRef & StdinDataIO :: GetReadSelectSocket() const +{ +#ifdef USE_WIN32_STDINDATAIO_IMPLEMENTATION + return _stdinBlocking ? GetNullSocket() : _masterSocket; +#else + return _fdIO.GetReadSelectSocket(); +#endif +} + +}; // end namespace muscle diff --git a/dataio/StdinDataIO.h b/dataio/StdinDataIO.h new file mode 100644 index 00000000..e5f7c824 --- /dev/null +++ b/dataio/StdinDataIO.h @@ -0,0 +1,90 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef StdinDataIO_h +#define StdinDataIO_h + +#include "util/NetworkUtilityFunctions.h" // for ConstSocketRef, WIN32, etc. + +#if defined(WIN32) || defined(CYGWIN) +# include "dataio/DataIO.h" +#else +# include "dataio/FileDescriptorDataIO.h" +#endif + +namespace muscle { + +/** + * This DataIO handles I/O input from the STDIN_FILENO file descriptor. + * In order to support non-blocking input on stdin without causing loss of + * data sent to stdout, this DataIO object will keep its file descriptor in + * blocking mode at all times except when it is actually about to read from + * it. Writing to stdin is not supported, of course. + */ +class StdinDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param blocking determines whether to use blocking or non-blocking I/O. + * If you will be using this object with a AbstractMessageIOGateway, + * and/or select(), then it's usually better to set blocking to false. + */ + StdinDataIO(bool blocking); + + /** Destructor */ + virtual ~StdinDataIO(); + + /** Reads bytes from stdin and places them into (buffer). + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + * @see DataIO::Read() + */ + virtual int32 Read(void * buffer, uint32 size); + + /** Overridden to always return -1, because you can't write to stdin! */ + virtual int32 Write(const void *, uint32) {return -1;} + + /** Overridden to always return B_ERROR, because you can't seek() stdin! */ + virtual status_t Seek(int64, int) {return B_ERROR;} + + /** Always returns -1, since stdin doesn't have a notion of current position. */ + virtual int64 GetPosition() const {return -1;} + + /** Implemented as a no-op because we don't ever do output on stdin */ + virtual void FlushOutput() {/* empty */} + + virtual void Shutdown(); + + /** Returns a socket to select() on to be notified when there is data + * ready to be read. Note that this works even under Windows, as long + * as this object was created with (blocking==false), and the returned + * socket is only used for select() (call StdinDataIO::Read() to do the + * actual data reading) + */ + virtual const ConstSocketRef & GetReadSelectSocket() const; + + /** Returns a NULL socket -- since you can't write to stdin, there is no point + * in waiting for space to be available for writing on stdin! + */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetNullSocket();} + + /** Returns the blocking flag that was passed into our constructor */ + bool IsBlockingIOEnabled() const {return _stdinBlocking;} + +private: + bool _stdinBlocking; + + void Close(); + +#if defined(WIN32) || defined(CYGWIN) + ConstSocketRef _masterSocket; + uint32 _slaveSocketTag; +#else + FileDescriptorDataIO _fdIO; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/TCPSocketDataIO.h b/dataio/TCPSocketDataIO.h new file mode 100644 index 00000000..37f5dc2f --- /dev/null +++ b/dataio/TCPSocketDataIO.h @@ -0,0 +1,133 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleTCPSocketDataIO_h +#define MuscleTCPSocketDataIO_h + +#include "support/MuscleSupport.h" + +#include "dataio/DataIO.h" +#include "util/NetworkUtilityFunctions.h" + +namespace muscle { + +#ifndef MUSCLE_DEFAULT_TCP_STALL_TIMEOUT +# define MUSCLE_DEFAULT_TCP_STALL_TIMEOUT MinutesToMicros(3) // 3 minutes is our default timeout period +#endif + +/** + * Data I/O to and from a TCP socket! + */ +class TCPSocketDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param sock The ConstSocketRef we should use for our I/O. + * @param blocking specifies whether to use blocking or non-blocking socket I/O. + * If you will be using this object with a AbstractMessageIOGateway, + * and/or select(), then it's usually better to set blocking to false. + */ + TCPSocketDataIO(const ConstSocketRef & sock, bool blocking) : _sock(sock), _blocking(true), _naglesEnabled(true), _stallLimit(MUSCLE_DEFAULT_TCP_STALL_TIMEOUT) + { + (void) SetBlockingIOEnabled(blocking); + } + + /** Destructor. + * Closes the socket descriptor, if necessary. + */ + virtual ~TCPSocketDataIO() + { + Shutdown(); + } + + virtual int32 Read(void * buffer, uint32 size) {return ReceiveData(_sock, buffer, size, _blocking);} + virtual int32 Write(const void * buffer, uint32 size) {return SendData(_sock, buffer, size, _blocking);} + + /** + * This method implementation always returns B_ERROR, because you can't seek on a socket! + */ + virtual status_t Seek(int64 /*seekOffset*/, int /*whence*/) {return B_ERROR;} + + /** Always returns -1, since a socket has no position to speak of */ + virtual int64 GetPosition() const {return -1;} + + /** + * Stall limit for TCP streams is 180000000 microseconds (aka 3 minutes) by default. + * Or change it by calling SetOutputStallLimit(). + */ + virtual uint64 GetOutputStallLimit() const {return _stallLimit;} + + /** Set a new output stall time limit. Set to MUSCLE_TIME_NEVER to disable stall limiting. */ + void SetOutputStallLimit(uint64 limit) {_stallLimit = limit;} + + /** + * Flushes the output buffer by turning off Nagle's Algorithm and then turning it back on again. + * If Nagle's Algorithm is disabled, then this call is a no-op (since there is never anything to flush) + */ + virtual void FlushOutput() + { + if ((_sock())&&(_naglesEnabled)) + { + SetSocketNaglesAlgorithmEnabled(_sock, false); +#ifndef __linux__ + (void) SendData(_sock, NULL, 0, _blocking); // Force immediate buffer flush (not necessary under Linux) +#endif + SetSocketNaglesAlgorithmEnabled(_sock, true); + } + } + + /** + * Closes our socket connection + */ + virtual void Shutdown() {_sock.Reset();} + + virtual const ConstSocketRef & GetReadSelectSocket() const {return _sock;} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _sock;} + + /** + * Enables or diables blocking I/O on this socket. + * If this object is to be used by an AbstractMessageIOGateway, + * then non-blocking I/O is usually better to use. + * @param blocking If true, socket is set to blocking I/O mode. Otherwise, non-blocking I/O. + * @return B_NO_ERROR on success, B_ERROR on error. + */ + status_t SetBlockingIOEnabled(bool blocking) + { + status_t ret = SetSocketBlockingEnabled(_sock, blocking); + if (ret == B_NO_ERROR) _blocking = blocking; + return ret; + } + + /** + * Turns Nagle's algorithm (output packet buffering/coalescing) on or off. + * @param enabled If true, data will be held momentarily before sending, to allow for bigger packets. + * If false, each Write() call will cause a new packet to be sent immediately. + * @return B_NO_ERROR on success, B_ERROR on error. + */ + status_t SetNaglesAlgorithmEnabled(bool enabled) + { + status_t ret = SetSocketNaglesAlgorithmEnabled(_sock, enabled); + if (ret == B_NO_ERROR) _naglesEnabled = enabled; + return ret; + } + + /** Returns true iff our socket is set to use blocking I/O (as specified in + * the constructor or in our SetBlockingIOEnabled() method) + */ + bool IsBlockingIOEnabled() const {return _blocking;} + + /** Returns true iff our socket has Nagle's algorithm enabled (as specified + * in our SetNaglesAlgorithmEnabled() method. Default state is true. + */ + bool IsNaglesAlgorithmEnabled() const {return _naglesEnabled;} + +private: + ConstSocketRef _sock; + bool _blocking; + bool _naglesEnabled; + uint64 _stallLimit; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/UDPSocketDataIO.h b/dataio/UDPSocketDataIO.h new file mode 100644 index 00000000..1f149e3a --- /dev/null +++ b/dataio/UDPSocketDataIO.h @@ -0,0 +1,149 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleUDPSocketDataIO_h +#define MuscleUDPSocketDataIO_h + +#include "support/MuscleSupport.h" + +#include "dataio/DataIO.h" +#include "util/NetworkUtilityFunctions.h" + +namespace muscle { + +/** + * Data I/O to and from a UDP socket! + */ +class UDPSocketDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param sock The socket to use. + * @param blocking specifies whether to use blocking or non-blocking socket I/O. + * If you will be using this object with a AbstractMessageIOGateway, + * and/or select(), then it's usually better to set blocking to false. + */ + UDPSocketDataIO(const ConstSocketRef & sock, bool blocking) : _sock(sock), _maxPacketSize(MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET) + { + (void) SetBlockingIOEnabled(blocking); + _sendTo.AddTail(); // so that by default, Write() will just call send() on our socket + } + + /** Destructor. + * Closes the socket descriptor, if necessary. + */ + virtual ~UDPSocketDataIO() + { + // empty + } + + virtual int32 Read(void * buffer, uint32 size) + { + ip_address tmpAddr = invalidIP; + uint16 tmpPort = 0; + int32 ret = ReceiveDataUDP(_sock, buffer, size, _blocking, &tmpAddr, &tmpPort); + _recvFrom.SetIPAddress(tmpAddr); + _recvFrom.SetPort(tmpPort); + return ret; + } + + virtual int32 Write(const void * buffer, uint32 size) + { + int32 ret = 0; + for (uint32 i=0; i<_sendTo.GetNumItems(); i++) + { + const IPAddressAndPort & iap = _sendTo[i]; + int32 r = SendDataUDP(_sock, buffer, size, _blocking, iap.GetIPAddress(), iap.GetPort()); + if (r < (int32)size) return r; + else ret = r; + } + return ret; + } + + /** + * This method implementation always returns B_ERROR, because you can't seek on a socket! + */ + virtual status_t Seek(int64 /*seekOffset*/, int /*whence*/) {return B_ERROR;} + + /** Always returns -1, since a socket has no position to speak of */ + virtual int64 GetPosition() const {return -1;} + + /** Implemented as a no-op: UDP sockets are always flushed immediately anyway */ + virtual void FlushOutput() {/* empty */} + + /** Overridden to return the maximum packet size of a UDP packet. + * Defaults to MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET (aka 1388 bytes), + * but the returned value can be changed via SetPacketMaximumSize(). + */ + virtual uint32 GetPacketMaximumSize() const {return _maxPacketSize;} + + /** This can be called to change the maximum packet size value returned + * by GetPacketMaximumSize(). You might call this e.g. if you are on a network + * that supports Jumbo UDP packets and want to take advantage of that. + */ + void SetPacketMaximumSize(uint32 maxPacketSize) {_maxPacketSize = maxPacketSize;} + + /** + * Closes our socket connection + */ + virtual void Shutdown() {_sock.Reset();} + + virtual const ConstSocketRef & GetReadSelectSocket() const {return _sock;} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _sock;} + + /** Call this to make our Write() method use sendto() with the specified + * destination address and port. Calling this with (invalidIP, 0) will + * revert us to our default behavior of just calling() send on our UDP socket. + */ + void SetSendDestination(const IPAddressAndPort & dest) {(void) _sendTo.EnsureSize(1, true); _sendTo.Head() = dest;} + + /** Returns the IP address and port that Write() will send to, as was + * previously specified in SetSendDestination(). + */ + const IPAddressAndPort & GetSendDestination() const {return _sendTo.HasItems() ? _sendTo.Head() : _sendTo.GetDefaultItem();} + + /** Call this to make our Write() method use sendto() with the specified destination addresss and ports. */ + void SetSendDestinations(const Queue & dests) {_sendTo = dests;} + + /** Returns read/write access to our list of send-destinations. */ + Queue & GetSendDestinations() {return _sendTo;} + + /** Returns read-only access to our list of send-destinations. */ + const Queue & GetSendDestinations() const {return _sendTo;} + + /** + * Enables or diables blocking I/O on this socket. + * If this object is to be used by an AbstractMessageIOGateway, + * then non-blocking I/O is usually better to use. + * @param blocking If true, socket is set to blocking I/O mode. Otherwise, non-blocking I/O. + * @return B_NO_ERROR on success, B_ERROR on error. + */ + status_t SetBlockingIOEnabled(bool blocking) + { + status_t ret = SetSocketBlockingEnabled(_sock, blocking); + if (ret == B_NO_ERROR) _blocking = blocking; + return ret; + } + + /** Returns true iff our socket is set to use blocking I/O (as specified in + * the constructor or in our SetBlockingIOEnabled() method) + */ + bool IsBlockingIOEnabled() const {return _blocking;} + + /** Call this after a call to Read() returns to find out the IP address of the computer + * sent the data that we read. + */ + const IPAddressAndPort & GetSourceOfLastReadPacket() const {return _recvFrom;} + +private: + ConstSocketRef _sock; + bool _blocking; + + IPAddressAndPort _recvFrom; + Queue _sendTo; + uint32 _maxPacketSize; +}; + +}; // end namespace muscle + +#endif diff --git a/dataio/XorDataIO.h b/dataio/XorDataIO.h new file mode 100644 index 00000000..facdce45 --- /dev/null +++ b/dataio/XorDataIO.h @@ -0,0 +1,88 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleXorDataIO_h +#define MuscleXorDataIO_h + +#include "dataio/DataIO.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +/** This DataIO is a "wrapper" DataIO that adds an XOR operation to any data + * that it reads or writes before passing the call on to the DataIO that it + * holds internally. This can be useful if you want to obfuscate your data + * a little bit before sending it out to disk or over the network. + */ +class XorDataIO : public DataIO, public CountedObject +{ +public: + /** Default Constructor. Be sure to set a child dataIO with SetChildDataIO() + * before using this DataIO, so that this object will do something useful! + */ + XorDataIO() {/* empty */} + + /** Constructor. + * @param childIO Reference to the DataIO to pass calls on through to + * after the data has been XOR'd. + */ + XorDataIO(const DataIORef & childIO) : _childIO(childIO) {/* empty */} + + /** Virtual destructor, to keep C++ honest. */ + virtual ~XorDataIO() {/* empty */} + + /** Implemented to XOR the child DataIO's read bytes before returning. */ + virtual int32 Read(void * buffer, uint32 size) + { + int32 ret = _childIO() ? _childIO()->Read(buffer, size) : -1; + if (ret > 0) XorCopy(buffer, buffer, size); + return ret; + } + + /** Implemented to pass XOR's bytes to the child DataIO's Write() method. */ + virtual int32 Write(const void * buffer, uint32 size) + { + if ((_childIO() == NULL)||(_tempBuf.SetNumBytes(buffer, size) != B_NO_ERROR)) return B_ERROR; + XorCopy(_tempBuf.GetBuffer(), buffer, size); + return _childIO()->Write(_tempBuf.GetBuffer(), size); + } + + virtual status_t Seek(int64 offset, int whence) {return _childIO() ? _childIO()->Seek(offset, whence) : B_ERROR;} + virtual int64 GetPosition() const {return _childIO() ? _childIO()->GetPosition() : -1;} + virtual uint64 GetOutputStallLimit() const {return _childIO() ? _childIO()->GetOutputStallLimit() : MUSCLE_TIME_NEVER;} + virtual void FlushOutput() {if (_childIO()) _childIO()->FlushOutput();} + virtual void Shutdown() {if (_childIO()) _childIO()->Shutdown(); _childIO.Reset();} + virtual const ConstSocketRef & GetReadSelectSocket() const {return _childIO() ? _childIO()->GetReadSelectSocket() : GetNullSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _childIO() ? _childIO()->GetWriteSelectSocket() : GetNullSocket();} + virtual status_t GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const {return _childIO() ? _childIO()->GetReadByteTimeStamp(whichByte, retStamp) : B_ERROR;} + + virtual bool HasBufferedOutput() const {return _childIO() ? _childIO()->HasBufferedOutput() : false;} + virtual void WriteBufferedOutput() {if (_childIO()) _childIO()->WriteBufferedOutput();} + virtual uint32 GetPacketMaximumSize() const {return (_childIO()) ? _childIO()->GetPacketMaximumSize() : 0:} + + /** Returns a reference to our held child DataIO (if any) */ + const DataIORef & GetChildDataIO() const {return _childIO;} + + /** Sets our current held child DataIO. */ + void SetChildDataIO(const DataIORef & childDataIO) {_childIO = childDataIO;} + +private: + void XorCopy(const void * to, const void * from, uint32 numBytes) + { + // Do the bulk of the copy a word at a time, for faster speed + unsigned long * uto = (unsigned long *) to; + const unsigned long * ufrom = (const unsigned long *) from; + for (int32 i=(numBytes/sizeof(unsigned long))-1; i>=0; i--) *uto++ = ~(*ufrom++); + + // The last few bytes we have to do a byte a time though + uint8 * cto = (uint8 *) uto; + const uint8 * cfrom = (const uint8 *) ufrom; + for (int32 i=(numBytes%sizeof(unsigned long))-1; i>=0; i--) *cto++ = ~(*cfrom++); + } + + DataIORef _childIO; + ByteBuffer _tempBuf; // holds the XOR'd bytes temporarily for us +}; + +}; // end namespace muscle + +#endif diff --git a/delphi/Flattenable.pas b/delphi/Flattenable.pas new file mode 100644 index 00000000..dc950fba --- /dev/null +++ b/delphi/Flattenable.pas @@ -0,0 +1,144 @@ +{----------------------------------------------------------------------------- + Unit Name: Flattenable + Author: Matthew Emson + Date: 30-Oct-2003 + Purpose: Interfaced TPersistent alike + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +unit Flattenable; + +interface + +uses + Classes, Sysutils; + +//** Interface for objects that can be flattened and unflattened from Be-style byte streams */ +type + TmuscleFlattenable = class(TInterfacedObject) + public + //** Should return true iff every object of this type has a flattened size that is known at compile time. */ + function isFixedSize: boolean; virtual; abstract; + + //** Should return the type code identifying this type of object. */ + function typeCode: cardinal; virtual; abstract; + + //** Should return the number of bytes needed to store this object in its current state. */ + function flattenedSize: integer; virtual; abstract; + + //** Should return a clone of this object */ + function cloneFlat: TmuscleFlattenable; virtual; + + //** Should set this object's state equal to that of (setFromMe), or throw an UnflattenFormatException if it can't be done. + //* @param setFromMe The object we want to be like. + //* @throws ClassCastException if (setFromMe) is the wrong type. + //*/ + procedure setEqualTo( setFromMe: TmuscleFlattenable ); virtual; //throws ClassCastException; ... assign??? + + //** + //* Should store this object's state into (buffer). + //* @param out The DataOutput to send the data to. + //* @throws IOException if there is a problem writing out the output bytes. + //*/ + procedure flatten( AOut: TStream ); virtual; // (DataOutput out) throws IOException; + + //** + //* Should return true iff a buffer with type_code (code) can be used to reconstruct + //* this object's state. + //* @param code A type code ant, e.g. B_RAW_TYPE or B_STRING_TYPE, or something custom. + //* @return True iff this object can Unflatten from a buffer of the given type, false otherwise. + //*/ + function allowsTypeCode(code: integer): boolean; virtual; + + //** + //* Should attempt to restore this object's state from the given buffer. + //* @param in The stream to read the object from. + //* @param numBytes The number of bytes the object takes up in the stream, or negative if this is unknown. + //* @throws IOException if there is a problem reading in the input bytes. + //* @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + //*/ + procedure unflatten(ain: TStream; numBytes: integer); virtual;// throws IOException, UnflattenFormatException; + + + //mainly for testing... + function toString: string; virtual; abstract; + +end; + +implementation + +uses + MuscleExceptions; + +{ TmuscleFlattenable } + +function TmuscleFlattenable.allowsTypeCode(code: integer): boolean; +begin + result := false; +end; + +function TmuscleFlattenable.cloneFlat: TmuscleFlattenable; +begin + result := nil; +end; + +procedure TmuscleFlattenable.flatten(AOut: TStream); +begin + raise EInOutError.Create('Unable to flatten'); +end; + +procedure TmuscleFlattenable.setEqualTo(setFromMe: TmuscleFlattenable); +begin + if (setFromMe.ClassName <> Self.ClassName) then + raise EInvalidCast.Create('bad cast'); +end; + +procedure TmuscleFlattenable.unflatten(ain: TStream; numBytes: integer); +begin + raise EUnflattenFormatException.CreateDefault; +end; + +end. diff --git a/delphi/IOGateway.pas b/delphi/IOGateway.pas new file mode 100644 index 00000000..32392607 --- /dev/null +++ b/delphi/IOGateway.pas @@ -0,0 +1,144 @@ +{----------------------------------------------------------------------------- + Unit Name: IOGateway + Author: Matthew Emson + Date: 02-Jul-2005 + Purpose: + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit IOGateway; + +interface + +uses + classes, sysutils, Message; + + +type + IMessageIOGateway = interface ['{95FFB6A4-D937-4C06-861D-CF50F22C5E57}'] + function unflattenMessage(datain: TStream): IPortableMessage; + procedure flattenMessage(dataout: TStream; const msg: IPortableMessage); + end; + + TMessageIOGateway = class(TInterfacedObject, IMessageIOGateway) + public + function unflattenMessage(datain: TStream): IPortableMessage; + procedure flattenMessage(dataout: TStream; const msg: IPortableMessage); + end; + +const + MESSAGE_HEADER_SIZE = 8; + + MUSCLE_MESSAGE_ENCODING_DEFAULT = 1164862256; // 'Enc0' ... our default (plain-vanilla) encoding + + // zlib encoding levels + MUSCLE_MESSAGE_ENCODING_ZLIB_1 = 1164862257; + MUSCLE_MESSAGE_ENCODING_ZLIB_2 = 1164862258; + MUSCLE_MESSAGE_ENCODING_ZLIB_3 = 1164862259; + MUSCLE_MESSAGE_ENCODING_ZLIB_4 = 1164862260; + MUSCLE_MESSAGE_ENCODING_ZLIB_5 = 1164862261; + MUSCLE_MESSAGE_ENCODING_ZLIB_6 = 1164862262; + MUSCLE_MESSAGE_ENCODING_ZLIB_7 = 1164862263; + MUSCLE_MESSAGE_ENCODING_ZLIB_8 = 1164862264; + MUSCLE_MESSAGE_ENCODING_ZLIB_9 = 1164862265; + MUSCLE_MESSAGE_ENCODING_END_MARKER = 1164862266; + + ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219; + ZLIB_CODEC_HEADER_DEPENDENT = 2053925218; + +implementation + +uses + winsock; + + + +{ TMessageIOGateway } + +// ** Converts the given Message into bytes and sends it out the stream. +// * @param out the data stream to send the converted bytes to. +// * @param message the Message to convert. +// * @throws IOException if there is an error writing to the stream. +// * +procedure TMessageIOGateway.flattenMessage(dataout: TStream; const msg: IPortableMessage); +var + t: cardinal; +begin + if (msg = nil) then exit; + + t := msg.flattenedSize; + dataout.write(t, sizeof(cardinal)); + t := MUSCLE_MESSAGE_ENCODING_DEFAULT; + dataout.write(t, sizeof(cardinal)); + msg.flatten(dataout); +end; + + +// ** Reads from the input stream until a Message can be assembled and returned. +// * @param in The input stream to read from. +// * @throws IOException if there is an error reading from the stream. +// * @throws UnflattenFormatException if there is an error parsing the data in the stream. +// * @return The next assembled Message. +// * +function TMessageIOGateway.unflattenMessage(datain: TStream): IPortableMessage; +var + numBytes, enc: cardinal; + +begin + datain.read(numBytes, sizeof(cardinal)); + datain.read(enc, sizeof(cardinal)); + + if not(enc = MUSCLE_MESSAGE_ENCODING_DEFAULT) then + raise Exception.Create('Failed to unflatten Message header!'); + + result := TPortableMessage.Create; + try + result.unflatten(datain, numBytes); + except + result := nil; + end; +end; + +end. diff --git a/delphi/Locker.pas b/delphi/Locker.pas new file mode 100644 index 00000000..63319cb9 --- /dev/null +++ b/delphi/Locker.pas @@ -0,0 +1,636 @@ +{----------------------------------------------------------------------------- + Unit Name: Locker + Author: Matthew Emson + Date: ??-??-2003 + Purpose: + History: +-----------------------------------------------------------------------------} + +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +unit Locker; + +{ The locker is an idea that grew out of the extreme inability of Borland to understand good and sound methods + for allowing multiple threads in their application framework to access the same Class using some kind of + synchronization methodology. The locker hierarchy tries to give less painfull wrappers for various + classes (including the main base class and base component class) to help the developer cater for a simple + syncronization mechanism of otherwise asynchronous threads. + + Remember.. this is all or nothing. If you use a locker, you must always access the protected class via the + locker. Not doing so will seriously mess up syncronization and will probably cause spurious a/v's due + to nasty things happening in one or more threads. + + By default the lock will timeout after 1 second (1000 miliseconds.) Remember to set an appropriate + lock timeout for your purposes. 'INFINITE' is possible, but beware of blocking..this may cause + deadlocks and is a pain to debug. } + + +interface + +uses + Classes, SysUtils, Semaphore, contnrs, forms; + +type + ELockerError = class(Exception); + + TBaseLocker = class(TComponent) + private + FSem: TNamedSemaphore; + FLockTimeout: cardinal; + function getSem: TSemaphore; + + protected + function GetSemaphoreHandle: cardinal; + function GetIsLocked: boolean; + + procedure SetLockTimeout(const Value: cardinal); + + property IsLocked: boolean read GetIsLocked; + property LockTimeout: cardinal read FLockTimeout write SetLockTimeout; + property Sem: TSemaphore read getSem; + + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + + procedure Unlock; virtual; + + property SemaphoreHandle: cardinal read GetSemaphoreHandle; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TBaseComponentLocker = class(TBaseLocker) + private + FComponent: TComponent; + + protected + procedure SetComponent(const Value: TComponent); + property Component: TComponent read FComponent write SetComponent; + procedure Notification(AComponent: TComponent; Operation: TOperation); override; + + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TBaseObjectLocker = class(TBaseLocker) + private + FSubject: TObject; + + protected + procedure Notification(AComponent: TComponent; Operation: TOperation); override; + + procedure SetSubject(const Value: TObject); + + property Subject: TObject read FSubject write SetSubject; //object is a reserved word... + + function DoLock( var AObject: TObject; useTimeout: boolean = false; ATimeout: cardinal = 1000 ): boolean; virtual; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TCustomComponentLocker = class(TBaseComponentLocker) + protected + function Lock: TComponent; overload; virtual; + function Lock( var AComponent: TComponent ): boolean; overload; virtual; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TComponentLocker = class(TCustomComponentLocker) + public + function Lock: TComponent; override; + property IsLocked; + + published + property LockTimeout; + property Component; + + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TFormLocker = class(TCustomComponentLocker) + protected + function GetForm: TForm; + procedure SetForm(const Value: TForm); + + public + function Lock: TForm; reintroduce; virtual; + property IsLocked; + + published + property LockTimeout; + property Form: TForm read GetForm write SetForm; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TFrameLocker = class(TCustomComponentLocker) + protected + function GetFrame: TFrame; + procedure SetFrame(const Value: TFrame); + + public + function Lock: TFrame; reintroduce; virtual; + property IsLocked; + + published + property LockTimeout; + property Frame: TFrame read GetFrame write SetFrame; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TCustomListLocker = class(TBaseObjectLocker) + protected + function GetList: TList; + procedure SetList(const Value: TList); + function Lock: TList; virtual; + property List: TList read GetList write SetList; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TListLocker = class(TCustomListLocker) + public + function Lock: TList; override; + function LockEx(const ATimeout: cardinal): TList; virtual; + property IsLocked; + + published + property LockTimeout; + + property List; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TObjectListLocker = class(TCustomListLocker) + private + function GetObjectList: TObjectList; + procedure SetObjectList(const Value: TObjectList); + + public + function Lock: TObjectList; reintroduce; virtual; + function LockEx(ATimeout: integer): TObjectList; + property IsLocked; + + published + property LockTimeout; + + property ObjectList: TObjectList read GetObjectList write SetObjectList; + + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + + TOrderedListLocker = class(TBaseObjectLocker) + private + function GetOrderedList: TOrderedList; + procedure SetOrderedList(const Value: TOrderedList); + public + function Lock: TOrderedList; reintroduce; virtual; + property IsLocked; + + published + property LockTimeout; + + property OrderedList: TOrderedList read GetOrderedList write SetOrderedList; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + //TInterfaceList inherites from TThreadList, but htis uses a Critical section to lock... We don't want to block when locking + TInterfaceListLocker = class(TCustomListLocker) + private + function GetInterfaceList: TInterfaceList; + procedure SetInterfaceList(const Value: TInterfaceList); + + public + function Lock: TInterfaceList; reintroduce; virtual; + function LockEx(ATimeout: integer): TInterfaceList; + property IsLocked; + + published + property LockTimeout; + + property InterfaceList: TInterfaceList read GetInterfaceList write SetInterfaceList; + + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + TStringsLocker = class(TBaseObjectLocker) + protected + function GetStrings: TStrings; + procedure SetStrings(const Value: TStrings); + + public + function Lock: TStrings; virtual; + property IsLocked; + + published + property LockTimeout; + + property Strings: TStrings read GetStrings write SetStrings; + end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + + + TStreamLocker = class(TBaseObjectLocker) + private + function getStream: TStream; + procedure setStream(const Value: TStream); + + public + function Lock: TStream; virtual; + + property IsLocked; + property LockTimeout; + property Stream: TStream read getStream write setStream; + end; + +implementation + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TBaseLocker } + +constructor TBaseLocker.Create(AOwner: TComponent); +begin + inherited; + + FSem := TNamedSemaphore.Create(format('%s%f', [Classname, now])); +end; + +destructor TBaseLocker.Destroy; +begin + while isLocked do + Unlock; + + FSem.free; + + inherited; + +end; + +function TBaseLocker.GetIsLocked: boolean; +begin + Result := FSem.CanLock; +end; + +function TBaseLocker.getSem: TSemaphore; +begin + Result := FSem; +end; + +function TBaseLocker.GetSemaphoreHandle: cardinal; +begin + Result := FSem.SemaphoreHandle; +end; + +procedure TBaseLocker.SetLockTimeout(const Value: cardinal); +begin + FSem.Timeout := Value; +end; + +procedure TBaseLocker.Unlock; +begin + FSem.Unlock; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TBaseComponentLocker } + +procedure TBaseComponentLocker.Notification(AComponent: TComponent; Operation: TOperation); +var + WasLocked: boolean; +begin + if (Operation = opRemove) and (AComponent = FComponent) then begin + WasLocked := IsLocked; + while IsLocked do Unlock; + FComponent := nil; + if WasLocked then raise ELockerError.Create('Component was locked when it was removed!'); + end; + + inherited; +end; + +procedure TBaseComponentLocker.SetComponent(const Value: TComponent); +begin + FComponent := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TCustomComponentLocker } + +function TCustomComponentLocker.Lock: TComponent; +begin + Lock(Result); +end; + +function TCustomComponentLocker.Lock(var AComponent: TComponent): boolean; +begin + Result := FSem.Lock; + if not Result then begin + AComponent := nil; + end + else + AComponent := FComponent; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TComponentLocker } + +function TComponentLocker.Lock: TComponent; +begin + Result := Inherited Lock; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TBaseObjectLocker } + +function TBaseObjectLocker.DoLock(var AObject: TObject; useTimeout: boolean = false; ATimeout: cardinal = 1000 ): boolean; +begin + if useTimeout then + Result := FSem.LockEx(ATimeout) + else + Result := FSem.Lock; + if not Result then begin + AObject := nil; + end + else + AObject := FSubject; +end; + +procedure TBaseObjectLocker.Notification(AComponent: TComponent; Operation: TOperation); +var + WasLocked: boolean; +begin + //This routine isn't really needed, but as all classes inherit from TObject, for sanity I'm including it.. + + if (Operation = opRemove) and (AComponent = FSubject) then begin + WasLocked := IsLocked; + while IsLocked do Unlock; + FSubject := nil; + if WasLocked then raise ELockerError.Create('Component was locked when it was removed!'); + end; + inherited; +end; + +procedure TBaseObjectLocker.SetSubject(const Value: TObject); +begin + FSubject := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TCustomListLocker } + +function TCustomListLocker.GetList: TList; +begin + Result := FSubject as TList; +end; + +function TCustomListLocker.Lock: TList; +var + Temp: TObject; +begin + if DoLock(Temp) then + Result := Temp as TList + else + Result := nil; +end; + +procedure TCustomListLocker.SetList(const Value: TList); +begin + FSubject := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TListLocker } + +function TListLocker.Lock: TList; +begin + Result := inherited Lock; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +function TListLocker.LockEx(const ATimeout: cardinal): TList; +var + Temp: TObject; +begin + if DoLock(Temp, true, ATimeout) then + Result := Temp as TList + else + Result := nil; +end; + +{ TObjectListLocker } + +function TObjectListLocker.GetObjectList: TObjectList; +begin + Result := GetList as TObjectList; +end; + +function TObjectListLocker.Lock: TObjectList; +begin + result := inherited Lock as TObjectList; +end; + +function TObjectListLocker.LockEx(ATimeout: integer): TObjectList; +var + Temp: TObject; +begin + if DoLock(Temp, true, ATimeout) then + Result := Temp as TObjectList + else + Result := nil; +end; + +procedure TObjectListLocker.SetObjectList(const Value: TObjectList); +begin + SetList(Value); +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TStringsLocker } + +function TStringsLocker.GetStrings: TStrings; +begin + Result := FSubject as TStrings; +end; + +function TStringsLocker.Lock: TStrings; +var + Temp: TObject; +begin + if DoLock(Temp) then + Result := Temp as TStrings + else + Result := nil; + +end; + +procedure TStringsLocker.SetStrings(const Value: TStrings); +begin + FSubject := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TFormLocker } + +function TFormLocker.GetForm: TForm; +begin + Result := FComponent as TForm; +end; + +function TFormLocker.Lock: TForm; +begin + Result := inherited Lock as TForm; +end; + +procedure TFormLocker.SetForm(const Value: TForm); +begin + FComponent := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ +{ TFrameLocker } + +function TFrameLocker.GetFrame: TFrame; +begin + Result := FComponent as TFrame; +end; + +function TFrameLocker.Lock: TFrame; +begin + Result := inherited Lock as TFrame; +end; + +procedure TFrameLocker.SetFrame(const Value: TFrame); +begin + FComponent := Value; +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + +{ TStreamLocker } + +function TStreamLocker.getStream: TStream; +begin + result := FSubject as TStream; +end; + +function TStreamLocker.Lock: TStream; +var + Temp: TObject; +begin + if DoLock(Temp) then + Result := Temp as TStream + else + Result := nil; +end; + +procedure TStreamLocker.setStream(const Value: TStream); +begin + FSubject := Value; +end; + +//-------------------------------------------------------------------------------------------------- + +{ TInterfaceListLocker } + +function TInterfaceListLocker.GetInterfaceList: TInterfaceList; +begin + result := FSubject as TInterfaceList; +end; + +function TInterfaceListLocker.Lock: TInterfaceList; +var + Temp: TObject; +begin + if DoLock(Temp) then + Result := Temp as TInterfaceList + else + Result := nil; +end; + +function TInterfaceListLocker.LockEx(ATimeout: integer): TInterfaceList; +var + Temp: TObject; +begin + if DoLock(Temp, true, ATimeout) then + Result := Temp as TInterfaceList + else + Result := nil; +end; + + +procedure TInterfaceListLocker.SetInterfaceList(const Value: TInterfaceList); +begin + FSubject := value; +end; + +{ TOrderedListLocker } + +function TOrderedListLocker.GetOrderedList: TOrderedList; +begin + Result := FSubject as TOrderedList; +end; + +function TOrderedListLocker.Lock: TOrderedList; +var + Temp: TObject; +begin + if DoLock(Temp) then + Result := Temp as TOrderedList + else + Result := nil; +end; + +procedure TOrderedListLocker.SetOrderedList(const Value: TOrderedList); +begin + FSubject := Value; +end; + +end. diff --git a/delphi/Message.pas b/delphi/Message.pas new file mode 100644 index 00000000..c9a09c55 --- /dev/null +++ b/delphi/Message.pas @@ -0,0 +1,3756 @@ +{----------------------------------------------------------------------------- + Unit Name: + Author: Matthew Emson + Date: 05/06/2003 + Purpose: Generic OO Message class for IPC. + History: 1.0 Basic code implementation. + 1.1 09/06/2003, M Emson + Messages can now contain messages. A reply could there- + fore contain the request.. Also allows for hierarchies + of data to be stored in message format. Also added a + DataCount and DataAt functionalits to genericly get at + the data in the message, you could search for data + using the FindTypeAtIndex to get the datatype and DataAt + to grab the item. I'm slowly (as nedded) adding more + specific routines to access types. StringAt and + MessageAt being the first two (as these are the + nastiest to access via DataAt. + 1.2 10/06/2003, M Emson + Fixed bugs in the message flatteneing protocol. + 1.3 14/06/2003, M Emson + Made FindDataStream a function that returns boolean + This allows an easy way to detect no data + 1.4 ??/??/2003, M Emson - fix memory leaks. + + 1.5 04/11/2003, M Emson + integrated port of Muscle message classes and added a + bridge conversion routine to BMessage classes. + 1.6 16/09/2004, M Emson | + Removed all the legacy and additional cruft. Renamed + TNewBMessage to TBMessage. Moved a load of + ImucscleMessage routines into IBMessage to simplify + Access to them. + 1.7 04/07/2005, M Emson + Updated and cleaned for release to LCS for inclusion + with main MUSCLE distro. Licensed as modified BSD. + +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +unit Message; + +interface + +uses + Classes, sysutils, Contnrs, + + TypeConstants, + Flattenable, + Point, + Rect; + +type + + {.$define DONTUSESILENTEXCEPTIONS}//Use this to force exceptions to surface to the user... + +{$IFDEF DONTUSESILENTEXCEPTIONS} + EPortableMessageError = class(Exception); //bickety-bam stylee +{$ELSE DONTUSESILENTEXCEPTIONS} + EPortableMessageError = class(EAbort); //dragon ninja stylee +{$ENDIF DONTUSESILENTEXCEPTIONS} + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //if you want to force a non-silent exception, use this as the base.. + EPortableMessageErrorAlt = class(Exception); //But use the code below rather than normal exception code... + //otherwise you may miss an exception in normal use... This is where Borland having some kind of multiple inheritence or + //flag within all exceptions for silent exceptions would have been cool... Dammit.. + // + //use the following code when trapping for generic exceptions :- + // except + // on e: do begin //<--- all specific exceptions trapped first + // { ...handle } + // end; + // { ...etc } + // on e: Exception do begin //<--- trap generic EPortableMessageError exceptions + // if (E is EPortableMessageError) then begin + // { ...trap specific error... } + // end + // else if (E is EPortableMessageErrorAlt) then begin //<--- trap generic EPortableMessageErrorAlt exceptions + // { ...trap specific error... } + // end + // else try //<--- Trap all other exceptions.... + // raise; //<--- re-raise the exception to make code treat like new exception + // except + // { ...other exception code... } + // end; + // end; + // end; + // + // This code could be worked into an *.inc file... + + EPortableMessageReadonlyError = class(EPortableMessageError); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //for internal use.... once the hierarchy is in place, this will become private + + IFlattenable = interface ['{BEB9BD12-3F90-4AAA-9582-1D6E78631E79}'] + procedure Flatten(AOut: TStream); + procedure Unflatten(ain: TStream; numBytes: integer); + function flattenedSize: integer; + end; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //Note to the cautious... + //Remember, the more data you add to a message, the larger impact it has on the transport!!! + //because messages are meant to be sent over tcp/ip, they are flattened before transport, and then + //unflattened on the target machine. This has little impact for small messages.. but if you add streams + //to the message, you *will* notice a really big speed degradation. Be cautious.. send a message that + //warns of data pending and only *then* stream that data via another means... especially if it's larger + //than 100k. + // + //At some point I plan a 'TBNetworkFile' that will address this issue. + IPortableMessage = interface(IFlattenable)['{BBF40989-24B4-40B7-8C49-CB93A5B81A87}'] + + //procedure AddVariant(const Name: string; const data: Variant; const datatype: TPortableMessageDataType); no longer supported + procedure AddString(const Name: string; const data: string); + procedure AddInteger(const Name: string; const data: integer); + procedure AddCardinal(const Name: string; const data: cardinal); + procedure AddBoolean(const Name: string; const data: boolean); + procedure AddFloat(const Name: string; const data: single); + procedure AddDouble(const Name: string; const data: double); //for TDateTime etc //V1_4_4_x + procedure AddDataStream(const Name: string; data: TStream); + procedure AddMessage(const Name: string; data: IPortableMessage); + + function ReplaceString(const Name: string; const data: string): boolean; + function ReplaceInteger(const Name: string; const data: integer): boolean; + function ReplaceCardinal(const Name: string; const data: cardinal): boolean; + function ReplaceBoolean(const Name: string; const data: boolean): boolean; + function ReplaceFloat(const Name: string; const data: single): boolean; + function ReplaceDouble(const Name: string; const data: double): boolean; //V1_4_4_x + function ReplaceMessage(const Name: string; data: IPortableMessage): boolean; + + //procedure FindVariant(const Name: string; var data: variant); + function FindString(const Name: string): string; overload; + function FindInteger(const Name: string): Integer; overload; + function FindCardinal(const Name: string): Cardinal; overload; + function FindFloat(const Name: string): single; overload; + function FindBoolean(const Name: string): boolean; overload; + function FindDouble(const Name: string): double; overload; //V1_4_4_x + function FindDataStream(const Name: string; data: TStream): boolean; overload; + function FindDataStreamData(const Name: string; toStream: TStream; dataCount: integer): integer; overload; //1_4_2_2 - jase, your wish... + function FindMessage(const Name: string): IPortableMessage; overload; + + function findString(const Name: string; var data: string): boolean; overload; + function findInt8(const Name: string; var data: ShortInt): boolean; overload; + function findInt16(const Name: string; var data: SmallInt): boolean; overload; + function findInt32(const Name: string; var data: LongInt): boolean; overload; + function findInt64(const Name: string; var data: Int64): boolean; overload; + function findFloat(const Name: string; var data: single): boolean; overload; + function findDouble(const Name: string; var data: double): boolean; overload; + function findBool(const Name: string; var data: boolean): boolean; overload; + function findMessage(const Name: string; var data: IPortableMessage): boolean; overload; + + function findString(const Name: string; index: integer; var data: string): boolean; overload; + function findInt8(const Name: string; index: integer; var data: ShortInt): boolean; overload; + function findInt16(const Name: string; index: integer; var data: SmallInt): boolean; overload; + function findInt32(const Name: string; index: integer; var data: LongInt): boolean; overload; + function findInt64(const Name: string; index: integer; var data: Int64): boolean; overload; + function findFloat(const Name: string; index: integer; var data: single): boolean; overload; + function findDouble(const Name: string; index: integer; var data: double): boolean; overload; + function findBool(const Name: string; index: integer; var data: boolean): boolean; overload; + function findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; overload; + function findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; overload; + function findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; overload; + + function fieldNames: TStrings; //ME 06/07/2005 - wow... holy bring forward method at htis late stage... + procedure Clear; + + function deleteField(const Name: string; index: integer = 0): boolean; + + function GetWhat: Cardinal; + procedure SetWhat(const Value: Cardinal); + property What: Cardinal read GetWhat write SetWhat; + + procedure copyFrom(const source: IPortableMessage); + + function toString: string; //DOH! should have done this a while ago... + function countFields(fieldname: string; fieldType: cardinal = B_ANY_TYPE): integer; overload; + function countFields(fieldType: cardinal = B_ANY_TYPE): integer; overload; + end; + + + //This interface is kept because it still does a couple of things the IPortableMessage doesn't. + ImuscleMessage = interface ['{85555CB0-7AA5-465C-9A87-A2C0AECDB951}'] + procedure addString(const Name: string; const data: string); + procedure addInt8(const Name: string; const data: Shortint); + procedure addInt16(const Name: string; const data: SmallInt); + procedure addInt32(const Name: string; const data: LongInt); + procedure addInt64(const Name: string; const data: int64); + procedure addBool(const Name: string; const data: boolean); + procedure addFloat(const Name: string; const data: single); + procedure addDouble(const Name: string; const data: double); + //procedure addMessage(const Name: string; data: TmuscleMessage); + procedure addRect(const Name: string; data: TmuscleRect); + procedure addPoint(const Name: string; data: TmusclePoint); + + function replaceString(const Name: string; const data: string): boolean; + function replaceInt8(const Name: string; const data: ShortInt): boolean; + function replaceInt16(const Name: string; const data: SmallInt): boolean; + function replaceInt32(const Name: string; const data: LongInt): boolean; + function replaceInt64(const Name: string; const data: Int64): boolean; + function replaceBool(const Name: string; const data: boolean): boolean; + function replaceFloat(const Name: string; const data: single): boolean; + function replaceDouble(const Name: string; const data: double): boolean; + //function replaceMessage(const Name: string; data: TmuscleMessage): boolean; overload; + function replaceRect(const Name: string; data: TmuscleRect): boolean; + function replacePoint(const Name: string; data: TmusclePoint): boolean; + + //function replaceMessage(const Name: string; index: integer; data: TmuscleMessage): boolean; overload; + + function findString(const Name: string; var data: string): boolean; overload; + function findInt8(const Name: string; var data: ShortInt): boolean; overload; + function findInt16(const Name: string; var data: SmallInt): boolean; overload; + function findInt32(const Name: string; var data: LongInt): boolean; overload; + function findInt64(const Name: string; var data: Int64): boolean; overload; + function findFloat(const Name: string; var data: single): boolean; overload; + function findDouble(const Name: string; var data: double): boolean; overload; + function findBool(const Name: string; var data: boolean): boolean; overload; + function findMessage(const Name: string; var data: IPortableMessage): boolean; overload; + function findRect(const Name: string; var data: TmuscleRect): boolean; overload; + function findPoint(const Name: string; var data: TmusclePoint): boolean; overload; + + function findString(const Name: string; index: integer; var data: string): boolean; overload; + function findInt8(const Name: string; index: integer; var data: ShortInt): boolean; overload; + function findInt16(const Name: string; index: integer; var data: SmallInt): boolean; overload; + function findInt32(const Name: string; index: integer; var data: LongInt): boolean; overload; + function findInt64(const Name: string; index: integer; var data: Int64): boolean; overload; + function findFloat(const Name: string; index: integer; var data: single): boolean; overload; + function findDouble(const Name: string; index: integer; var data: double): boolean; overload; + function findBool(const Name: string; index: integer; var data: boolean): boolean; overload; + function findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; overload; + function findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; overload; + function findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; overload; + + function countFields(fieldname: string; fieldType: cardinal = B_ANY_TYPE): integer; overload; //V1_4_4_x + function countFields(fieldType: cardinal = B_ANY_TYPE): integer; overload; + + procedure Clear; + + function deleteField(const Name: string; index: integer = 0): boolean; + + function toString: string; + end; + + //------------------------------------------------------------------------------------------------------------------------------------------ + + // muscle message is another message format (TM) + +const + OLDEST_SUPPORTED_PROTOCOL_VERSION = 1347235888; // 'PM00' + CURRENT_PROTOCOL_VERSION = 1347235888; // 'PM00' + +type + TmuscleMessageField = class(TmuscleFlattenable) + private + FtypeCode: cardinal; + FPayLoad: TList; + + procedure SetPayLoad(const Value: TList); + + public + constructor Create; overload; virtual; + constructor Create(AtypeCode: cardinal); overload; virtual; + + destructor Destroy; override; + + function Count: integer; + function flattenedItemSize: integer; virtual; + + //from TmuscleFlattenable + function isFixedSize: boolean; override; + function typeCode: cardinal; override; + + function flattenedSize: integer; override; + function cloneFlat: TmuscleFlattenable; override; + procedure setEqualTo(setFromMe: TmuscleFlattenable); override; //throws ClassCastException; ... assign??? + + procedure flatten(AOut: TStream); override; + function allowsTypeCode(code: integer): boolean; override; + procedure unflatten(ain: TStream; numBytes: integer); override; + + //mainly for testing... + function toString: string; override; + + property PayLoad: TList read FPayLoad write SetPayLoad; + end; + + //------------------------------------------------------------------------------------------------------------------------------------------ + + TmuscleMessage = class; //forward + + TmuscleCustomMessage = class(TmuscleFlattenable, ImuscleMessage) + private + Fwhat: cardinal; + + _fieldTable: TStringList; + + procedure Setwhat(const Value: cardinal); + function Getwhat: cardinal; + + procedure ensureFieldTableAllocated; + function getCreateOrReplaceField(fieldname: string; fieldType: cardinal): TmuscleMessageField; + + protected + procedure addString(const Name: string; const data: string); virtual; + procedure addInt8(const Name: string; const data: Shortint); virtual; + procedure addInt16(const Name: string; const data: SmallInt); virtual; + procedure addInt32(const Name: string; const data: LongInt); virtual; + procedure addInt64(const Name: string; const data: int64); virtual; + procedure addBool(const Name: string; const data: boolean); virtual; + procedure addFloat(const Name: string; const data: single); virtual; + procedure addDouble(const Name: string; const data: double); virtual; + procedure addMessage(const Name: string; data: IPortableMessage); virtual; + procedure addRect(const Name: string; data: TmuscleRect); virtual; + procedure addPoint(const Name: string; data: TmusclePoint); virtual; + + function replaceString(const Name: string; const data: string): boolean; virtual; + function replaceInt8(const Name: string; const data: ShortInt): boolean; virtual; + function replaceInt16(const Name: string; const data: SmallInt): boolean; virtual; + function replaceInt32(const Name: string; const data: LongInt): boolean; virtual; + function replaceInt64(const Name: string; const data: Int64): boolean; virtual; + function replaceBool(const Name: string; const data: boolean): boolean; virtual; + function replaceFloat(const Name: string; const data: single): boolean; virtual; + function replaceDouble(const Name: string; const data: double): boolean; virtual; + function replaceMessage(const Name: string; data: IPortableMessage): boolean; overload; virtual; + function replaceRect(const Name: string; data: TmuscleRect): boolean; virtual; + function replacePoint(const Name: string; data: TmusclePoint): boolean; virtual; + + function replaceMessage(const Name: string; index: integer; data: IPortableMessage): boolean; overload; virtual; + + function findString(const Name: string; var data: string): boolean; overload; virtual; + function findInt8(const Name: string; var data: ShortInt): boolean; overload; virtual; + function findInt16(const Name: string; var data: SmallInt): boolean; overload; virtual; + function findInt32(const Name: string; var data: LongInt): boolean; overload; virtual; + function findInt64(const Name: string; var data: Int64): boolean; overload; virtual; + function findFloat(const Name: string; var data: single): boolean; overload; virtual; + function findDouble(const Name: string; var data: double): boolean; overload; virtual; + function findBool(const Name: string; var data: boolean): boolean; overload; virtual; + function findMessage(const Name: string; var data: IPortableMessage): boolean; overload; virtual; + function findRect(const Name: string; var data: TmuscleRect): boolean; overload; virtual; + function findPoint(const Name: string; var data: TmusclePoint): boolean; overload; virtual; + + function findString(const Name: string; index: integer; var data: string): boolean; overload; virtual; + function findInt8(const Name: string; index: integer; var data: ShortInt): boolean; overload; virtual; + function findInt16(const Name: string; index: integer; var data: SmallInt): boolean; overload; virtual; + function findInt32(const Name: string; index: integer; var data: LongInt): boolean; overload; virtual; + function findInt64(const Name: string; index: integer; var data: Int64): boolean; overload; virtual; + function findFloat(const Name: string; index: integer; var data: single): boolean; overload; virtual; + function findDouble(const Name: string; index: integer; var data: double): boolean; overload; virtual; + function findBool(const Name: string; index: integer; var data: boolean): boolean; overload; virtual; + function findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; overload; virtual; + function findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; overload; virtual; + function findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; overload; virtual; + + public + property what: cardinal read Getwhat write Setwhat; + + constructor Create; overload; virtual; + constructor Create(w: cardinal); overload; virtual; + constructor Create(copyMe: TmuscleMessage); overload; virtual; + + destructor Destroy; override; //V1_4_3_9 + + function fieldNames: TStrings; + procedure Clear; + + procedure copyField(fieldName: string; msg: IPortableMessage); virtual; + function countFields(fieldType: cardinal = B_ANY_TYPE): integer; overload; + + function countFields(fieldname: string; fieldType: cardinal = B_ANY_TYPE): integer; overload; + + procedure renameField(old, new: string); + + function deleteField(const Name: string; index: integer = 0): boolean; + + class function whatString(w: cardinal): string; //this should really be external.. bloody Java + + //from TmuscleFlattenable + function isFixedSize: boolean; override; + function typeCode: cardinal; override; + function flattenedSize: integer; override; + function cloneFlat: TmuscleFlattenable; override; + procedure setEqualTo(setFromMe: TmuscleFlattenable); override; //throws ClassCastException; ... assign??? + + procedure flatten(AOut: TStream); override; + function allowsTypeCode(code: integer): boolean; override; + procedure unflatten(ain: TStream; numBytes: integer); override; + + //mainly for testing... + function toString: string; override; + end; + + TmuscleMessage = class(TmuscleCustomMessage, ImuscleMessage) + public + procedure addString(const Name: string; const data: string); override; + procedure addInt8(const Name: string; const data: Shortint); override; + procedure addInt16(const Name: string; const data: SmallInt); override; + procedure addInt32(const Name: string; const data: LongInt); override; + procedure addInt64(const Name: string; const data: int64); override; + procedure addBool(const Name: string; const data: boolean); override; + procedure addFloat(const Name: string; const data: single); override; + procedure addDouble(const Name: string; const data: double); override; + procedure addMessage(const Name: string; data: IPortableMessage); override; + procedure addRect(const Name: string; data: TmuscleRect); override; + procedure addPoint(const Name: string; data: TmusclePoint); override; + + function replaceString(const Name: string; const data: string): boolean; override; + function replaceInt8(const Name: string; const data: ShortInt): boolean; override; + function replaceInt16(const Name: string; const data: SmallInt): boolean; override; + function replaceInt32(const Name: string; const data: LongInt): boolean; override; + function replaceInt64(const Name: string; const data: Int64): boolean; override; + function replaceBool(const Name: string; const data: boolean): boolean; override; + function replaceFloat(const Name: string; const data: single): boolean; override; + function replaceDouble(const Name: string; const data: double): boolean; override; + function replaceMessage(const Name: string; data: IPortableMessage): boolean; overload; override; + function replaceRect(const Name: string; data: TmuscleRect): boolean; override; + function replacePoint(const Name: string; data: TmusclePoint): boolean; override; + + function replaceMessage(const Name: string; index: integer; data: IPortableMessage): boolean; overload; override; + + function findString(const Name: string; var data: string): boolean; overload; override; + function findInt8(const Name: string; var data: ShortInt): boolean; overload; override; + function findInt16(const Name: string; var data: SmallInt): boolean; overload; override; + function findInt32(const Name: string; var data: LongInt): boolean; overload; override; + function findInt64(const Name: string; var data: Int64): boolean; overload; override; + function findFloat(const Name: string; var data: single): boolean; overload; override; + function findDouble(const Name: string; var data: double): boolean; overload; override; + function findBool(const Name: string; var data: boolean): boolean; overload; override; + function findMessage(const Name: string; var data: IPortableMessage): boolean; overload; override; + function findRect(const Name: string; var data: TmuscleRect): boolean; overload; override; + function findPoint(const Name: string; var data: TmusclePoint): boolean; overload; override; + + function findString(const Name: string; index: integer; var data: string): boolean; overload; override; + function findInt8(const Name: string; index: integer; var data: ShortInt): boolean; overload; override; + function findInt16(const Name: string; index: integer; var data: SmallInt): boolean; overload; override; + function findInt32(const Name: string; index: integer; var data: LongInt): boolean; overload; override; + function findInt64(const Name: string; index: integer; var data: Int64): boolean; overload; override; + function findFloat(const Name: string; index: integer; var data: single): boolean; overload; override; + function findDouble(const Name: string; index: integer; var data: double): boolean; overload; override; + function findBool(const Name: string; index: integer; var data: boolean): boolean; overload; override; + function findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; overload; override; + function findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; overload; override; + function findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; overload; override; + + end; + + ////////////////////////////////////////////////////////////////////////////// + + TPortableMessage = class(TmuscleCustomMessage, ImuscleMessage, IPortableMessage) + protected + function __Flatten: string; //depricated + procedure __Unflatten(data: string); //depricated + + public + //procedure AddVariant(const Name: string; const data: Variant; const datatype: TPortableMessageDataType); + procedure AddString(const Name: string; const data: string); override; + procedure AddInteger(const Name: string; const data: integer); + procedure AddCardinal(const Name: string; const data: cardinal); + procedure AddBoolean(const Name: string; const data: boolean); + procedure AddFloat(const Name: string; const data: single); override; + procedure AddDouble(const Name: string; const data: double); override; //for TDateTime etc //V1_4_4_x + procedure AddDataStream(const Name: string; data: TStream); + procedure AddMessage(const Name: string; data: IPortableMessage); override; + + function ReplaceString(const Name: string; const data: string): boolean; override; + function ReplaceInteger(const Name: string; const data: integer): boolean; + function ReplaceCardinal(const Name: string; const data: cardinal): boolean; + function ReplaceBoolean(const Name: string; const data: boolean): boolean; + function ReplaceFloat(const Name: string; const data: single): boolean; override; + function ReplaceDouble(const Name: string; const data: double): boolean; override; //V1_4_4_x + function ReplaceMessage(const Name: string; data: IPortableMessage): boolean; override; + + procedure FindVariant(const Name: string; var data: variant); + function FindString(const Name: string): string; overload; + function FindInteger(const Name: string): Integer; + function FindCardinal(const Name: string): Cardinal; + function FindFloat(const Name: string): single; overload; + function FindDouble(const Name: string): double; overload; //V1_4_4_x + function FindBoolean(const Name: string): boolean; + function FindDataStream(const Name: string; data: TStream): boolean; + function FindDataStreamData(const Name: string; toStream: TStream; dataCount: integer): integer; + function FindMessage(const Name: string): IPortableMessage; overload; + + function GetWhat: Cardinal; + procedure SetWhat(const Value: Cardinal); + property What: Cardinal read GetWhat write SetWhat; + + procedure copyFrom(const source: IPortableMessage); + end; + +function GetCharsAsInteger(fchars: string): cardinal; //convenience routine... + +//lovely binhex routines +procedure StreamHexToBin(Reader: TStream; Writer: TStream); //convenience routine... +procedure StreamBinToHex(Reader: TStream; Writer: TStream); //convenience routine... + +implementation + +uses + MuscleExceptions, windows, Winsock; + + +function GetCharsAsInteger(fchars: string): cardinal; +var + ch: array[0..3] of char; +begin + strlcopy(@ch, pchar(fchars), 4); + Result := cardinal(ch); +end; + +procedure StreamBinToHex(Reader: TStream; Writer: TStream); +const + BytesPerLine = 32; +var + I: Integer; + Count: Longint; + Buffer: array[0..BytesPerLine - 1] of Char; + Text: array[0..BytesPerLine * 2 - 1] of Char; +begin + Count := Reader.Size; + while Count > 0 do + begin + if Count >= 32 then + I := 32 + else + I := Count; + Reader.Read(Buffer, I); + BinToHex(Buffer, Text, I); + Writer.Write(Text, I * 2); + Dec(Count, I); + end; +end; + +procedure StreamHexToBin(Reader: TStream; Writer: TStream); +const + BytesPerLine = 32; +var + I, _read: Integer; + Count: Longint; + Buffer: array[0..BytesPerLine - 1] of Char; + Text: array[0..BytesPerLine * 2 - 1] of Char; +begin + Count := Reader.Size; + while Count > 0 do + begin + if Count >= 32 then + I := 32 + else + I := Count; + Reader.Read(Text, I); + _read := HexToBin(Text, Buffer, Sizeof(Buffer)); + Writer.Write(Buffer, _read); + Dec(Count, I); + end; +end; + +//internal data storage + +type + TBDataRec = class(TPersistent) + public + name: Shortstring; + muscledatatype: Cardinal; + + constructor Create(Adatatype: Cardinal); overload; + destructor Destroy; override; //V1_4_3_9 + + function GetData: Variant; virtual; abstract; + procedure SetData(AData: variant); virtual; abstract; + + function DataAsString: string; virtual; + + procedure DataAsStream(s: TStream); virtual; + + property __Data: Variant read GetData write SetData; + + function SizeOf: cardinal; virtual; abstract; + end; + + TStringBDataRec = class(TBDataRec) + public + IsShortString: Boolean; + + end; + + TShortStringBDataRec = class(TStringBDataRec) + public + Data: shortstring; + constructor Create(AData: shortstring); overload; + + function DataAsString: string; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TLongStringBDataRec = class(TStringBDataRec) + public + Data: string; + constructor Create(AData: string); overload; + + function DataAsString: string; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TFloatBDataRec = class(TBDataRec) + public + Data: Single; + constructor Create(AData: single); overload; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function DataAsString: string; override; + + function SizeOf: cardinal; override; + end; + + TDoubleBDataRec = class(TBDataRec) + public + Data: Double; + constructor Create(AData: Double); + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TInt8BDataRec = class(TBDataRec) + public + Data: shortint; + constructor Create(AData: shortint); + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TInt16BDataRec = class(TBDataRec) + public + Data: smallint; + constructor Create(AData: smallint); + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TInt32BDataRec = class(TBDataRec) + public + Data: Integer; + constructor Create(AData: Integer); + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TLongBDataRec = class(TBDataRec) + public + Data: Int64; + constructor Create(AData: Int64); + constructor CreateStr(AData: string); + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + + TBoolBDataRec = class(TBDataRec) + public + Data: boolean; + + constructor Create(AData: Boolean); overload; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function DataAsString: string; override; + + function SizeOf: cardinal; override; + end; + + TBLOBDataRec = class(TBDataRec) + public + Data: TMemoryStream; + constructor Create(AData: TStream); + constructor CreateFromHex(AData: string); + destructor Destroy; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + procedure DataAsStream(s: TStream); override; + + function DataAsString: string; override; + + function SizeOf: cardinal; override; + end; + + TPortableMessageDataRec = class(TBDataRec) + public + //Data: TmuscleCustomMessage; + Data: IPortableMessage; + constructor Create(AData: IPortableMessage); + constructor CreateStr(AData: TStream); + destructor Destroy; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TRectDataRec = class(TBDataRec) + public + Data: TmuscleRect; + constructor Create(AData: TmuscleRect); + constructor CreateStr(AData: TStream); + destructor Destroy; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + TPointDataRec = class(TBDataRec) + public + Data: TmusclePoint; + constructor Create(AData: TmusclePoint); + constructor CreateStr(AData: TStream); + destructor Destroy; override; + + function GetData: Variant; override; + procedure SetData(AData: variant); override; + + function SizeOf: cardinal; override; + end; + + +// ------------------------------------------------------------------------------------------------ +// TBDataRec +// ------------------------------------------------------------------------------------------------ + +constructor TBDataRec.Create(Adatatype: Cardinal); +begin + muscledatatype := Adatatype; +end; + + +// ------------------------------------------------------------------------------------------------ + //new fn V1_4_3_9 +destructor TBDataRec.Destroy; +begin + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TBDataRec.DataAsStream(s: TStream); +var + t: TStringStream; +begin + t := TStringStream.Create(DataAsString); + try + t.position := 0; + s.CopyFrom(t, t.size); + finally + t.free; + end; +end; + +// ------------------------------------------------------------------------------------------------ + +function TBDataRec.DataAsString: string; +begin + Result := ''; +end; + +// ------------------------------------------------------------------------------------------------ +// TShortStringBDataRec +// ------------------------------------------------------------------------------------------------ + +constructor TShortStringBDataRec.Create(AData: shortstring); +begin + inherited Create(B_STRING_TYPE); + + IsShortString := true; + + Data := AData; + +end; + +function TShortStringBDataRec.DataAsString: string; +begin + result := ''; +// Result := format('%s'#6#6#6'%d'#6#6#6'%s'#7#7#7, [Name, Ord(dataType), Data]); +end; + +function TShortStringBDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TShortStringBDataRec.SetData(AData: variant); +begin + Data := VarToStr(AData); +end; + +{ TFloatBDataRec } + +function TFloatBDataRec.DataAsString: string; +begin + result := ''; +// Result := format('%s'#6#6#6'%d'#6#6#6'%f'#7#7#7, [Name, Ord(dataType), Data]); +end; + +function TFloatBDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TFloatBDataRec.SetData(AData: variant); +begin + Data := AData; +end; + +constructor TBoolBDataRec.Create(AData: Boolean); +begin + inherited Create(B_BOOL_TYPE); + + Data := Adata; +end; + +function TBoolBDataRec.DataAsString: string; +begin + result := ''; +// Result := format('%s'#6#6#6'%d'#6#6#6'%u'#7#7#7, [Name, Ord(dataType), Ord(Data)]); +end; + +function TBoolBDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TBoolBDataRec.SetData(AData: variant); +begin + Data := VarAsType(Adata, varBoolean); +end; + +constructor TLongStringBDataRec.Create(AData: string); +begin + inherited Create(B_STRING_TYPE); + + Data := AData; + + IsShortString := false; +end; + +function TLongStringBDataRec.DataAsString: string; +begin + result := ''; +// Result := format('%s'#6#6#6'%d'#6#6#6'%s'#7#7#7, [Name, Ord(dataType), Data]); +end; + +function TLongStringBDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TLongStringBDataRec.SetData(AData: variant); +begin + Data := VarToStr(AData); +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + +//Tmuscle extensions + +function TShortStringBDataRec.SizeOf: cardinal; +begin + Result := length(data); +end; + +function TBoolBDataRec.SizeOf: cardinal; +begin + result := 1; //defined by Tmuscle code... +end; + +function TLongStringBDataRec.SizeOf: cardinal; +begin + Result := length(data); +end; + +{ TFloatBDataRec } + +constructor TFloatBDataRec.Create(AData: Single); +begin + inherited Create(B_FLOAT_TYPE); + + Data := AData; +end; + +function TFloatBDataRec.SizeOf: cardinal; +begin + Result := System.sizeof(single); +end; + +{ TInt32BDataRec } + +constructor TInt32BDataRec.Create(AData: Integer); +begin + inherited Create(B_INT32_TYPE); + + Data := AData; +end; + +function TInt32BDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TInt32BDataRec.SetData(AData: variant); +begin + Data := AData; +end; + +function TInt32BDataRec.SizeOf: cardinal; +begin + result := System.sizeof(integer); +end; + +{ TInt8BDataRec } + +constructor TInt8BDataRec.Create(AData: shortint); +begin + inherited Create(B_INT8_TYPE); + + Data := AData; +end; + +function TInt8BDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TInt8BDataRec.SetData(AData: variant); +begin + Data := AData; +end; + +function TInt8BDataRec.SizeOf: cardinal; +begin + result := System.sizeof(shortint); +end; + +{ TInt16BDataRec } + +constructor TInt16BDataRec.Create(AData: smallint); +begin + inherited Create(B_INT16_TYPE); + + Data := AData; +end; + +function TInt16BDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TInt16BDataRec.SetData(AData: variant); +begin + Data := AData; +end; + +function TInt16BDataRec.SizeOf: cardinal; +begin + Result := System.sizeof(smallint); +end; + +{ TMessageDataRec } + +constructor TPortableMessageDataRec.Create(AData: IPortableMessage); +begin + inherited Create(B_MESSAGE_TYPE); + + //Data := AData; + Data := TPortableMessage.Create; + + (Data).copyFrom(AData); +end; + +constructor TPortableMessageDataRec.CreateStr(AData: TStream); +begin + inherited Create(B_MESSAGE_TYPE); + + Data := TPortableMessage.Create; + AData.Position := 0; + Data.Unflatten(AData, AData.Size); +end; + +destructor TPortableMessageDataRec.Destroy; +begin + //Data.free; //we have to own this as we constructed it... + + Data := nil; + + inherited; +end; + +function TPortableMessageDataRec.GetData: Variant; +begin + //Result := Data.Flatten; +end; + +procedure TPortableMessageDataRec.SetData(AData: variant); +var + s: string; + ss: TStringStream; +begin + s := VarToStr(AData); + + if not Assigned(Data) then + Data := TPortableMessage.Create; + + ss := TStringStream.create(s); + try + ss.position := 0; + Data.Unflatten(ss, ss.size); + finally + ss.free; + end; +end; + +function TPortableMessageDataRec.SizeOf: cardinal; +begin + result := Data.flattenedSize; +end; + +// ------------------------------------------------------------------------------------------------ +// TmuscleCustomMessage +// ------------------------------------------------------------------------------------------------ + +constructor TmuscleCustomMessage.Create; +begin + ensureFieldTableAllocated; +end; + +// ------------------------------------------------------------------------------------------------ + +constructor TmuscleCustomMessage.Create(w: cardinal); +begin + Create; + Fwhat := w; +end; + +// ------------------------------------------------------------------------------------------------ + +constructor TmuscleCustomMessage.Create(copyMe: TmuscleMessage); +begin + Create; + setEqualTo(copyMe); +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TmuscleCustomMessage.ensureFieldTableAllocated; +begin + if (_fieldTable = nil) then + _fieldTable := TStringList.Create; +end; + +// ------------------------------------------------------------------------------------------------ + //new fn V1_4_3_9 +destructor TmuscleCustomMessage.Destroy; +begin + Clear; + + if Assigned (_fieldTable) then //oops..... + FreeAndNil (_fieldTable); + + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TmuscleCustomMessage.Clear; +var + i: integer; + +begin + if not Assigned (_fieldTable) then //V1_4_3_9 + exit; //V1_4_3_9 + + for i := _fieldTable.Count - 1 downto 0 do + TObject(_fieldTable.Objects[i]).Free; + + _fieldTable.Clear; +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TmuscleCustomMessage.copyField(fieldName: string; msg: IPortableMessage); +//var +// count, i, ftype: integer; +begin + +//we need to know they type code of any given field for this to work... + +// +// count := (msg as ImuscleMessage).countFields(fieldName); +// +// if count > 0 then +// ftype := (msg as ImuscleMessage). +// for i := 0 to count -1 do +// begin +// +// end; +end; + +function TmuscleCustomMessage.allowsTypeCode(code: integer): boolean; +begin + Result := (code = B_MESSAGE_TYPE); +end; + +function TmuscleCustomMessage.cloneFlat: TmuscleFlattenable; +var + clone: TmuscleMessage; +begin + clone := TmuscleMessage.Create; + clone.setEqualTo(Self); + Result := clone; +end; + +function TmuscleCustomMessage.fieldNames: TStrings; +begin + //I'm sure I'm going to hate and change this... + + result := _fieldTable; +end; + +procedure TmuscleCustomMessage.flatten(AOut: TStream); +var + protocol, fieldcount, i, l, t: integer; + field: TmuscleMessageField; + + procedure WriteString(s: string; str: TStream); + var + i: integer; + nullch: char; + begin + for i := 1 to length(s) do + str.Write(s[i], sizeof(char)); + + nullch := #0; + str.Write(nullch, sizeof(char)); + end; + +begin + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + try + protocol := CURRENT_PROTOCOL_VERSION; //0 + AOut.Write(protocol, SizeOf(Integer)); //1 + AOut.Write(Fwhat, SizeOf(integer)); //2 + + fieldcount := countFields; + AOut.Write(fieldcount, SizeOf(Integer)); //3 + + for i := 0 to _fieldTable.Count - 1 do + begin + l := length(_fieldTable[i]) + 1; + + AOut.Write(l, SizeOf(Integer)); //4 + WriteString(_fieldTable[i], AOut); //5 + + field := TmuscleMessageField(_fieldTable.Objects[i]); + + t := field.typecode; + AOut.Write(t, sizeof(integer)); //6 + + t := field.flattenedSize; + AOut.Write(t, sizeof(integer)); //7 + + field.flatten(Aout); //8 ...hmmm, recursive.. + end; + + except + inherited; + end; +end; + +function TmuscleCustomMessage.flattenedSize: integer; +var + field: TmuscleMessageField; + i: integer; +begin + Result := 4 + 4 + 4; // Revision + Number-Of-Entries + what + + if (_fieldTable <> nil) then + begin + for i := 0 to _fieldTable.Count - 1 do + begin + field := TmuscleMessageField(_fieldTable.Objects[i]); + Result := Result + // namelength + namedata + typecode + entrydata length + entrydata + 4 + (length(_fieldTable[i]) + 1) + + 4 + 4; + result := result + field.flattenedSize; + end; + end; +end; + +function TmuscleCustomMessage.isFixedSize: boolean; +begin + Result := false; +end; + +procedure TmuscleCustomMessage.setEqualTo(setFromMe: TmuscleFlattenable); +//var +// copyMe: TmuscleMessage; +// fields: TStrings; +// i: integer; +begin + inherited; + +// clear; +// +// copyMe := TmuscleMessage(setFromMe); +// +// Fwhat := copyMe.what; +// fields := copyMe.fieldNames; +// try +// for i := 0 to fields.count - 1 do +// try +// copyMe.copyField(fields[i], TmuscleMessage(self)); +// except +// on E: Exception do +// raise Exception.Create('Field not found'#13#10'Original Error: ' + E.Message); +// end; +// finally +// Fields.Free; +// end; + +end; + +procedure TmuscleCustomMessage.Setwhat(const Value: cardinal); +begin + Fwhat := Value; +end; + +function TmuscleCustomMessage.toString: string; +var + i: integer; +begin + Result := 'Message: what=''' + whatString(Fwhat) + ''' (' + IntToStr(Fwhat) + '), countFields=' + + IntToStr(countFields) + ', flattenedSize=' + intToStr(flattenedSize) + #13#10; + + if (_fieldTable = nil) then + exit; //avoid an exception, I guess.... + + for i := 0 to _fieldTable.Count - 1 do + begin + Result := Result + ' ' + _fieldTable[i] + ': ' + TmuscleMessageField(_fieldTable.Objects[i]).toString; + end; +end; + +function TmuscleCustomMessage.typeCode: cardinal; +begin + Result := B_MESSAGE_TYPE; +end; + +procedure TmuscleCustomMessage.unflatten(ain: TStream; numBytes: integer); +var + protocolVersion, numEntries, fieldNameLength, i, t: integer; + s: pchar; + field: TmuscleMessageField; +begin + (* clear(); + int protocolVersion = in.readInt(); + if ((protocolVersion > CURRENT_PROTOCOL_VERSION)||(protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) throw new UnflattenFormatException("Version mismatch error"); + what = in.readInt(); + int numEntries = in.readInt(); + byte [] stringBuf = null; + if (numEntries > 0) ensureFieldTableAllocated(); + for (int i=0; i5)?fieldNameLength:5)*2]; + in.readFully(stringBuf, 0, fieldNameLength); + MessageField field = getCreateOrReplaceField(new String(stringBuf, 0, fieldNameLength-1), in.readInt()); + field.unflatten(in, in.readInt()); + _fieldTable.put(new String(stringBuf, 0, fieldNameLength-1), field); + } *) + + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + + try + clear; + + ain.Read(protocolVersion, sizeof(Integer)); //0 + if ((protocolVersion > CURRENT_PROTOCOL_VERSION) or (protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) then + raise Exception.Create('Version mismatch error'); + + ain.Read(Fwhat, sizeof(Integer)); //1 + + ain.Read(numEntries, sizeof(integer)); //2 + if (numEntries > 0) then + ensureFieldTableAllocated; + + s := nil; + try + + for i := 0 to numEntries - 1 do + begin + ain.Read(fieldNameLength, sizeof(Integer)); //3 + if (s = nil) or (strlen(s) < Cardinal(fieldNameLength)) then + begin //warning... + //this sucks... + if not (s = nil) then + strdispose(s); + + if (fieldNameLength <= 5) then + s := stralloc(10) + else + s := stralloc(fieldNameLength * 2); + end; + + ain.Read(s^, fieldNameLength); //4 + ain.Read(t, sizeof(Integer)); //5 + field := getCreateOrReplaceField(s, t); + + ain.Read(t, sizeof(Integer)); //6 + field.unflatten(ain, t); //7 + _fieldTable.AddObject(s, field); + end; + + finally + if (s <> nil) then + strdispose(s); + end; + except + inherited; + end; + +end; + +class function TmuscleCustomMessage.whatString(w: cardinal): string; +var + ch: array[0..4] of char; +begin + SetLength(Result, 4); //not syre this is still needed...? + + w := ntohl(w); + + move(w, ch, sizeof(cardinal)); + + ch[4] := #0; + result := ch; + + if (ch[0] = #0) and (ch[1] = #0) and (ch[2] = #0) and (ch[3] = #0) then + Result := 'XXXX'; +end; + +function TmuscleCustomMessage.countFields(fieldType: cardinal): integer; +var + i: integer; + field: TmuscleMessageField; +begin + if (_fieldTable = nil) then + begin + Result := 0; + end + else if (fieldType = B_ANY_TYPE) then + Result := _fieldTable.Count + else + begin + Result := 0; + + for i := 0 to _fieldTable.Count do + begin + field := TmuscleMessageField(_fieldTable.Objects[i]); + if (field.typeCode = fieldType) then + inc(Result); + end; + end; +end; + +procedure TmuscleCustomMessage.renameField(old, new: string); +var + i: integer; +begin + i := _fieldTable.IndexOf(old); + if (i > -1) then + begin + _fieldTable[i] := new; + end + else + raise Exception.Create('Field not found'); + +end; + +// ------------------------------------------------------------------------------------------------ + +function TmuscleCustomMessage.getCreateOrReplaceField(fieldname: string; fieldType: cardinal): TmuscleMessageField; +var + field: TmuscleMessageField; + idx: integer; +begin + //Result := nil; + ensureFieldTableAllocated; + + idx := _fieldTable.IndexOf(fieldname); + + if (idx > -1) then + begin + field := TmuscleMessageField(_fieldTable.Objects[idx]); + + if not (field.typeCode = fieldType) then + begin + //delete the item + _fieldTable.Delete(idx); + field.Free; + + Result := getCreateOrReplaceField(fieldName, fieldType); + end + else + Result := field; + end + else + begin + Result := TmuscleMessageField.Create(fieldtype); + end; +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TmuscleCustomMessage.addBool(const Name: string; const data: boolean); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TBoolBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TBoolBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +procedure TmuscleCustomMessage.addFloat(const Name: string; const data: single); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TFloatBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TFloatBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +procedure TmuscleCustomMessage.addInt16(const Name: string; const data: SmallInt); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TInt16BDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TInt16BDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +procedure TmuscleCustomMessage.addInt32(const Name: string; const data: Integer); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TInt32BDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TInt32BDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +procedure TmuscleCustomMessage.addInt64(const Name: string; const data: int64); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TLongBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TLongBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +procedure TmuscleCustomMessage.addInt8(const Name: string; const data: Shortint); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TInt8BDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TInt8BDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +procedure TmuscleCustomMessage.addMessage(const Name: string; data: IPortableMessage); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TPortableMessageDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TPortableMessageDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +procedure TmuscleCustomMessage.addString(const Name, data: string); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TLongStringBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TLongStringBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +function TmuscleCustomMessage.replaceBool(const Name: string; const data: boolean): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TBoolBDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceFloat(const Name: string; const data: single): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TFloatBDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceInt16(const Name: string; const data: SmallInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TInt16BDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceInt32(const Name: string; const data: Integer): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TInt32BDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceInt64(const Name: string; const data: Int64): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TLongBDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceInt8(const Name: string; const data: ShortInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TInt8BDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceMessage(const Name: string; data: IPortableMessage): boolean; +var + idx: integer; + fld: TmuscleMessageField; + m: IPortableMessage; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + try + m := TPortableMessageDataRec(fld.Payload[0]).Data; + finally + m := nil; + end; + + TPortableMessageDataRec(fld.Payload[0]).Data := Data; + + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceString(const Name, data: string): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TLongStringBDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +procedure TmuscleCustomMessage.addDouble(const Name: string; const data: double); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TDoubleBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TDoubleBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +function TmuscleCustomMessage.replaceDouble(const Name: string; const data: double): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TDoubleBDataRec(fld.Payload[0]).Data := Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +procedure TmuscleCustomMessage.addPoint(const Name: string; data: TmusclePoint); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TPointDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TPointDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +procedure TmuscleCustomMessage.addRect(const Name: string; data: TmuscleRect); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TRectDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TRectDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; + +end; + +function TmuscleCustomMessage.countFields(fieldname: string; fieldType: cardinal): integer; +var + i: integer; + field: TmuscleMessageField; +begin + if (_fieldTable = nil) or (_fieldTable.Count = 0) then + begin + Result := 0; + end + else + begin + Result := 0; + + for i := 0 to _fieldTable.Count - 1 do + begin + field := TmuscleMessageField(_fieldTable.Objects[i]); + if (AnsiCompareText(_fieldTable[i], fieldname) = 0) and ((field.typeCode = fieldType) or (fieldtype = B_ANY_TYPE)) then //V1_4_4_x + inc(Result, TmuscleMessageField(_fieldTable.Objects[i]).Count); + end; + end; + +end; + +function TmuscleCustomMessage.findBool(const Name: string; index: integer; var data: boolean): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TBoolBDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findBool(const Name: string; var data: boolean): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TBoolBDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findDouble(const Name: string; index: integer; var data: double): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TDoubleBDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findFloat(const Name: string; var data: single): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TFloatBDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findFloat(const Name: string; index: integer; var data: single): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TFloatBDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt16(const Name: string; var data: SmallInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TInt16BDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt16(const Name: string; index: integer; var data: SmallInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TInt16BDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt32(const Name: string; index: integer; var data: Integer): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TInt32BDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt32(const Name: string; var data: Integer): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TInt32BDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt64(const Name: string; var data: Int64): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TLongBDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt64(const Name: string; index: integer; var data: Int64): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TLongBDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt8(const Name: string; index: integer; var data: ShortInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TInt8BDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findInt8(const Name: string; var data: ShortInt): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TInt8BDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TPortableMessageDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findMessage(const Name: string; var data: IPortableMessage): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TPortableMessageDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TPointDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findPoint(const Name: string; var data: TmusclePoint): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TPointDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; + +end; + +function TmuscleCustomMessage.findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := TRectDataRec(fld.Payload[index]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findRect(const Name: string; var data: TmuscleRect): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TRectDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findString(const Name: string; var data: string): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := copy(TLongStringBDataRec(fld.Payload[0]).Data, 1, length(TLongStringBDataRec(fld.Payload[0]).Data)); //I know, I know... + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findString(const Name: string; index: integer; var data: string): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + Data := copy(TLongStringBDataRec(fld.Payload[index]).Data, 1, length(TLongStringBDataRec(fld.Payload[index]).Data)); //I know, I know... + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replacePoint(const Name: string; data: TmusclePoint): boolean; +var + idx: integer; + fld: TmuscleMessageField; + m: TmusclePoint; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + m := TPointDataRec(fld.Payload[0]).Data; + m.free; + + TPointDataRec(fld.Payload[0]).Data := Data; + + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.replaceRect(const Name: string; data: TmuscleRect): boolean; +var + idx: integer; + fld: TmuscleMessageField; + m: TmuscleRect; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + m := TRectDataRec(fld.Payload[0]).Data; + m.free; + + TRectDataRec(fld.Payload[0]).Data := Data; + + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.findDouble(const Name: string; var data: double): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + Data := TDoubleBDataRec(fld.Payload[0]).Data; + Result := True; + exit; + end; + end; + + Result := False; + +end; + +function TmuscleCustomMessage.replaceMessage(const Name: string; index: integer; data: IPortableMessage): boolean; +var + idx: integer; + fld: TmuscleMessageField; + m: IPortableMessage; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) and (index > -1) and (index < fld.Payload.Count) then + begin + m := TPortableMessageDataRec(fld.Payload[index]).Data; + + TPortableMessageDataRec(fld.Payload[index]).Data := Data; + + if (m <> data) then + m := nil; + + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TmuscleCustomMessage.deleteField(const Name: string; index: integer): boolean; +var + fld: TmuscleMessageField; + idx: integer; +begin + idx := _fieldTable.IndexOf(name); + + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + + if (fld.PayLoad.Count <= 0) or (index < 0) or (index >= fld.PayLoad.Count) then + begin + Result := false; + Exit; + end + else + begin + //remove field + fld.PayLoad.Delete(index); //object list cleans up for us... + + //is the fld now empty? + if (fld.PayLoad.Count = 0) then + begin + //yes... we should delete it... + _fieldTable.delete(idx); + fld.free; + end; + + Result := true; + end; + end + else + Result := false; +end; + +function TmuscleCustomMessage.Getwhat: cardinal; +begin + Result := Fwhat; +end; + +// ------------------------------------------------------------------------------------------------ +// TmuscleMessageField +// ------------------------------------------------------------------------------------------------ + +constructor TmuscleMessageField.Create; +begin + FPayload := TObjectList.Create(True); + + // if DebugMemLogOn then + // OutputDebugString ('++++ TmuscleMessageField.Create'); +end; + +// ------------------------------------------------------------------------------------------------ + +constructor TmuscleMessageField.Create(AtypeCode: cardinal); +begin + Create; + FtypeCode := AtypeCode; +end; + +// ------------------------------------------------------------------------------------------------ + +destructor TmuscleMessageField.Destroy; +begin + // if DebugMemLogOn then + // OutputDebugString ('---- TmuscleMessageField.Free'); + + FPayload.Free; + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +function TmuscleMessageField.allowsTypeCode(code: integer): boolean; +begin + result := (cardinal(code) = FtypeCode); +end; + +function TmuscleMessageField.cloneFlat: TmuscleFlattenable; +begin + raise Exception.Create('cloneFlat is not supported for TmuscleMessageField'); +end; + +function TmuscleMessageField.Count: integer; +begin + result := FPayload.Count; +end; + +procedure TmuscleMessageField.flatten(AOut: TStream); +var + i: integer; + b: byte; + i8: shortint; + i16: smallint; + i32: longint; + i64: int64; + f: single; + d: double; + m: IPortableMessage; + s: pchar; +begin + try + if FTypeCode = B_STRING_TYPE then //V1_4_4_x START + begin + //DOH!! B_STRING_TYPE has a hidden item count...big bug.. + i32 := FPayLoad.Count; + AOut.WriteBuffer(i32, sizeof(longint)); //write count + end; //V1_4_4_x FINISH + + for i := 0 to FPayLoad.Count - 1 do + case FtypeCode of + B_BOOL_TYPE: + begin + b := ord(TBoolBDataRec(FPayLoad[i]).Data); + AOut.WriteBuffer(b, sizeof(byte)); + end; + + B_INT8_TYPE: + begin + i8 := TInt8BDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(i8, sizeof(shortint)); + end; + + B_INT16_TYPE: + begin + i16 := TInt16BDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(i16, sizeof(smallint)); + end; + + B_INT32_TYPE: + begin + i32 := TInt32BDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(i32, sizeof(longint)); + end; + + B_INT64_TYPE: + begin + i64 := TLongBDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(i64, sizeof(int64)); + end; + + B_FLOAT_TYPE: + begin + f := TFloatBDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(f, sizeof(single)); + end; + + B_DOUBLE_TYPE: + begin + d := TDoubleBDataRec(FPayLoad[i]).Data; + AOut.WriteBuffer(d, sizeof(double)); + end; + + B_POINT_TYPE: + begin + TmusclePoint(TPointDataRec(FPayLoad[i]).data).flatten(AOut); + end; + + B_RECT_TYPE: + begin + TmuscleRect(TPointDataRec(FPayLoad[i]).data).flatten(AOut); + end; + + B_MESSAGE_TYPE: + begin + m := (TPortableMessageDataRec(FPayLoad[i]).Data); + + i32 := m.flattenedSize; + AOut.WriteBuffer(i32, sizeof(longint)); //write size + + m.flatten(AOut); //write actual message... + end; + + B_STRING_TYPE: + begin + i32 := length(TLongStringBDataRec(FPayLoad[i]).Data) + 1; + s := stralloc(i32); + try + StrPCopy(s, TLongStringBDataRec(FPayLoad[i]).Data); + s[i32 - 1] := #0; + AOut.WriteBuffer(i32, sizeof(longint)); //write size + AOut.WriteBuffer(s^, i32); //write size + finally + StrDispose(s); + end; + end; + + else + (* TODO + byte [][] array = (byte[][]) _payload; + out.writeInt(_numItems); + for (int i=0; i<_numItems; i++) + { + out.writeInt(array[i].length); + out.write(array[i]); + } + *) + raise Exception.Create('Unimplemented'); + + end; + + except + inherited; + end; +end; + +function TmuscleMessageField.flattenedItemSize: integer; +begin + case FTypeCode of + B_BOOL_TYPE, B_INT8_TYPE: + Result := 1; + + B_INT16_TYPE: + Result := 2; + + B_FLOAT_TYPE, B_INT32_TYPE: + Result := 4; + + B_INT64_TYPE, B_DOUBLE_TYPE, B_POINT_TYPE: + Result := 8; + + B_RECT_TYPE: + Result := 16; + else + Result := 0; + end; +end; + +function TmuscleMessageField.flattenedSize: integer; +var + i: integer; +begin + Result := 0; + + case FtypeCode of + B_BOOL_TYPE, B_INT8_TYPE, B_INT16_TYPE, B_FLOAT_TYPE, B_INT32_TYPE, + B_INT64_TYPE, B_DOUBLE_TYPE, B_POINT_TYPE, B_RECT_TYPE: + Result := FPayload.Count * flattenedItemSize; + + B_MESSAGE_TYPE: + begin + // there is no number-of-items field for B_MESSAGE_TYPE (for historical reasons, sigh) + //Message array[] = (Message[]) _payload; + for i := 0 to FPayload.Count - 1 do + begin + Result := Result + 4 + TPortableMessageDataRec(FPayload[i]).Data.flattenedSize; // 4 for the size int + end; + end; + + B_STRING_TYPE: + begin + Result := 4; // for the number-of-items field + //String array[] = (String[]) _payload; + //try { + // for (int i=0; i<_numItems; i++) ret += 4 + array[i].getBytes("UTF8").length + 1; // 4 for the size int, 1 for the nul byte + //} catch (UnsupportedEncodingException uee) { + // for (int i=0; i<_numItems; i++) ret += 4 + array[i].length() + 1; // 4 for the size int, 1 for the nul byte + //} + + //UTF8 - todo... + for i := 0 to FPayload.Count - 1 do + begin +{$WARNINGS OFF} + Result := Result + 4 + TLongStringBDataRec(FPayload[i]).Sizeof + 1; +{$WARNINGS ON} + end; + end; + + else + Result := 4; // for the number-of-items field + //byte [][] array = (byte[][]) _payload; + //for (int i=0; i<_numItems; i++) ret += 4 + array[i].length; // 4 for the size int + for i := 0 to FPayload.Count - 1 do + begin +{$WARNINGS OFF} + Result := Result + 4 + TBDataRec(FPayload[i]).SizeOf; //hmmm... will this work??? +{$WARNINGS ON} + end; + end; +end; + +function TmuscleMessageField.isFixedSize: boolean; +begin + Result := false; +end; + +procedure TmuscleMessageField.setEqualTo(setFromMe: TmuscleFlattenable); +begin + raise Exception.Create('Unsupported'); +end; + +procedure TmuscleMessageField.SetPayLoad(const Value: TList); +begin + FPayLoad := Value; +end; + +function TmuscleMessageField.toString: string; +var + i: integer; + +const + SEPERATOR = #9#9'(idx: %d, typecode: %s) : '; + + function getSeperator(idx: integer): string; + begin + Result := format(SEPERATOR, [idx, TmuscleMessage.whatString(FtypeCode)]); + end; +begin + { TODO : this would be bloody useful... } + + Result := ''; + try + for i := 0 to FPayLoad.Count - 1 do + case FtypeCode of + B_BOOL_TYPE: + begin + //b := ord(TBoolBDataRec(FPayLoad[i]).Data); + case TBoolBDataRec(FPayLoad[i]).Data of + true: + Result := Result + getSeperator(i) + 'True' + #13#10; + false: + Result := Result + getSeperator(i) + 'False' + #13#10; + end; + end; + + B_INT8_TYPE: + begin + //i8 := TInt8BDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(i8, sizeof(shortint)); + Result := Result + getSeperator(i) + format('%d', [TInt8BDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_INT16_TYPE: + begin + //i16 := TInt16BDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(i16, sizeof(smallint)); + Result := Result + getSeperator(i) + format('%d', [TInt16BDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_INT32_TYPE: + begin + //i32 := TInt32BDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(i32, sizeof(longint)); + Result := Result + getSeperator(i) + format('%d', [TInt32BDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_INT64_TYPE: + begin + //i64 := TLongBDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(i64, sizeof(int64)); + Result := Result + getSeperator(i) + format('%d', [TLongBDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_FLOAT_TYPE: + begin + //f := TFloatBDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(f, sizeof(single)); + Result := Result + getSeperator(i) + format('%f', [TFloatBDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_DOUBLE_TYPE: + begin + //d := TDoubleBDataRec(FPayLoad[i]).Data; + //AOut.WriteBuffer(d, sizeof(double)); + Result := Result + getSeperator(i) + format('%f', [TDoubleBDataRec(FPayLoad[i]).Data]) + #13#10; + end; + + B_POINT_TYPE: + begin + //TmusclePoint(TPointDataRec(FPayLoad[i]).data).flatten(AOut); + Result := Result + getSeperator(i) + TmusclePoint(TPointDataRec(FPayLoad[i]).data).toString + #13#10; + end; + + B_RECT_TYPE: + begin + //TmuscleRect(TPointDataRec(FPayLoad[i]).data).flatten(AOut); + Result := Result + getSeperator(i) + TmuscleRect(TRectDataRec(FPayLoad[i]).data).toString + #13#10 + end; + + B_MESSAGE_TYPE: + begin + + //m := TmuscleMessage(TPortableMessageDataRec(FPayLoad[i]).Data); + + //i32 := m.flattenedSize; + //AOut.WriteBuffer(i32, sizeof(longint)); //write size + + //m.flatten(AOut); //write actual message... + + Result := Result + getSeperator(i) + #13#10'>>>>>>>>>>>>>>>>'#13#10 + (TPortableMessageDataRec(FPayLoad[i]).data as ImuscleMessage).toString + #13#10'<<<<<<<<<<<<<<<<'#13#10 + end; + + B_STRING_TYPE: + begin + //i32 := length(TLongStringBDataRec(FPayLoad[i]).Data) + 1; + //s := stralloc(i32); + //try + // StrPCopy(s, TLongStringBDataRec(FPayLoad[i]).Data); + // s[i32 - 1] := #0; + // AOut.WriteBuffer(i32, sizeof(longint)); //write size + // AOut.WriteBuffer(s^, i32); //write size + //finally + // StrDispose(s); + //end; + + Result := Result + getSeperator(i) + TLongStringBDataRec(FPayLoad[i]).Data + #13#10; + end; + + else + (* TODO + byte [][] array = (byte[][]) _payload; + out.writeInt(_numItems); + for (int i=0; i<_numItems; i++) + { + out.writeInt(array[i].length); + out.write(array[i]); + } + *) + raise Exception.Create('Unimplemented'); + + end; + + except + raise; + end; + +end; + +function TmuscleMessageField.typeCode: cardinal; +begin + Result := FtypeCode; +end; + +procedure TmuscleMessageField.unflatten(ain: TStream; numBytes: integer); +var + i: integer; + b: byte; + i8: shortint; + i16: smallint; + i32: longint; + i64: int64; + f: single; + d: double; + pt: TmusclePoint; + r: TmuscleRect; + m: IPortableMessage; + s: pchar; + + tmp: integer; //V1_4_4_x + + msgSize: cardinal; + + AflattenedItemSize: integer; + AnumItems: integer; +begin + AflattenedItemSize := flattenedItemSize; + + if (AflattenedItemSize > 0) then + AnumItems := numBytes div AflattenedItemSize + else + AnumItems := 1; + + try + for i := 0 to AnumItems - 1 do + case FtypeCode of + B_BOOL_TYPE: + begin + ain.ReadBuffer(b, sizeof(byte)); + + FPayLoad.Add(TBoolBDataRec.Create(Boolean(b))); + end; + + B_INT8_TYPE: + begin + ain.ReadBuffer(i8, sizeof(shortint)); + + FPayLoad.Add(TInt8BDataRec.Create(i8)); + end; + + B_INT16_TYPE: + begin + ain.ReadBuffer(i16, sizeof(smallint)); + + FPayLoad.Add(TInt16BDataRec.Create(i16)); + end; + + B_INT32_TYPE: + begin + ain.ReadBuffer(i32, sizeof(longint)); + + FPayLoad.Add(TInt32BDataRec.Create(i32)); + end; + + B_INT64_TYPE: + begin + ain.ReadBuffer(i64, sizeof(int64)); + + FPayLoad.Add(TLongBDataRec.Create(i64)); + end; + + B_FLOAT_TYPE: + begin + ain.ReadBuffer(f, sizeof(single)); + + FPayLoad.Add(TFloatBDataRec.Create(f)); + end; + + B_DOUBLE_TYPE: + begin + ain.ReadBuffer(d, sizeof(double)); + + FPayLoad.Add(TDoubleBDataRec.Create(d)); + end; + + B_POINT_TYPE: + begin + pt := TmusclePoint.Create; + pt.unflatten(ain, pt.flattenedSize); + FPayLoad.Add(TPointDataRec.Create(pt)); + end; + + B_RECT_TYPE: + begin + r := TmuscleRect.Create; + r.unflatten(ain, r.flattenedSize); + FPayLoad.Add(TRectDataRec.Create(r)); + end; + + B_MESSAGE_TYPE: + begin + msgSize := numBytes; + + while (msgSize > 0) do + begin + m := TPortableMessage.Create; + + ain.ReadBuffer(i32, sizeof(longint)); //read size + m.unflatten(ain, i32); //read actual message... + FPayLoad.Add(TPortableMessageDataRec.Create(m)); + + dec(msgSize, i32 + 4); // 4 is the sizeof the i32 data item pertaining to the message size... + end; + end; + + B_STRING_TYPE: + begin + //god only knows how this worked... //V1_4_4_x START + + msgSize := numBytes; + + ain.Read(tmp, sizeof(longint));//number of elements + dec(msgSize, sizeof(longint)); //i32 + 4); + + + while (msgSize > 0) do + begin + ain.Read(i32, sizeof(longint));//size of block + s := StrAlloc(i32); + try //V1_4_3_9 + tmp := tmp + ain.Read(s^, i32); //write size + FPayLoad.Add(TLongStringBDataRec.Create(s)); + + finally //V1_4_3_9 + StrDispose(s); //V1_4_3_9 + end; //V1_4_3_9 + + dec(msgSize, i32 + 4); + end; + end; + + else + (* TODO + byte [][] array = (byte[][]) _payload; + out.writeInt(_numItems); + for (int i=0; i<_numItems; i++) + { + out.writeInt(array[i].length); + out.write(array[i]); + } + *) + raise Exception.Create('Unimplemented'); + + end; + + except + inherited; + end; +end; + +{ TLongBDataRec } + +constructor TLongBDataRec.Create(AData: Int64); +begin + inherited Create(B_INT64_TYPE); + Data := AData; +end; + +constructor TLongBDataRec.CreateStr(AData: string); +begin + +end; + +function TLongBDataRec.GetData: Variant; +begin + Result := IntToStr(Data); +end; + +procedure TLongBDataRec.SetData(AData: variant); +begin + Data := StrToInt(AData); +end; + +function TLongBDataRec.SizeOf: cardinal; +begin + result := System.sizeof(int64); +end; + +{ TDoubleBDataRec } + +constructor TDoubleBDataRec.Create(AData: Double); +begin + inherited Create(B_DOUBLE_TYPE); + + DATA := AData; +end; + +function TDoubleBDataRec.GetData: Variant; +begin + Result := Data; +end; + +procedure TDoubleBDataRec.SetData(AData: variant); +begin + Data := AData; +end; + +function TDoubleBDataRec.SizeOf: cardinal; +begin + Result := System.sizeof(double); +end; + +{ TRectDataRec } + +constructor TRectDataRec.Create(AData: TmuscleRect); +begin + inherited Create(B_RECT_TYPE); + + Data := TmuscleRect.Create(AData); +end; + +constructor TRectDataRec.CreateStr(AData: TStream); +begin + { TODO : ... } +end; + +destructor TRectDataRec.Destroy; +begin + Data.free; + + inherited; +end; + +function TRectDataRec.GetData: Variant; +begin + Result := 0; +end; + +procedure TRectDataRec.SetData(AData: variant); +begin + { TODO : ... } +end; + +function TRectDataRec.SizeOf: cardinal; +begin + Result := Data.flattenedSize; +end; + +{ TPointDataRec } + +constructor TPointDataRec.Create(AData: TmusclePoint); +begin + inherited Create(B_POINT_TYPE); + + Data := TmusclePoint.Create(AData.x, AData.y); +end; + +constructor TPointDataRec.CreateStr(AData: TStream); +begin + { TODO : ... } +end; + +destructor TPointDataRec.Destroy; +begin + Data.Free; + + inherited; +end; + +function TPointDataRec.GetData: Variant; +begin + result := 0; +end; + +procedure TPointDataRec.SetData(AData: variant); +begin + { TODO : ... } +end; + +function TPointDataRec.SizeOf: cardinal; +begin + Result := Data.flattenedSize; +end; + +{function TBinStreamBDataRec.SizeOf: cardinal; +begin + Result := Data.Size; +end;} + +{ TmuscleMessage } + +procedure TmuscleMessage.addBool(const Name: string; const data: boolean); +begin + inherited; +end; + +procedure TmuscleMessage.addDouble(const Name: string; const data: double); +begin + inherited; + +end; + +procedure TmuscleMessage.addFloat(const Name: string; const data: single); +begin + inherited; + +end; + +procedure TmuscleMessage.addInt16(const Name: string; const data: SmallInt); +begin + inherited; + +end; + +procedure TmuscleMessage.addInt32(const Name: string; const data: Integer); +begin + inherited; + +end; + +procedure TmuscleMessage.addInt64(const Name: string; const data: int64); +begin + inherited; + +end; + +procedure TmuscleMessage.addInt8(const Name: string; const data: Shortint); +begin + inherited; + +end; + +procedure TmuscleMessage.addMessage(const Name: string; data: IPortableMessage); +begin + inherited; + +end; + +procedure TmuscleMessage.addPoint(const Name: string; data: TmusclePoint); +begin + inherited; + +end; + +procedure TmuscleMessage.addRect(const Name: string; data: TmuscleRect); +begin + inherited; + +end; + +procedure TmuscleMessage.addString(const Name, data: string); +begin + inherited; + +end; + +function TmuscleMessage.findBool(const Name: string; var data: boolean): boolean; +begin + result := inherited findBool(Name, data); +end; + +function TmuscleMessage.findBool(const Name: string; index: integer; var data: boolean): boolean; +begin + result := inherited findBool(Name, index, data); +end; + +function TmuscleMessage.findDouble(const Name: string; index: integer; var data: double): boolean; +begin + result := inherited findDouble(name, index, data); +end; + +function TmuscleMessage.findDouble(const Name: string; var data: double): boolean; +begin + result := inherited findDouble(name, data); +end; + +function TmuscleMessage.findFloat(const Name: string; var data: single): boolean; +begin + result := inherited findFloat(name, data); +end; + +function TmuscleMessage.findFloat(const Name: string; index: integer; var data: single): boolean; +begin + result := inherited findFloat(name, index, data); +end; + +function TmuscleMessage.findInt16(const Name: string; var data: SmallInt): boolean; +begin + result := inherited findInt16(name, data); +end; + +function TmuscleMessage.findInt16(const Name: string; index: integer; var data: SmallInt): boolean; +begin + result := inherited findInt16(name, index, data); +end; + +function TmuscleMessage.findInt32(const Name: string; var data: Integer): boolean; +begin + result := inherited findInt32(name, data); +end; + +function TmuscleMessage.findInt32(const Name: string; index: integer; var data: Integer): boolean; +begin + result := inherited findInt32(name, index, data); +end; + +function TmuscleMessage.findInt64(const Name: string; index: integer; var data: Int64): boolean; +begin + result := inherited findInt64(name, index, data); +end; + +function TmuscleMessage.findInt64(const Name: string; var data: Int64): boolean; +begin + result := inherited findInt64(name, data); +end; + +function TmuscleMessage.findInt8(const Name: string; var data: ShortInt): boolean; +begin + result := inherited findInt8(name, data); +end; + +function TmuscleMessage.findInt8(const Name: string; index: integer; var data: ShortInt): boolean; +begin + result := inherited findInt8(name, index, data); +end; + +function TmuscleMessage.findMessage(const Name: string; index: integer; var data: IPortableMessage): boolean; +begin + result := inherited findMessage(name, index, data); +end; + +function TmuscleMessage.findMessage(const Name: string; var data: IPortableMessage): boolean; +begin + result := inherited findMessage(name, data); +end; + +function TmuscleMessage.findPoint(const Name: string; var data: TmusclePoint): boolean; +begin + result := inherited findPoint(name, data); +end; + +function TmuscleMessage.findPoint(const Name: string; index: integer; var data: TmusclePoint): boolean; +begin + result := inherited findPoint(name, index, data); +end; + +function TmuscleMessage.findRect(const Name: string; var data: TmuscleRect): boolean; +begin + result := inherited findRect(name, data); +end; + +function TmuscleMessage.findRect(const Name: string; index: integer; var data: TmuscleRect): boolean; +begin + result := inherited findRect(name, index, data); +end; + +function TmuscleMessage.findString(const Name: string; index: integer; var data: string): boolean; +begin + result := inherited findString(name, index, data); +end; + +function TmuscleMessage.findString(const Name: string; var data: string): boolean; +begin + result := inherited findString(name, data); +end; + +function TmuscleMessage.replaceBool(const Name: string; const data: boolean): boolean; +begin + result := inherited replaceBool(name, data); +end; + +function TmuscleMessage.replaceDouble(const Name: string; const data: double): boolean; +begin + result := inherited replaceDouble(name, data); +end; + +function TmuscleMessage.replaceFloat(const Name: string; const data: single): boolean; +begin + result := inherited replaceFloat(name, data); +end; + +function TmuscleMessage.replaceInt16(const Name: string; const data: SmallInt): boolean; +begin + result := inherited replaceInt16(name, data); +end; + +function TmuscleMessage.replaceInt32(const Name: string; const data: Integer): boolean; +begin + result := inherited replaceInt32(name, data); +end; + +function TmuscleMessage.replaceInt64(const Name: string; const data: Int64): boolean; +begin + result := inherited replaceInt64(name, data); +end; + +function TmuscleMessage.replaceInt8(const Name: string; const data: ShortInt): boolean; +begin + result := inherited replaceInt8(name, data); +end; + +function TmuscleMessage.replaceMessage(const Name: string; data: IPortableMessage): boolean; +begin + result := inherited replaceMessage(name, data); +end; + +function TmuscleMessage.replaceMessage(const Name: string; index: integer; data: IPortableMessage): boolean; +begin + result := inherited replaceMessage(name, index, data); +end; + +function TmuscleMessage.replacePoint(const Name: string; data: TmusclePoint): boolean; +begin + result := inherited replacePoint(name, data); +end; + +function TmuscleMessage.replaceRect(const Name: string; data: TmuscleRect): boolean; +begin + result := inherited replaceRect(name, data); +end; + +function TmuscleMessage.replaceString(const Name, data: string): boolean; +begin + result := inherited replaceString(name, data); +end; + +{ TPortableMessage } + +function TPortableMessage.__Flatten: string; +begin + +end; + +procedure TPortableMessage.__Unflatten(data: string); +begin + +end; + +procedure TPortableMessage.AddBoolean(const Name: string; const data: boolean); +begin + AddBool(name, data); +end; + +procedure TPortableMessage.AddCardinal(const Name: string; const data: cardinal); +begin + AddInteger(Name, integer(data)); +end; + +procedure TPortableMessage.AddDataStream(const Name: string; data: TStream); +var + idx: integer; + fld: TmuscleMessageField; + b: TBDataRec; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + b := TBLOBDataRec.Create(Data); + fld.PayLoad.Add(b); + end + else + begin + b := TBLOBDataRec.Create(Data); + fld := TmuscleMessageField.Create(b.muscledatatype); + fld.PayLoad.Add(b); + _fieldTable.AddObject(name, fld); + end; +end; + +procedure TPortableMessage.AddFloat(const Name: string; const data: single); +begin + inherited; + +end; + +procedure TPortableMessage.AddInteger(const Name: string; const data: integer); +begin + //lets not worry about packing the integer for now... + addint32(name, data); +end; + +function TPortableMessage.FindBoolean(const Name: string): boolean; +begin + findBool(name, Result); +end; + +function TPortableMessage.FindCardinal(const Name: string): Cardinal; +var + i: integer; +begin + //we always store this as an int32 and apply signing via type conversion + findInt32(name, i); + + Result := cardinal(i); +end; + +function TPortableMessage.FindDataStream(const Name: string; data: TStream): boolean; +var + idx: integer; + fld: TmuscleMessageField; +begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TBLOBDataRec(fld.Payload[0]).Data.position := 0; + Data.CopyFrom(TBLOBDataRec(fld.Payload[0]).Data, TBLOBDataRec(fld.Payload[0]).Data.Size); + Result := True; + exit; + end; + end; + + Result := False; +end; + +function TPortableMessage.FindFloat(const Name: string): single; +begin + findfloat(name, result); +end; + +function TPortableMessage.FindInteger(const Name: string): Integer; +begin + //no packing is implemented for speed, otherwise we'd need to search int8, 16 and 32 + findInt32(name, result); +end; + +function TPortableMessage.FindMessage(const Name: string): IPortableMessage; +begin + findMessage(name, result); +end; + +function TPortableMessage.FindString(const Name: string): string; +begin + findstring(name, result); +end; + +procedure TPortableMessage.FindVariant(const Name: string; var data: variant); +begin + //this is no longer supported. + raise exception.create('TPortableMessage.FindVariant is unsupported'); +end; + +function TPortableMessage.GetWhat: Cardinal; +begin + result := fWhat; +end; + +function TPortableMessage.ReplaceBoolean(const Name: string; const data: boolean): boolean; +begin + result := replacebool(name, data); +end; + +function TPortableMessage.ReplaceCardinal(const Name: string; const data: cardinal): boolean; +begin + result := replaceInteger(name, data); +end; + +function TPortableMessage.ReplaceFloat(const Name: string; const data: single): boolean; +begin + result := inherited ReplaceFloat(name, data); +end; + +function TPortableMessage.ReplaceInteger(const Name: string; const data: integer): boolean; +begin + result := replaceInt32(name, data); +end; + +function TPortableMessage.ReplaceMessage(const Name: string; data: IPortableMessage): boolean; +begin + //todo + + result := inherited ReplaceMessage(name, data); +end; + +function TPortableMessage.ReplaceString(const Name, data: string): boolean; +begin + result := inherited ReplaceString(name, data); +end; + +procedure TPortableMessage.SetWhat(const Value: Cardinal); +begin + Fwhat := Value; +end; + +procedure TPortableMessage.AddMessage(const Name: string; data: IPortableMessage); +begin + inherited; +end; + +procedure TPortableMessage.AddString(const Name, data: string); +begin + inherited; + +end; + +//------------------------------------------------------------------------------------------------------------------------------------------ + +function TPortableMessage.FindDataStreamData(const Name: string; toStream: TStream; dataCount: integer): integer; +var + idx: integer; + fld: TmuscleMessageField; +begin + Result := 0; + + if Assigned(toStream) then + begin + idx := _fieldTable.IndexOf(name); + if (idx > -1) then + begin + fld := TmuscleMessageField(_fieldTable.Objects[idx]); + if (fld.Payload.Count > 0) then + begin + TBLOBDataRec(fld.Payload[0]).Data.position := 0; + + if (TBLOBDataRec(fld.Payload[0]).Data.Size < dataCount) then + begin + toStream.CopyFrom(TBLOBDataRec(fld.Payload[0]).Data, TBLOBDataRec(fld.Payload[0]).Data.Size); + Result := TBLOBDataRec(fld.Payload[0]).Data.Size; + end + else + begin + toStream.CopyFrom(TBLOBDataRec(fld.Payload[0]).Data, dataCount); + Result := dataCount; + end; + + exit; + end; + end; + end; +end; + +procedure TPortableMessage.AddDouble(const Name: string; const data: double); //new fn V1_4_4_x +begin + inherited; + +end; + +function TPortableMessage.ReplaceDouble(const Name: string; const data: double): boolean; //new fn V1_4_4_x +begin + result := inherited ReplaceDouble(name, data); +end; + +function TPortableMessage.FindDouble(const Name: string): double; //new fn V1_4_4_x +begin + finddouble(name, result); +end; + +procedure TPortableMessage.copyFrom(const source: IPortableMessage); +var + ms: TMemoryStream; +begin + ms := TMemoryStream.Create; + try + source.flatten(ms); + ms.Seek(0, soFromBeginning); + self.unflatten(ms, ms.size); + finally + ms.free; + end; +end; + +{ TBLOBDataRec } + +procedure TBLOBDataRec.DataAsStream(s: TStream); +begin + data.position := 0; + s.copyfrom(data, data.size); +end; + +constructor TBLOBDataRec.Create(AData: TStream); +begin + inherited Create(B_RAW_TYPE); + + data := TMemoryStream.Create; + + AData.position := 0; + data.CopyFrom(AData, AData.Size); +end; + +constructor TBLOBDataRec.CreateFromHex(AData: string); +var + s: TStringStream; + ms: TMemoryStream; +begin + s := TStringStream.Create(AData); + ms := TMemoryStream.Create; + try + s.position := 0; + + StreamHexToBin(s, ms); + ms.position := 0; + + Create(ms); + finally + ms.free; + s.free; + end; +end; + +function TBLOBDataRec.DataAsString: string; +var + s: TStringStream; +begin + + s := TStringStream.Create(''); + try + data.position := 0; + StreamBinToHex(data, s); + + Result := s.Datastring; + finally + s.free; + end; +end; + +destructor TBLOBDataRec.Destroy; +begin + data.free; + + inherited; +end; + +function TBLOBDataRec.GetData: Variant; +begin + // + result := null; +end; + +procedure TBLOBDataRec.SetData(AData: variant); +begin + /// not implemented... +end; + +function TBLOBDataRec.SizeOf: cardinal; +begin + Result := Data.Size; +end; + +end. + diff --git a/delphi/MessageQueue.pas b/delphi/MessageQueue.pas new file mode 100644 index 00000000..a9ede5a9 --- /dev/null +++ b/delphi/MessageQueue.pas @@ -0,0 +1,146 @@ +{----------------------------------------------------------------------------- + Unit Name: MessageQueue + Author: matt + Date: 17-Sep-2004 + Purpose: + History: +-----------------------------------------------------------------------------} + +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit MessageQueue; + +interface + + +uses + Sysutils, Classes, Windows, Messages, Message, Locker, Contnrs; + +type + + //The queue is FIFO + TMessageQueue = class + private + private_list: TInterfaceList; + locker: TInterfaceListLocker; //because Borland (and the VCL) are a not MT Friendly, unless I use + //this, many of the TInterfaceList methods actually use a Critical + //Section that blocks till it's released! + + public + constructor Create; + destructor Destroy; override; + + function push(const data: IPortableMessage): boolean; + function pop(out queueCount: cardinal): IPortableMessage; + end; + + +implementation + +//------------------------------------------------------------------------------ + +{ TMessageQueue } + +constructor TMessageQueue.Create; +begin + private_list := TInterfaceList.Create; + locker := TInterfaceListLocker.Create(nil); + locker.InterfaceList := private_list; +end; + +//------------------------------------------------------------------------------ + +destructor TMessageQueue.Destroy; +begin + locker.Free; + private_list.Free; + + inherited; +end; + +//------------------------------------------------------------------------------ + +function TMessageQueue.pop(out queueCount: cardinal): IPortableMessage; +var + list: TInterfaceList; +begin + // for this we will attempt to lock for 2 seconds + list := locker.LockEx(2000); + if assigned(list) then + try + if (list.count > 0) then + begin + result := (list.first as IPortableMessage); + list.delete(0); //aarg!! this locks the list!! + end + else + result := nil; + + queueCount := list.count; + finally + locker.Unlock; + end; +end; + +//------------------------------------------------------------------------------ + +function TMessageQueue.push(const data: IPortableMessage): boolean; +var + list: TInterfaceList; +begin + result := false; + try + // for this we will attempt to lock for 2 seconds + list := locker.LockEx(2000); + + result := assigned(list); + if result then + try + list.add(data); + finally + locker.Unlock; + end; + except + on e: exception do + MessageBox(0, pchar(e.message), '', 0); + end; +end; + +end. diff --git a/delphi/MessageTransceiver.pas b/delphi/MessageTransceiver.pas new file mode 100644 index 00000000..d23a1356 --- /dev/null +++ b/delphi/MessageTransceiver.pas @@ -0,0 +1,450 @@ +{----------------------------------------------------------------------------- + Unit Name: MessageTransceiver + Author: matte + Date: 05-Jul-2005 + Purpose: Simple non threaded message tranceiver class + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +unit MessageTransceiver; + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, + ScktComp, IOGateway, Message, MessageQueue; + +const + MTT_EVENT_CONNECTED = 0; + MTT_EVENT_DISCONNECTED = 1; + +type + //used internally to hold data. + TMessageBuffer = record + size: cardinal; + pos: cardinal; + readSize: boolean; + readEnc: boolean; + buffer: TMemoryStream; + end; + + //the events.. currently the Message received is a little surplus to requirements because + //messages are not queued when using it. In a future implementation this will be handled + //bo another thread and will give a better overall effect. + TMessageReceivedEvent = procedure (const msgRef: IPortableMessage) of object; + THandleMessageEvent = procedure (msgRef: IPortableMessage; var handled: boolean) of object; + + //simple implementation in the shape of a component. Install and place on form at + //design time or use it in code - choice is yours. Currently rampantly single threaded + //and so not very efficient. I expect to have a "MessageTranceiverThread" that contains + //this class at some point. That will then put the comms in a seperate thread than the + //VCL, and will hopefully make life snappier. + //NB. we're using Borland's Delphi 5+ socket implementation.. sorry. It's the lowest + // common denominator. At some point I will implement some "socketsource" style + // alternative. You'll then be able to use other brands of socket (Synapse, indy, + // FPiette's etc) + //For now... Enjoy.. + TMessageTransceiver = class(TComponent) + private + _socket: TClientSocket; + + _InBuffer: TMessageBuffer; + _OutBuffer: TMessageBuffer; + _tmpbuffer: TMemoryStream; + + _InQueue: TMessageQueue; + _OutQueue: TMessageQueue; + + _gateway: TMessageIOGateway; + + FHandleMessage: THandleMessageEvent; + FMessageReceived: TMessageReceivedEvent; + function getConnected: boolean; + function getHost: string; + function getPort: integer; + procedure setHost(const Value: string); + procedure setPort(const Value: integer); + + protected + procedure dowork; //does the writing + procedure SocketRead(Sender: TObject; Socket: TCustomWinSocket); + procedure SocketWrite(Sender: TObject; Socket: TCustomWinSocket); //does this even work?? + procedure SocketConnect(Sender: TObject; Socket: TCustomWinSocket); + procedure SocketDisconnect(Sender: TObject; Socket: TCustomWinSocket); + + public + constructor Create(acomponent: TComponent); override; + destructor Destroy; override; //NB. we currently DO NOT flush the queues, so any messages in transit or + // on the queue need to be processed *before* destruction or they will + // be forever lost. + + //call this to post essages to the server for despatch to clients + function SendMessageToSessions(const msgRef: IPortableMessage): boolean; + + //call this to _Manually_ get messages from the queue. To do this you must make sure + //messages end up on the queue. Easiest way to do this is to ignore OnMessageReceived and + //either ignore OnHandleMessage as well or implement it returning "handled = false" unless you + //really have to do something else. + function GetNextEvent(out evtCode: cardinal; {optional?} out msgCount: cardinal): IPortableMessage; //returns events left on queue + + //socket stuff + procedure connect(const ahost: string; const aport: integer); overload; + procedure connect; overload; //use this one if you've set the host and port at design time + //NB. by default these will be '127.0.0.1' and '2960' + + procedure disconnect; + + property connected: boolean read getConnected; //reads the socket's connection status + + property socket: TClientSocket read _socket; //raw access to the socket. Runtime only. + + published + property Host: string read getHost write setHost; + property Port: integer read getPort write setPort; + + //use this event to "peek" at the message.. + property OnHandleMessage: THandleMessageEvent read FHandleMessage write FHandleMessage; + + //use this event to grab the message immediately (after potential "peek") + property OnMessageReceived: TMessageReceivedEvent read FMessageReceived write FMessageReceived; + + end; + +procedure Register; + +implementation + +procedure Register; +begin + RegisterComponents('MUSCLE', [TMessageTransceiver]); +end; + +{ TMessageTransceiver } + +{-----------------------------------------------------------------------------} +constructor TMessageTransceiver.Create; +begin + inherited; + _socket := TClientSocket.Create(nil); + _socket.OnRead := SocketRead; + _socket.OnWrite := SocketWrite; //I question whether this works?! + _socket.OnConnect := SocketConnect; + _socket.OnDisconnect := SocketDisconnect; + _socket.ClientType := ctNonBlocking; + + //defaults... streaming *may* override these. + _socket.port := 2960; + _socket.host := '127.0.0.1'; + + _InBuffer.buffer := TMemoryStream.Create; + _OutBuffer.buffer := TMemoryStream.Create; + + _InQueue := TMessageQueue.Create; + _OutQueue := TMessageQueue.Create; + + _gateway := TMessageIOGateway.Create; + + _tmpbuffer := TMemoryStream.Create; +end; + +{-----------------------------------------------------------------------------} +destructor TMessageTransceiver.Destroy; +begin + //note to self - probably need to fiddle with destruction order... + + _socket.free; + + _InBuffer.buffer.free; + _OutBuffer.buffer.free; + + _InQueue.free; + _OutQueue.free; + + _gateway.Free; + + _tmpbuffer.Free; + + inherited; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.connect(const ahost: string; const aport: integer); +begin + _socket.host := ahost; + _socket.port := aport; + connect; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.connect; +begin + if connected then + raise Exception.Create('Socket is already connected!'); + + _socket.active := true; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.disconnect; +begin + _socket.Active := false; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.dowork; +var + msg: IPortableMessage; + c: cardinal; + buffer: array[0..8191] of byte; //8KB buffer + bytesread: cardinal; +begin + repeat + _OutBuffer.buffer.position := 0; + _OutBuffer.buffer.size := 0; + + msg := _OutQueue.pop(c); + + if msg <> nil then + begin + _gateway.flattenMessage(_OutBuffer.buffer, msg); + + _OutBuffer.buffer.Position := 0; //rewind + + while (_OutBuffer.buffer.Position < _OutBuffer.buffer.Size) do + begin + bytesread := _OutBuffer.buffer.read(buffer, sizeof(buffer)); + _socket.Socket.SendBuf(buffer, bytesread); + end; + end; + until c <= 0; +end; + +{-----------------------------------------------------------------------------} +function TMessageTransceiver.getConnected: boolean; +begin + result := _socket.Active; +end; + +{-----------------------------------------------------------------------------} +function TMessageTransceiver.getHost: string; +begin + result := _socket.Host; +end; + +{-----------------------------------------------------------------------------} +function TMessageTransceiver.getPort: integer; +begin + result := _socket.port; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.setHost(const Value: string); +begin + _socket.Host := Value; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.setPort(const Value: integer); +begin + _socket.port := Value; +end; + +{-----------------------------------------------------------------------------} +function TMessageTransceiver.GetNextEvent(out evtCode, msgCount: cardinal): IPortableMessage; +begin + result := _InQueue.pop(msgCount); + if assigned(result) then + evtCode := Result.what; +end; + +{-----------------------------------------------------------------------------} +function TMessageTransceiver.SendMessageToSessions(const msgRef: IPortableMessage): boolean; +begin + result := _OutQueue.push(msgref); + dowork; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.SocketRead(Sender: TObject; Socket: TCustomWinSocket); +var + t: cardinal; + buffer: array[0..8191] of byte; //8KB buffer + bytesread: cardinal; + msg: IPortableMessage; + handled: boolean; +begin + if (_InBuffer.size = 0) then //result vars + begin + _InBuffer.readSize := false; + _InBuffer.readEnc := false; + _InBuffer.buffer.Position := 0; + _InBuffer.buffer.Size := 0; + end; + + //half arsed state machine: + if not(_InBuffer.readSize) then + begin + bytesread := Socket.ReceiveBuf(_InBuffer.size, sizeof(cardinal)); + assert(bytesread = sizeof(cardinal)); + _InBuffer.buffer.write(_InBuffer.size, bytesread); + _InBuffer.readSize := true; + end + else if not(_InBuffer.readEnc) then + begin + bytesread := Socket.ReceiveBuf(t, sizeof(cardinal)); + assert(bytesread = sizeof(cardinal)); + assert(t = MUSCLE_MESSAGE_ENCODING_DEFAULT); + _InBuffer.buffer.write(t, bytesread); + _InBuffer.readEnc := true; + end + else begin + while (socket.ReceiveLength > 0) do + begin + if ((_InBuffer.size - _InBuffer.pos) < sizeof(buffer)) then + t := _InBuffer.size - _InBuffer.pos + else + t := sizeof(buffer); + + bytesread := Socket.ReceiveBuf(buffer, sizeof(buffer)); + inc(_InBuffer.pos, bytesread); + _InBuffer.buffer.write(buffer, bytesread); + end; + + if (_InBuffer.pos >= _InBuffer.size) then + begin + _InBuffer.buffer.Position := 0; + msg := _gateway.unflattenMessage(_InBuffer.buffer); + + handled := false; + + if assigned(FHandleMessage) then + FHandleMessage(msg, handled); + + if handled then + begin + msg := nil; //free message + end + else begin + if assigned(FMessageReceived) then + begin + FMessageReceived(msg); //push message right away - dont cache + msg := nil; //this process owns the message and so we free it + end + else + _InQueue.push(msg); //put message on queue - we no longer "own" the message. + end; + + _InBuffer.size := 0; + end; + end; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.SocketWrite(Sender: TObject; Socket: TCustomWinSocket); +begin + dowork; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.SocketConnect(Sender: TObject; Socket: TCustomWinSocket); +var + connectMsg: IPortableMessage; + handled: boolean; +begin + connectMsg := TPortableMessage.Create(MTT_EVENT_CONNECTED); + + //okay, so now we fake the message journey.. at some point I should just + //make an "internal post message" type method, but it's 1am and I need sleep.. + + handled := false; + + if assigned(FHandleMessage) then + FHandleMessage(connectMsg, handled); + + if handled then + begin + connectMsg := nil; //free message + end + else begin + if assigned(FMessageReceived) then + begin + FMessageReceived(connectMsg); //push message right away - dont cache + connectMsg := nil; //this process owns the message and so we free it + end + else + _InQueue.push(connectMsg); //put message on queue - we no longer "own" the message. + end; +end; + +{-----------------------------------------------------------------------------} +procedure TMessageTransceiver.SocketDisconnect(Sender: TObject; Socket: TCustomWinSocket); +var + disconnectMsg: IPortableMessage; + handled: boolean; +begin + disconnectMsg := TPortableMessage.Create(MTT_EVENT_DISCONNECTED); + + //okay, so now we fake the message journey.. at some point I should just + //make an "internal post message" type method, but it's 1am and I need sleep.. + + handled := false; + + if assigned(FHandleMessage) then + FHandleMessage(disconnectMsg, handled); + + if handled then + begin + disconnectMsg := nil; //free message + end + else begin + if assigned(FMessageReceived) then + begin + FMessageReceived(disconnectMsg); //push message right away - dont cache + disconnectMsg := nil; //this process owns the message and so we free it + end + else + _InQueue.push(disconnectMsg); //put message on queue - we no longer "own" the message. + end; +end; + +end. diff --git a/delphi/MuscleExceptions.pas b/delphi/MuscleExceptions.pas new file mode 100644 index 00000000..358b2b00 --- /dev/null +++ b/delphi/MuscleExceptions.pas @@ -0,0 +1,75 @@ +{----------------------------------------------------------------------------- + Unit Name: MuscleExceptions + Author: Matthew Emson + Date: 30-Oct-2003 + Purpose: + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +unit MuscleExceptions; + +interface + +uses + Classes, SysUtils; + +type + EUnflattenFormatException = class(EInOutError) + public + constructor CreateDefault; virtual; + end; + + +implementation + +{ EUnflattenFormatException } + +constructor EUnflattenFormatException.CreateDefault; +begin + inherited Create('unexpected bytes during Flattenable unflatten'); +end; + +end. diff --git a/delphi/Point.pas b/delphi/Point.pas new file mode 100644 index 00000000..d40010a4 --- /dev/null +++ b/delphi/Point.pas @@ -0,0 +1,251 @@ +{----------------------------------------------------------------------------- + Procedure: Point + Author: Matthew Emson + Date: 30-Oct-2003 + Purpose: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit Point; + +interface + +uses + Classes, SysUtils, + + TypeConstants, + Flattenable; + + +type + TmusclePoint = class(TmuscleFlattenable) + private + Fy: single; + Fx: single; + + procedure Setx(const Value: single); + procedure Sety(const Value: single); + + public + constructor Create; overload; virtual; + constructor Create( ax, ay: single ); overload; virtual; + + procedure setBounds( dx, dy: single ); + + procedure constrainTo( topLeft, bottomRight: TmusclePoint ); + function add(p: TmusclePoint): TmusclePoint; + function subtract(p: TmusclePoint): TmusclePoint; + + procedure addToThis(p: TmusclePoint); + procedure subtractFromThis(p: TmusclePoint); + + //Comparison Operator. Returns true iff (r)'s dimensions are exactly the same as this rectangle's. + function equals(o: TObject): boolean; + + + //from TmuscleFlattenable + function isFixedSize: boolean; override; + function typeCode: cardinal; override; + function flattenedSize: integer; override; + function cloneFlat: TmuscleFlattenable; override; + procedure setEqualTo( setFromMe: TmuscleFlattenable ); override; + + procedure flatten( AOut: TStream ); override; + function allowsTypeCode(code: integer): boolean; override; + procedure unflatten(ain: TStream; numBytes: integer); override;// throws IOException, UnflattenFormatException; + + //mainly for testing... + function toString: string; override; + + property x: single read Fx write Setx; + property y: single read Fy write Sety; + end; + + +implementation + +uses + Rect; + +{ TmusclePoint } + +function TmusclePoint.add(p: TmusclePoint): TmusclePoint; +begin + Result := TmusclePoint.Create( Fx + p.x, Fy + p.y); +end; + +procedure TmusclePoint.addToThis(p: TmusclePoint); +begin + Fx := Fx + p.x; + Fy := Fy + p.y; +end; + +function TmusclePoint.allowsTypeCode(code: integer): boolean; +begin + Result := (code = TypeConstants.B_POINT_TYPE); +end; + +function TmusclePoint.cloneFlat: TmuscleFlattenable; +begin + Result := TmusclePoint.Create( Fx, Fy ); +end; + +procedure TmusclePoint.constrainTo(topLeft, bottomRight: TmusclePoint); +begin + if (Fx < topLeft.x) then + Fx := topLeft.x; + + if (Fy < topLeft.y) then + Fy := topLeft.y; + + if (Fx > bottomRight.x) then + Fx := bottomRight.x; + + if (Fy > bottomRight.y) then + Fy := bottomRight.y; +end; + +constructor TmusclePoint.Create(ax, ay: single); +begin + +end; + +constructor TmusclePoint.Create; +begin + Fx := 0.0; + Fy := 0.0; +end; + +function TmusclePoint.equals(o: TObject): boolean; +var + p: TmusclePoint; +begin + if (o is TmuscleRect) then begin + p := TmusclePoint(o); + Result := (Fx = p.x) and (Fy = p.y); + end + else + Result := false; + +end; + +procedure TmusclePoint.flatten(AOut: TStream); +begin + try + AOut.Write(Fx, SizeOf(Single)); + AOut.Write(Fy, SizeOf(Single)); + except + inherited; + end; +end; + +function TmusclePoint.flattenedSize: integer; +begin + Result := ( SizeOf(Single) * 2 ); +end; + +function TmusclePoint.isFixedSize: boolean; +begin + Result := True; +end; + +procedure TmusclePoint.setBounds(dx, dy: single); +begin + Fx := dx; + Fy := dy; +end; + +procedure TmusclePoint.setEqualTo(setFromMe: TmuscleFlattenable); +var + copyMe: TmusclePoint; +begin + inherited; + + copyMe := (setFromMe as TmusclePoint); + + setBounds(copyMe.x, copyMe.y); +end; + +procedure TmusclePoint.Setx(const Value: single); +begin + Fx := Value; +end; + +procedure TmusclePoint.Sety(const Value: single); +begin + Fy := Value; +end; + +function TmusclePoint.subtract(p: TmusclePoint): TmusclePoint; +begin + Result := TmusclePoint.Create( Fx - p.x, Fy - p.y); +end; + +procedure TmusclePoint.subtractFromThis(p: TmusclePoint); +begin + Fx := Fx - p.x; + Fy := Fy - p.y; +end; + +function TmusclePoint.toString: string; +begin + Result := format('Point: %f %f', [Fx, Fy]); +end; + +function TmusclePoint.typeCode: cardinal; +begin + Result := TypeConstants.B_POINT_TYPE; +end; + +procedure TmusclePoint.unflatten(ain: TStream; numBytes: integer); +begin + try + ain.Read(Fx, SizeOf(Single)); + ain.Read(Fy, SizeOf(Single)); + except + inherited; + end; +end; + +end. diff --git a/delphi/Readme.txt b/delphi/Readme.txt new file mode 100644 index 00000000..5c394ca2 --- /dev/null +++ b/delphi/Readme.txt @@ -0,0 +1,19 @@ +MUSCLE Client Component for Delphi version 1.0.0 + +This is the first public release. Documentation is a bit thin on the ground, sorry! + +LICENSING: Please see "license-delphi.txt" or the header of any source file to see the licensing terms for this code. + +INSTALLING: open the included package, compile and then hit "install" on the package manager. Everything *should* work without a hitch under Delphi 5. Remember to include this directory in the Project or Global Library Path! + +HISTORY: This is a basic implementation of the MUSCLE client side API. It works well enough to be a starting point for projects wishing to utilise MUSCLE in their Delphi applications. + +I wrote this code based, more or less, on the Java (and bits of the C#) API, and so it's not really like the C++ API. Delphi, being fairly single threaded, doesn't do anything clever with thread pooling and such. We just use a TClientSocket and put that into non-blocking mode and hope for the best. + +The component and classes, except for the TPortableMessage, have NOT been thoroughly tested. The TPortableMessage code is very old, and has been extensively tested in real world situations - but still may have bugs, errors and incompatibilities with the MUSCLE::Message. The basic Types all work though (string, intXX and bool) and any others will get an overhaul in the next release. I cannot verify that any types past these will work for this release! + +The IPortableMessage interface should be used as much as possible. I realise the use of interfaces may not be to everyones taste, but they make life a lot easier, and so they are here to stay. + +There are some oddites in the Message.pas unit still. There is a class callet TmuscleMessage, and an interface of a similar name. This was the original port, before I sugar coated it for use in another application (that already used a similar interface, so I adapted the TmuscleMessage to TPortableMessage to use the same legacy interface.) The TPortableMessage was called the TBMessage originally, and if you see any code refering to a TBMessage or equiv interface, the class in question is the TPortableMessage or equiv interface. + +THIS CODE HAS ONLY BEEN TESTED UNDER DELPHI 5 ENTERPRISE AND DELPHI 5 PROFESSIONAL. IT MAY WORK WITH LATER VERSIONS WITH OUT CHANGES, BUT I ASSUME SOME TWEAKS MAY BE REQUIRED. I plan to update the code to Delphi 7 at a later date. Delphi 8/2005 for dotNET will be unsupported. Use the CSharp classes instead! \ No newline at end of file diff --git a/delphi/Rect.pas b/delphi/Rect.pas new file mode 100644 index 00000000..def831ca --- /dev/null +++ b/delphi/Rect.pas @@ -0,0 +1,431 @@ +{----------------------------------------------------------------------------- + Unit Name: Rect + Author: mathew emson + Date: 30-Oct-2003 + Purpose: + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit Rect; + +interface + +uses + Classes, SysUtils, + + TypeConstants, + Flattenable, + Point; + +type + TmuscleRect = class(TmuscleFlattenable) + private + Fbottom: single; + Fleft: single; + Fright: single; + Ftop: single; + function getleftBottom: TmusclePoint; + function getleftTop: TmusclePoint; + function getrightBottom: TmusclePoint; + function getrightTop: TmusclePoint; + procedure setleftBottom(const Value: TmusclePoint); + procedure setleftTop(const Value: TmusclePoint); + procedure setrightBottom(const Value: TmusclePoint); + procedure setrightTop(const Value: TmusclePoint); + + protected + procedure Setbottom(const Value: single); + procedure Setleft(const Value: single); + procedure Setright(const Value: single); + procedure Settop(const Value: single); + function getHeight: single; + function getWidth: single; + + public + constructor Create; overload; virtual; + constructor Create(l, t, r, b: single); overload; virtual; + constructor Create(rhs: TmuscleRect); overload; virtual; + constructor Create(topLeft, bottomRight: TmusclePoint); overload; virtual; + + property top: single read Ftop write Settop; + property left: single read Fleft write Setleft; + property right: single read Fright write Setright; + property bottom: single read Fbottom write Setbottom; + + //** Set a new position for the rectangle. */ was called 'set' + procedure setBounds(l, t, r, b: single); + + function toString: string; override; + + //from TmuscleFlattenable + procedure setEqualTo(setFromMe: TmuscleFlattenable); override; + function cloneFlat: TmuscleFlattenable; override; + + //Makes the rectangle smaller by the amount specified in both the x and y dimensions + procedure insetBy(point: TmusclePoint); overload; + procedure insetBy(dx, dy: single); overload; + + //Translates the rectangle by the amount specified in both the x and y dimensions + procedure offsetBy(point: TmusclePoint); overload; + procedure offsetBy(dx, dy: single); overload; + + //Comparison Operator. Returns true iff (r)'s dimensions are exactly the same as this rectangle's. + function equals(o: TObject): boolean; + + //Returns a rectangle whose area is the intersecting subset of this rectangle's and (r)'s + function intersect(r: TmuscleRect): TmuscleRect; + + //Returns true iff this rectangle and (r) overlap in space. + function intersects(r: TmuscleRect): boolean; + + //Returns true iff this rectangle's area is non imaginary (i.e. width() and height()) are both non-negative) + function isValid: boolean; + + //Returns a rectangle whose area is a superset of the union of this rectangle's and (r)'s + function unify(r: TmuscleRect): TmuscleRect; + + //Returns true iff this rectangle contains the specified point. + + function contains( point: TmusclePoint ): boolean; overload; + + function contains( r: TmuscleRect ): boolean; overload; + + + //from TmuscleFlattenable + function isFixedSize: boolean; override; + + function typeCode: cardinal; override; + + function flattenedSize: integer; override; + + //Returns true only if code is B_RECT_TYPE + function allowsTypeCode(code: integer): boolean; override; + + procedure flatten( AOut: TStream ); override; + procedure unflatten(ain: TStream; numBytes: integer); override; + + + property Height: single read getHeight; + property Width: single read getWidth; + + property leftTop: TmusclePoint read getleftTop write setleftTop; + property rightBottom: TmusclePoint read getrightBottom write setrightBottom; + property rightTop: TmusclePoint read getrightTop write setrightTop; + property leftBottom: TmusclePoint read getleftBottom write setleftBottom; + + end; + +implementation + +{ TmuscleRect } + +constructor TmuscleRect.Create; +begin + Fbottom := -1.0; + Ftop := 0.0; + Fleft := 0.0; + Fright := -1.0; +end; + +constructor TmuscleRect.Create(l, t, r, b: single); +begin + setBounds(l, t, r, b); +end; + +constructor TmuscleRect.Create(rhs: TmuscleRect); +begin + setEqualTo(rhs); +end; + +function TmuscleRect.cloneFlat: TmuscleFlattenable; +begin + Result := TmuscleRect.Create(Fleft, Ftop, FRight, Fbottom); +end; + +constructor TmuscleRect.Create(topLeft, bottomRight: TmusclePoint); +begin + setBounds(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); +end; + +procedure TmuscleRect.Setbottom(const Value: single); +begin + Fbottom := Value; +end; + +procedure TmuscleRect.setBounds(l, t, r, b: single); +begin + Fbottom := b; + Ftop := t; + Fleft := l; + Fright := r; +end; + +procedure TmuscleRect.setEqualTo(setFromMe: TmuscleFlattenable); +var + copyMe: TmuscleRect; +begin + inherited; + + copyMe := (setFromMe as TmuscleRect); + + setBounds(copyMe.left, copyMe.top, copyMe.right, copyMe.bottom); +end; + +procedure TmuscleRect.Setleft(const Value: single); +begin + Fleft := Value; +end; + +procedure TmuscleRect.Setright(const Value: single); +begin + Fright := Value; +end; + +procedure TmuscleRect.Settop(const Value: single); +begin + Ftop := Value; +end; + +function TmuscleRect.getHeight: single; +begin + Result := Fright - Fleft; +end; + +function TmuscleRect.getWidth: single; +begin + Result := Fbottom - Ftop; +end; + +function TmuscleRect.toString: string; +begin + Result := format('Rect: leftTop=(%f,%f) rightBottom=(%f,%f)', [Fleft, Ftop, Fright, Fbottom]); +end; + +function TmuscleRect.getleftBottom: TmusclePoint; +begin + Result := TmusclePoint.Create(Fleft, Fbottom); +end; + +function TmuscleRect.getleftTop: TmusclePoint; +begin + Result := TmusclePoint.Create(Fleft, Ftop); +end; + +function TmuscleRect.getrightBottom: TmusclePoint; +begin + Result := TmusclePoint.Create(Fright, Fbottom); +end; + +function TmuscleRect.getrightTop: TmusclePoint; +begin + Result := TmusclePoint.Create(Fright, Ftop); +end; + +procedure TmuscleRect.setleftBottom(const Value: TmusclePoint); +begin + Fleft := Value.x; + Fbottom := Value.y; +end; + +procedure TmuscleRect.setleftTop(const Value: TmusclePoint); +begin + Fleft := Value.x; + Ftop := Value.y; +end; + +procedure TmuscleRect.setrightBottom(const Value: TmusclePoint); +begin + Fright := Value.x; + Fbottom := Value.y; +end; + +procedure TmuscleRect.setrightTop(const Value: TmusclePoint); +begin + Fright := Value.x; + Ftop := Value.y; +end; + +procedure TmuscleRect.insetBy(point: TmusclePoint); +begin + insetBy(point.x, point.y); +end; + +procedure TmuscleRect.insetBy(dx, dy: single); +begin + Fleft := Fleft + dx; + Ftop := Ftop + dy; + Fright := Fright - dx; + Fbottom := Fbottom - dy; +end; + +procedure TmuscleRect.offsetBy(point: TmusclePoint); +begin + offsetBy(point.x, point.y); +end; + +procedure TmuscleRect.offsetBy(dx, dy: single); +begin + {left += dx; top += dy; right += dx; bottom += dy} + + Fleft := Fleft + dx; + Ftop := Ftop + dy; + Fright := Fright + dx; + Fbottom := Fbottom + dy; +end; + +function TmuscleRect.equals(o: TObject): boolean; +var + r: TmuscleRect; +begin + if (o is TmuscleRect) then begin + r := TmuscleRect(o); + Result := (Fleft = r.left) and (Ftop = r.top) and (Fright = r.right) and (Fbottom = r.bottom); + end + else + Result := false; +end; + +function TmuscleRect.intersect(r: TmuscleRect): TmuscleRect; +begin + Result := TmuscleRect.Create(Self); + + if (Result.left < r.left) then + Result.left := r.left; + + if (Result.right > r.right) then + Result.right := r.right; + + if (Result.top < r.top) then + Result.top := r.top; + + if (Result.bottom > r.bottom) then + Result.bottom := r.bottom; + +end; + +function TmuscleRect.unify(r: TmuscleRect): TmuscleRect; +begin + Result := TmuscleRect.Create(Self); + + if (r.left < Result.left) then + Result.left := r.left; + + if (r.right > Result.right) then + Result.right := r.right; + + if (r.top < Result.top) then + Result.top := r.top; + + if (r.bottom > Result.bottom) then + Result.bottom := r.bottom; +end; + +function TmuscleRect.intersects(r: TmuscleRect): boolean; +begin + Result := r.intersect(self).isValid; +end; + +function TmuscleRect.isValid: boolean; +begin + {return ((width() >= 0.0f)&&(height() >= 0.0f));} + Result := (width >= 0.0) and (height >= 0.0); +end; + +function TmuscleRect.contains(point: TmusclePoint): boolean; +begin + Result := ((point.x >= Fleft) and (point.x <= Fright) and (point.y >= Ftop) and (point.y <= Fbottom)); +end; + +function TmuscleRect.contains(r: TmuscleRect): boolean; +begin + Result := ((contains(r.leftTop)) and (contains(r.rightTop)) and + (contains(r.leftBottom)) and (contains(r.rightBottom))); +end; + +function TmuscleRect.isFixedSize: boolean; +begin + Result := true; +end; + +function TmuscleRect.typeCode: cardinal; +begin + Result := TypeConstants.B_RECT_TYPE; // 'RECT'; +end; + +function TmuscleRect.flattenedSize: integer; +begin + Result := SizeOf(Single) * 4; //16... +end; + +function TmuscleRect.allowsTypeCode(code: integer): boolean; +begin + Result := (code = TypeConstants.B_RECT_TYPE); +end; + +procedure TmuscleRect.flatten(AOut: TStream); +begin + try + AOut.Write(Fleft, SizeOf(Single)); + AOut.Write(Ftop, SizeOf(Single)); + AOut.Write(Fright, SizeOf(Single)); + AOut.Write(Fbottom, SizeOf(Single)); + except + inherited; + end; +end; + +procedure TmuscleRect.unflatten(ain: TStream; numBytes: integer); +begin + try + ain.Read(Fleft, SizeOf(Single)); + ain.Read(Ftop, SizeOf(Single)); + ain.Read(Fright, SizeOf(Single)); + ain.Read(Fbottom, SizeOf(Single)); + except + inherited; + end; +end; + +end. diff --git a/delphi/StorageReflectConstants.pas b/delphi/StorageReflectConstants.pas new file mode 100644 index 00000000..c3b833ed --- /dev/null +++ b/delphi/StorageReflectConstants.pas @@ -0,0 +1,370 @@ +{----------------------------------------------------------------------------- + Unit Name: StorageReflectConstants + Author: matte + Date: 05-Jul-2005 + Purpose: + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit StorageReflectConstants; + +interface + +const + BEGIN_PR_COMMANDS = 558916400; + + /// Adds/replaces the given fields in the parameters table + PR_COMMAND_SETPARAMETERS = 558916401; + + /// Returns the current parameter set to the client + PR_COMMAND_GETPARAMETERS = 558916402; + + /// deletes the parameters specified in PR_NAME_KEYS + PR_COMMAND_REMOVEPARAMETERS = 558916403; + + /// Adds/replaces the given message in the data table + PR_COMMAND_SETDATA = 558916404; + + /// Retrieves the given message(s) in the data table + PR_COMMAND_GETDATA = 558916405; + + /// Removes the gives message(s) from the data table + PR_COMMAND_REMOVEDATA = 558916406; + + /// Removes data from outgoing result messages + PR_COMMAND_JETTISONRESULTS = 558916407; + + /// Insert nodes underneath a node, as an ordered list + PR_COMMAND_INSERTORDEREDDATA = 558916408; + + /// Echo this message back to the sending client + PR_COMMAND_PING = 558916409; + + /// Kick matching clients off the server (Requires privilege) + PR_COMMAND_KICK = 558916410; + + /// Add ban patterns to the server's ban list (Requires privilege) + PR_COMMAND_ADDBANS = 558916411; + + /// Remove ban patterns from the server's ban list (Requires privilege) + PR_COMMAND_REMOVEBANS = 558916412; + + /// Submessages under PR_NAME_KEYS are executed in order, as if they + /// came separately + PR_COMMAND_BATCH = 558916413; + + /// Server will ignore this message + PR_COMMAND_NOOP = 558916414; + + /// Move one or more intries in a node index to a different spot in + /// the index + PR_COMMAND_REORDERDATA = 558916415; + + /// Add require patterns to the server's require list + /// (Requires ban privilege) + PR_COMMAND_ADDREQUIRES = 558916416; + + /// Remove require patterns from the server's require list + /// (Requires ban privilege) + PR_COMMAND_REMOVEREQUIRES = 558916417; + + /// Reserved for future expansion + PR_COMMAND_RESERVED11 = 558916418; + + /// Reserved for future expansion + PR_COMMAND_RESERVED12 = 558916419; + + /// Reserved for future expansion + PR_COMMAND_RESERVED13 = 558916420; + + /// Reserved for future expansion + PR_COMMAND_RESERVED14 = 558916421; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED15 = 558916422; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED16 = 558916423; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED17 = 558916424; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED18 = 558916425; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED19 = 558916426; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED20 = 558916427; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED21 = 558916428; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED22 = 558916429; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED23 = 558916430; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED24 = 558916431; + + /// Reserved for future expansion */ + PR_COMMAND_RESERVED25 = 558916432; + + /// Dummy value to indicate the end of the reserved command range + END_PR_COMMANDS = 558916433; + + /// Marks beginning of range of 'what' codes that may be generated + /// by the StorageReflectSession and sent back to the client + BEGIN_PR_RESULTS = 558920240; + + /// Sent to client in response to PR_COMMAND_GETPARAMETERS + PR_RESULT_PARAMETERS = 558920241; + + /// Sent to client in response to PR_COMMAND_GETDATA, or subscriptions + PR_RESULT_DATAITEMS = 558920242; + + /// Sent to client to tell him that we don't know how to process + /// his request message + PR_RESULT_ERRORUNIMPLEMENTED = 558920243; + + /// Notification that an entry has been inserted into an ordered index + PR_RESULT_INDEXUPDATED = 558920244; + + /// Response from a PR_COMMAND_PING message */ + PR_RESULT_PONG = 558920245; + + /// Your client isn't allowed to do something it tried to do + PR_RESULT_ERRORACCESSDENIED = 558920246; + + /// Reserved for future expansion + PR_RESULT_RESERVED4 = 558920247; + + /// Reserved for future expansion + PR_RESULT_RESERVED5 = 558920248; + + /// Reserved for future expansion + PR_RESULT_RESERVED6 = 558920249; + + /// Reserved for future expansion + PR_RESULT_RESERVED7 = 558920250; + + /// Reserved for future expansion + PR_RESULT_RESERVED8 = 558920251; + + /// Reserved for future expansion + PR_RESULT_RESERVED9 = 558920252; + + /// Reserved for future expansion + PR_RESULT_RESERVED10 = 558920253; + + /// Reserved for future expansion + PR_RESULT_RESERVED11 = 558920254; + + /// Reserved for future expansion + PR_RESULT_RESERVED12 = 558920255; + + /// Reserved for future expansion + PR_RESULT_RESERVED13 = 558920256; + + /// Reserved for future expansion + PR_RESULT_RESERVED14 = 558920257; + + /// Reserved for future expansion + PR_RESULT_RESERVED15 = 558920258; + + /// Reserved for future expansion + PR_RESULT_RESERVED16 = 558920259; + + /// Reserved for future expansion + PR_RESULT_RESERVED17 = 558920260; + + /// Reserved for future expansion + PR_RESULT_RESERVED18 = 558920261; + + /// Reserved for future expansion + PR_RESULT_RESERVED19 = 558920262; + + /// Reserved for future expansion + PR_RESULT_RESERVED20 = 558920263; + + /// Reserved for future expansion + PR_RESULT_RESERVED21 = 558920264; + + /// Reserved for future expansion + PR_RESULT_RESERVED22 = 558920265; + + /// Reserved for future expansion + PR_RESULT_RESERVED23 = 558920266; + + /// Reserved for future expansion + PR_RESULT_RESERVED24 = 558920267; + + /// Reserved for future expansion + PR_RESULT_RESERVED25 = 558920268; + + /// Reserved for future expansion + END_PR_RESULTS = 558920269; + + /// Privilege bit, indicates that the client is allowed to kick + /// other clients off the MUSCLE server + PR_PRIVILEGE_KICK = 0; + + /// Privilege bit, indicates that the client is allowed to ban + /// other clients from the MUSCLE server + PR_PRIVILEGE_ADDBANS = 1; + + /// Privilege bit, indicates that the client is allowed to unban + /// other clients from the MUSCLE server + PR_PRIVILEGE_REMOVEBANS = 2; + + /// Number of defined privilege bits + PR_NUM_PRIVILEGES = 3; + + /// Op-code that indicates that an entry was inserted at the + /// given slot index, with the given ID + INDEX_OP_ENTRYINSERTED = 'i'; + + /// Op-code that indicates that an entry was removed from the + /// given slot index, had the given ID + INDEX_OP_ENTRYREMOVED = 'r'; + + /// Op-code that indicates that the index was cleared */ + INDEX_OP_CLEARED = 'c'; + + /// Field name for key-strings (often node paths or regex expressions) + PR_NAME_KEYS = '!SnKy'; + + /// Field name to contains node path strings of removed data items + PR_NAME_REMOVED_DATAITEMS = '!SnRd'; + + /// Field name (any type): If present in a PR_COMMAND_SETPARAMETERS + /// message, disables inital-value-send from new subscriptions + PR_NAME_SUBSCRIBE_QUIETLY = '!SnQs'; + + /// Field name (any type): If present in a PR_COMMAND_SETDATA message, + /// subscribers won't be notified about the change. + PR_NAME_SET_QUIETLY = '!SnQ2'; + + /// Field name (any type): If present in a PR_COMMAND_REMOVEDATA + /// message, subscribers won't be notified about the change. + PR_NAME_REMOVE_QUIETLY = '!SnQ3'; + + // Parameter name holding int32 of MUSCLE_MESSAGE_ENCODING_* used to + // send to client + PR_NAME_REPLY_ENCODING = '!Enc'; + + /// Field name (any type): If set as parameter, include ourself + /// in wildcard matches + PR_NAME_REFLECT_TO_SELF = '!Self'; + + /// Field name (any type): If set as a parameter, disable all + /// subscription update messaging. + PR_NAME_DISABLE_SUBSCRIPTIONS = '!Dsub'; + + /// Field name of int parameter; sets max # of items per + /// PR_RESULT_DATAITEMS message + PR_NAME_MAX_UPDATE_MESSAGE_ITEMS = '!MxUp'; + + /// Field name of string returned in parameter set; + /// contains this session's /host/sessionID path + PR_NAME_SESSION_ROOT = '!Root'; + + /// Field name for Message: In PR_RESULT_ERROR_* messages, + /// holds the client's message that failed to execute. + PR_NAME_REJECTED_MESSAGE = '!Rjct'; + + /// Field name of int32 bitchord of client's PR_PRIVILEGE_* bits. + PR_NAME_PRIVILEGE_BITS = '!Priv'; + + /// Field name of int64 indicating how many more bytes are available + /// for MUSCLE server to use + PR_NAME_SERVER_MEM_AVAILABLE = '!Mav'; + + /// Field name of int64 indicating how many bytes + /// the MUSCLE server currently has allocated */ + PR_NAME_SERVER_MEM_USED = '!Mus'; + + /// Field name of int64 indicating how the maximum number of bytes + /// the MUSCLE server may have allocated at once. + PR_NAME_SERVER_MEM_MAX = '!Mmx'; + + /// Field name of string indicating version of MUSCLE that the server + /// was compiled from + PR_NAME_SERVER_VERSION = '!Msv'; + + /// Field name of int64 indicating how many microseconds have + /// elapsed since the server was started. + PR_NAME_SERVER_UPTIME = '!Mup'; + + /// Field name of int32 indicating how many database nodes may be + /// uploaded by this client (total). + PR_NAME_MAX_NODES_PER_SESSION = '!Mns'; + + /// Field name of a string that the server will replace with the + /// session ID string of your session in any outgoing + /// client-to-client messages. + PR_NAME_SESSION = 'session'; + + /// this field name's submessage is the payload of the current node + /// in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + PR_NAME_NODEDATA = 'data'; + + /// this field name's submessage represents the children of the + /// current node (recursive) in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + PR_NAME_NODECHILDREN = 'kids'; + + /// this field name's submessage represents the index of the current + /// node in the message created by + /// StorageReflectSession::SaveNodeTreeToMessage() + PR_NAME_NODEINDEX = 'index'; + +implementation + +end. diff --git a/delphi/TypeConstants.pas b/delphi/TypeConstants.pas new file mode 100644 index 00000000..99dcec64 --- /dev/null +++ b/delphi/TypeConstants.pas @@ -0,0 +1,211 @@ +{----------------------------------------------------------------------------- + Unit Name: TypeConstants + Author: Matthew Emson + Date: 30-Oct-2003 + Purpose: + History: +-----------------------------------------------------------------------------} + +// This file is based on MUSCLE, Copyright 2007 Meyer Sound Laboratories Inc. +// See the LICENSE.txt file included with the MUSCLE source for details. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +unit TypeConstants; + +interface + +//** declarations for Be's type constants. The values are all the same as Be's. */ + +const //all 'integer' + B_ANY_TYPE = 1095653716; // 'ANYT', // wild card + B_BOOL_TYPE = 1112493900; // 'BOOL', + B_DOUBLE_TYPE = 1145195589; // 'DBLE', + B_FLOAT_TYPE = 1179406164; // 'FLOT', + B_INT64_TYPE = 1280069191; // 'LLNG', // a.k.a. long in Java + B_INT32_TYPE = 1280265799; // 'LONG', // a.k.a. int in Java + B_INT16_TYPE = 1397248596; // 'SHRT', // a.k.a. short in Java + B_INT8_TYPE = 1113150533; // 'BYTE', // a.k.a. byte in Java + B_MESSAGE_TYPE = 1297303367; // 'MSGG', + B_POINTER_TYPE = 1347310674; // 'PNTR', // parsed as int in Java (but not very useful) + B_POINT_TYPE = 1112559188; // 'BPNT', // muscle.support.Point in Java + B_RECT_TYPE = 1380270932; // 'RECT', // muscle.support.Rect in Java + B_STRING_TYPE = 1129534546; // 'CSTR', // java.lang.String + B_OBJECT_TYPE = 1330664530; // 'OPTR', + B_RAW_TYPE = 1380013908; // 'RAWT', + B_MIME_TYPE = 1296649541; // 'MIME', + + B_TAG_TYPE = 1297367367; //* 'MTAG' = new for v2.00; for in-mem-only tags */ + + B_ERROR = -1; + B_NO_ERROR = 0; + B_OK = B_NO_ERROR; + +type + uint32 = longword; + int32 = longint; + int16 = smallint; + uint16 = word; + int8 = shortint; + uint6 = int64; //not much else we can do!! + + float = single; //just to make code similar.. + + status_t = integer; + + +function {MuscleX86SwapDouble} B_SWAP_DOUBLE(val: double): double; cdecl; +function {MuscleX86SwapFloat} B_SWAP_FLOAT(val: float): float; cdecl; +function {MuscleX86SwapInt64} B_SWAP_INT64(val: int64): int64; cdecl; +function {MuscleX86SwapInt32} B_SWAP_INT32(val: uint32): uint32; cdecl; +function {MuscleX86SwapInt16} B_SWAP_INT16(val: uint16): uint16; cdecl; + +procedure UnitTest; //test stuff, including the byteswapping routines.. + +implementation + + +function {MuscleX86SwapInt16} B_SWAP_INT16(val: uint16): uint16; cdecl; +begin + asm + mov ax, val; + xchg al, ah; + mov val, ax; + end; + result := val; +end; + + +function {MuscleX86SwapInt32} B_SWAP_INT32(val: uint32): uint32; cdecl; +begin + asm + mov eax, val; + bswap eax; + mov val, eax; + end; + result := val; +end; + +function {MuscleX86SwapFloat} B_SWAP_FLOAT(val: float): float; cdecl; +begin + asm + mov eax, val; + bswap eax; + mov val, eax; + end; + + result := val; +end; + +function {MuscleX86SwapInt64} B_SWAP_INT64(val: int64): int64; cdecl; +begin + asm + mov eax, DWORD PTR val; + mov edx, DWORD PTR val + 4; + bswap eax; + bswap edx; + mov DWORD PTR val, edx; + mov DWORD PTR val + 4, eax; + end; + result := val; +end; + +function {MuscleX86SwapDouble} B_SWAP_DOUBLE(val: double): double; cdecl; +begin + asm + mov eax, DWORD PTR val; + mov edx, DWORD PTR val + 4; + bswap eax; + bswap edx; + mov DWORD PTR val, edx; + mov DWORD PTR val + 4, eax; + end; + result := val; +end; + + +{----------------------------------------------------------------------------- + Procedure: unittest + Author: Matthew Emson + Purpose: test stuff, including the byteswapping routines.. +-----------------------------------------------------------------------------} +procedure unittest; +var + a, f: int16; + b, g: int32; + c, h: int64; + d, i, k: float; + e, j, l: double; + +begin + a := 12345; + f := B_SWAP_INT16(a); + a := B_SWAP_INT16(f); + + assert(a = 12345); + + b := 1234567; + g := B_SWAP_INT32(b); + b := B_SWAP_INT32(g); + + assert(b = 1234567); + + c := 1234567891011; + h := B_SWAP_INT64(c); + c := B_SWAP_INT64(h); + + assert(c = 1234567891011); + + d := 123.456; + i := B_SWAP_FLOAT(d); + d := B_SWAP_FLOAT(i) *1000; + k := Round(d); //singles and rounding suck.. + + assert(k = 123456); + + e := 12345.6789; + j := B_SWAP_DOUBLE(e); + e := B_SWAP_DOUBLE(j) * 10000; + l := Round(e); //doubles and rounding suck.. + + assert(l = 123456789); +end; + +end. diff --git a/delphi/licence-delphi.txt b/delphi/licence-delphi.txt new file mode 100644 index 00000000..25dc5c03 --- /dev/null +++ b/delphi/licence-delphi.txt @@ -0,0 +1,41 @@ +MUSCLE Client Component for Delphi. + +Portions are based on MUSCLE, Copyright 2000-2009 Meyer Sound Laboratories Inc. +See the LICENSE.txt file included with the MUSCLE source for details. + +Copyright (c) 2005, Matthew Emson +All rights reserved. + +Redistribution and use in source and binary forms, +with or without modification, are permitted provided +that the following conditions are met: + + * Redistributions of source code must retain the + above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce + the above copyright notice, this list of + conditions and the following disclaimer in the + documentation and/or other materials provided + with the distribution. + * Neither the name of "Matthew Emson", current or + past employer of "Matthew Emson" nor the names + of any contributors may be used to endorse or + promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/delphi/musclecomponents.dof b/delphi/musclecomponents.dof new file mode 100644 index 00000000..291bddf3 --- /dev/null +++ b/delphi/musclecomponents.dof @@ -0,0 +1,81 @@ +[Compiler] +A=1 +B=0 +C=1 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=1 +K=0 +L=1 +M=0 +N=1 +O=0 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=1 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=4194304 +ExeDescription=Muscle delphi components +[Directories] +OutputDir= +UnitOutputDir= +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath=$(DELPHI)\Lib\Debug +Packages=Vcl50;Vclx50;VclSmp50;Vcldb50;vclado50;ibevnt50;Vclbde50;vcldbx50;Qrpt50;TeeUI50;TeeDB50;TeeQR50;VCLIB50;Vclmid50;vclie50;Inetdb50;Inet50;NMFast50;webmid50;dclocx50;dclaxserver50;rbTDBC75;rbRCL75;rbBDE75;rbUSER75;Indy50;B304_r50;ff2_r50;L207_r50;ZCore;ZParseSql;ZPlain;ZDbc;ZComponent;ZDbView5;EPCOTAUtils50 +Conditionals= +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=2057 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= +[Excluded Packages] +$(DELPHI)\Bin\dclite50.bpl=Borland Integrated Translation Environment +$(DELPHI)\Bin\DCLDSS50.bpl=Borland Decision Cube Components diff --git a/delphi/musclecomponents.dpk b/delphi/musclecomponents.dpk new file mode 100644 index 00000000..7de9475d --- /dev/null +++ b/delphi/musclecomponents.dpk @@ -0,0 +1,45 @@ +package musclecomponents; + +{$R *.RES} +{$ALIGN ON} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO ON} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS ON} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION OFF} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO ON} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST ON} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DESCRIPTION 'Muscle delphi components'} +{$IMPLICITBUILD OFF} + +requires + vcl50; + +contains + MessageTransceiver in 'MessageTransceiver.pas', + MessageQueue in 'MessageQueue.pas', + IOGateway in 'IOGateway.pas', + Locker in 'Locker.pas', + Message in 'Message.pas', + Flattenable in 'Flattenable.pas', + MuscleExceptions in 'MuscleExceptions.pas', + Point in 'Point.pas', + Rect in 'Rect.pas', + StorageReflectConstants in 'StorageReflectConstants.pas', + TypeConstants in 'TypeConstants.pas', + Semaphore in 'semaphore.pas'; + +end. diff --git a/delphi/musclecomponents.res b/delphi/musclecomponents.res new file mode 100644 index 00000000..564196d0 Binary files /dev/null and b/delphi/musclecomponents.res differ diff --git a/delphi/semaphore.pas b/delphi/semaphore.pas new file mode 100644 index 00000000..e4e43eaf --- /dev/null +++ b/delphi/semaphore.pas @@ -0,0 +1,558 @@ +{----------------------------------------------------------------------------- + Unit Name: Semaphore + Author: mathew emson + Date: 21-Oct-2001 + Purpose: + History: Matt Emson 20021021 + + 0.0.1 - basic functionality there, but some areas need a little work. + 0.0.2 - Added two further classes 'TNamedSemaphore' and 'TNamedBlockingSemaphore'. + These accomodate the need to acquire semaphores already active in other + processes. So long as you use the exact same name in the same case, the + semaphore will be shared. NB. Acquiring a semaphore will allow the + semaphore to be closed by a second semaphore class. This should not close + the handle for all other classes who own the handle, but be carefull + none the less.. + 0.0.3 - Now tried and tested old workhorse !! + +-----------------------------------------------------------------------------} + + + + + + +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +unit Semaphore; + +interface + +uses + Classes, windows, messages, sysutils; + +const + //Why did Borland not include this in 'Windows'??? + //It's in Borland's BCC32 v5.5 'Winnt.h' header file... + SEMAPHORE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $3; + +type + // ---------------------------------------------------------------------------------------------- + // TBaseSemaphore TSingleInstance + // TBlockingSemaphore TSemaphore + // TNamedBlockingSemaphore TNamedSemaphore + // ---------------------------------------------------------------------------------------------- + TBaseSemaphore = class(TObject) + private + FSemaphore: Thandle; + FlockCount: Integer; + FSemCount: integer; + + function GetUnreliableLockCount: integer; + + protected + FName: PChar; + //you must add a lock method to enable locking to take place... + //e.g. + // procedure Lock; + // function Lock: boolean; + + //unlocks one level of lock. If you use the default lock level of 1, this + //works well, but otherwise remember to unlock the same number of times you lock. + procedure Unlock; virtual; + //most flakey at the best of times. needs to be improved - interlockedexchange will + //probably yield better results. + function CanLock: boolean; virtual; + //defaults the lock count to 1 + + public + constructor Create(count: cardinal = 1); virtual; + destructor Destroy; override; + + property UnreliableLockCount: integer read GetUnreliableLockCount; + property SemaphoreHandle: THandle read FSemaphore; + end; + + // ---------------------------------------------------------------------------------------------- + TBlockingSemaphore = class(TBaseSemaphore) + public + //CAUTION! Calling lock will block until enough semaphores + // are unlocked to allow another semaphore to be created. + // Always take a look at 'CanLock' if in a single threaded + // environment. + procedure Lock; virtual; + procedure Unlock; override; //brought forward + function CanLock: boolean; override; //brought forward + end; + + // ---------------------------------------------------------------------------------------------- + TNamedBlockingSemaphore = class(TBlockingSemaphore) + public + function Acquire(const AName: string): boolean; + + {will acquire sem if is is already in existance..} + constructor Create(AName: string; count: cardinal = 1); reintroduce; virtual; + constructor CreateUnconnected(AName: string = ''); virtual; + destructor Destroy; override; + end; + + // ---------------------------------------------------------------------------------------------- + TSemaphore = class(TBaseSemaphore) + private + FTimeout: Longword; + procedure SetTimeout(const Value: Longword); + + public + //The timeout will happen between the lock call and the success/fail of the + //lock: + // If the lock succeeeds, then wait <= Timeout, + // else if lock fails, then wait = timeout; + //With this in mind, a wait of 1000 (I second) is reasonable, but caustion + //should be taken with longet timeout's - you could seriously impact on the + //preformance of the app. + property Timeout: Longword read FTimeout write SetTimeout; + + //lock returns true if the semaphore was able to acquire a lock + function Lock: boolean; virtual; + function LockEx(ATimeOut: longword): boolean; + procedure Unlock; override; //brought forward + function CanLock: boolean; override; //brought forward + + constructor Create(count: cardinal = 1); override; + end; + + // ---------------------------------------------------------------------------------------------- + TNamedSemaphore = class(TSemaphore) + public + function Acquire(const AName: string): boolean; + constructor Create(AName: string; count: cardinal = 1); reintroduce; virtual; + constructor CreateUnconnected(AName: string = ''); virtual; + destructor Destroy; override; + end; + + // ---------------------------------------------------------------------------------------------- + TSingleInstance = class + private + FSemaphore: THandle; + FToken: string; + FInstalled: Boolean; + public + constructor Create(Token: string); reintroduce; virtual; + function InstallSingleInstance: boolean; + procedure UninstallSingleInstance; + destructor Destroy; override; + end; + + // ---------------------------------------------------------------------------------------------- + +implementation + +// ------------------------------------------------------------------------------------------------ +// TBaseSemaphore +// ------------------------------------------------------------------------------------------------ + +constructor TBaseSemaphore.Create(count: cardinal); +begin + FlockCount := 0; + FSemCount := count; + + FSemaphore := CreateSemaphore(nil, count, count, FName); + + //dwritefmt('create_sem %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); +end; + +// ------------------------------------------------------------------------------------------------ + +destructor TBaseSemaphore.Destroy; +var + i: integer; +begin + // why bother? surely just close the semaphore????? + + if (FlockCount > 0) then + begin + //dwritefmt('Lock error %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); + for i := FlockCount - 1 downto 0 do + begin + Unlock; + end; + end; + + CloseHandle(FSemaphore); + + //dwritefmt('delete_sem %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); + + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +function TBaseSemaphore.CanLock: boolean; +begin + Result := (FSemCount < FlockCount); // dodgy ground.. may give false + // results in extreme threading +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TBaseSemaphore.Unlock; +begin + ReleaseSemaphore(FSemaphore, 1, nil); + + if (FLockCount > 0) then + InterlockedDecrement(FLockCount); + //else + {if (FName <> nil) then begin + dwritefmt('attempt to unlock > count %d - %s."%s"', [FSemaphore, Classname, FName]) + end + else + dwritefmt('attempt to unlock > count %d - %s."%s"', [FSemaphore, Classname, '']); + + if (FName <> nil) then + dwritefmt('Unlock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]) + else + dwritefmt('Unlock %d - %s."%s" count %d', [FSemaphore, Classname, '', FLockCount]); } +end; + +// ------------------------------------------------------------------------------------------------ + +function TBaseSemaphore.GetUnreliableLockCount: integer; +begin + //dwritefmt('GetUnreliableLockCount(1) %d', [FLockCount]); + Result := FLockCount; + //dwritefmt('GetUnreliableLockCount(2) %d', [FLockCount]); + sleep(0); + //dwritefmt('GetUnreliableLockCount(3) %d', [FLockCount]); +end; + + + +// ------------------------------------------------------------------------------------------------ +// TSemaphore +// ------------------------------------------------------------------------------------------------ + +constructor TSemaphore.Create(count: cardinal); +begin + FTimeout := 1000; // 1 second; + + inherited; + + {if (FName <> nil) then + dwritefmt('create_sem %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]) + else + dwritefmt('create_sem %d - %s."%s" count %d', [FSemaphore, Classname, '', FLockCount]); } +end; + +// ------------------------------------------------------------------------------------------------ + +function TSemaphore.Lock: boolean; +begin + //dwritefmt('Before Lock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); + {Result := false; + if WaitForSingleObject(FSemaphore, FTimeout) = WAIT_OBJECT_0 then begin + Result := True; + InterlockedIncrement(FLockCount); + end; } + + //1_4_2_2 - needed a way to specify the timeout... + Result := LockEx(FTimeout); + + //dwritefmt('Lock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); +end; + + +// ------------------------------------------------------------------------------------------------ + +function TSemaphore.LockEx (ATimeOut: longword): boolean; +begin + Result := (WaitForSingleObject(FSemaphore, ATimeout) = WAIT_OBJECT_0); + + if Result then + InterlockedIncrement(FLockCount); +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TSemaphore.Unlock; +begin + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +function TSemaphore.CanLock: boolean; +begin + result := inherited CanLock; + + //dwritefmt('can lock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TSemaphore.SetTimeout(const Value: Longword); +begin + FTimeout := Value; +end; + + +// ------------------------------------------------------------------------------------------------ +// BlockingSemaphore +// ------------------------------------------------------------------------------------------------ + +procedure TBlockingSemaphore.Lock; +begin + {if (FName <> nil) then + dwritefmt('Before Lock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]) + else + dwritefmt('Before Lock %d - %s."%s" count %d', [FSemaphore, Classname, '', FLockCount]); } + + while WaitForSingleObject(FSemaphore, INFINITE) <> WAIT_OBJECT_0 do + sleep(0); //release the context to allow other threads to run. + + InterlockedIncrement(FLockCount); + + {if (FName <> nil) then + dwritefmt('Lock %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]) + else + dwritefmt('Lock %d - %s."%s" count %d', [FSemaphore, Classname, '', FLockCount]); } +end; + +// ------------------------------------------------------------------------------------------------ + +procedure TBlockingSemaphore.Unlock; +begin + inherited; +end; + +// ------------------------------------------------------------------------------------------------ + +function TBlockingSemaphore.CanLock: boolean; +begin + result := inherited CanLock; +end; + +// ------------------------------------------------------------------------------------------------ +// TNamedBlockingSemaphore +// ------------------------------------------------------------------------------------------------ + + +function TNamedBlockingSemaphore.Acquire(const AName: string): boolean; +begin + Result := True; + + if FSemaphore > 0 then + CloseHandle(FSemaphore); + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, FName); + + if FSemaphore = 0 then + Result := False; +end; + +constructor TNamedBlockingSemaphore.Create(AName: string; count: cardinal); +begin + FlockCount := 0; + FSemCount := count; + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, FName); + + if FSemaphore = 0 then + inherited Create(count) + { else + dwritefmt('create_sem %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount])}; +end; + +constructor TNamedBlockingSemaphore.CreateUnconnected(AName: string); +begin + FlockCount := 0; + FSemCount := -1; //can't be set if not connected because the only way + //to activate the class is to call 'Acquire' and this + //will fail if the semaphore doesn't exist already, quid-pro-quo + //the count will already be set up + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FSemaphore := 0; + + //dwritefmt('create_sem_ex %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); +end; + +destructor TNamedBlockingSemaphore.Destroy; +begin + if FName <> nil then StrDispose(FName); + + inherited; +end; + +{ TNamedSemaphore } + +function TNamedSemaphore.Acquire(const AName: string): boolean; +begin + Result := True; + + if FSemaphore > 0 then + CloseHandle(FSemaphore); + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, FName); + + if FSemaphore = 0 then + Result := False; +end; + +constructor TNamedSemaphore.Create(AName: string; count: cardinal); +begin + FlockCount := 0; + FSemCount := count; + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, FName); + + if FSemaphore = 0 then + inherited Create(count); + //else + //dwritefmt('create_sem %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); + + FTimeout := 1000; +end; + +constructor TNamedSemaphore.CreateUnconnected(AName: string); +begin + FlockCount := 0; + FSemCount := -1; //can't be set if not connected because the only way + //to activate the class is to call 'Acquire' and this + //will fail if the semaphore doesn't exist already, quid-pro-quo + //the count will already be set up + + if FName <> nil then StrDispose(FName); + FName := StrAlloc(length(aname) + 1); + StrPCopy(FName, pchar(AName)); + + FTimeout := 1000; + + //dwritefmt('create_sem_ex %d - %s."%s" count %d', [FSemaphore, Classname, FName, FLockCount]); +end; + +destructor TNamedSemaphore.Destroy; +begin + if FName <> nil then StrDispose(FName); + + inherited; +end; + +{ TSingleInstance } + +constructor TSingleInstance.Create(Token: string); +begin + FToken := Token; + FInstalled := false; + + //dwritefmt('singleinstance_sem "%s"', [FToken]); +end; + +destructor TSingleInstance.Destroy; +begin + if FInstalled then + UninstallSingleInstance; + + inherited; +end; + +function TSingleInstance.InstallSingleInstance: boolean; +begin + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, pchar(FToken)); + + if (0 <> FSemaphore) then + begin + // already exists... + Result := False; + UninstallSingleInstance; + end + else + // (ERROR_FILE_NOT_FOUND = GetLastError) + begin + FSemaphore := CreateSemaphore (nil, 1, 1, pchar(FToken)); + + // possible race between opensemaphore and createsemaphore...... + + if (GetLastError = ERROR_ALREADY_EXISTS) then + FSemaphore := OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, pchar(FToken)); + + if (FSemaphore = 0) or (GetLastError = ERROR_ALREADY_EXISTS) then + Result := False + else + Result := True; + + FInstalled := Result; + end; +end; + +procedure TSingleInstance.UninstallSingleInstance; +begin + FInstalled := false; + + ReleaseSemaphore(FSemaphore, 1, nil); + closehandle(FSemaphore); +end; + + +end. + + + + + diff --git a/delphi/test/basic_beshare_client/ChatClientMain.dfm b/delphi/test/basic_beshare_client/ChatClientMain.dfm new file mode 100644 index 00000000..a12b33e7 --- /dev/null +++ b/delphi/test/basic_beshare_client/ChatClientMain.dfm @@ -0,0 +1,123 @@ +object DelphiShareClient: TDelphiShareClient + Left = 278 + Top = 252 + Width = 526 + Height = 460 + Caption = 'DelphiChat' + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + OldCreateOrder = False + OnCreate = FormCreate + PixelsPerInch = 96 + TextHeight = 13 + object Splitter1: TSplitter + Left = 394 + Top = 41 + Width = 3 + Height = 351 + Cursor = crHSplit + Align = alRight + end + object Memo1: TMemo + Left = 0 + Top = 41 + Width = 394 + Height = 351 + Align = alClient + TabOrder = 0 + end + object users: TListBox + Left = 397 + Top = 41 + Width = 121 + Height = 351 + Align = alRight + ItemHeight = 13 + TabOrder = 1 + end + object Panel1: TPanel + Left = 0 + Top = 392 + Width = 518 + Height = 41 + Align = alBottom + Caption = 'Panel1' + TabOrder = 2 + object Edit1: TEdit + Left = 8 + Top = 9 + Width = 417 + Height = 21 + TabOrder = 0 + OnKeyUp = Edit1KeyUp + end + object Button1: TButton + Left = 432 + Top = 8 + Width = 75 + Height = 25 + Caption = 'Send' + TabOrder = 1 + OnClick = Button1Click + end + end + object Panel2: TPanel + Left = 0 + Top = 0 + Width = 518 + Height = 41 + Align = alTop + TabOrder = 3 + object Label1: TLabel + Left = 8 + Top = 12 + Width = 20 + Height = 13 + Caption = 'user' + end + object Label2: TLabel + Left = 208 + Top = 13 + Width = 29 + Height = 13 + Caption = 'server' + end + object Button2: TButton + Left = 432 + Top = 8 + Width = 75 + Height = 25 + Caption = 'Connect' + TabOrder = 0 + OnClick = Button2Click + end + object Edit2: TEdit + Left = 40 + Top = 8 + Width = 121 + Height = 21 + TabOrder = 1 + Text = 'DelphiBinky' + OnKeyUp = Edit2KeyUp + end + object Edit3: TEdit + Left = 248 + Top = 8 + Width = 169 + Height = 21 + TabOrder = 2 + Text = 'beshare.tycomsystems.com' + end + end + object MessageTransceiver1: TMessageTransceiver + Host = 'beshare.tycomsystems.com' + Port = 2960 + OnMessageReceived = MessageTransceiver1MessageReceived + Left = 232 + Top = 160 + end +end diff --git a/delphi/test/basic_beshare_client/ChatClientMain.pas b/delphi/test/basic_beshare_client/ChatClientMain.pas new file mode 100644 index 00000000..089e3f88 --- /dev/null +++ b/delphi/test/basic_beshare_client/ChatClientMain.pas @@ -0,0 +1,377 @@ +{----------------------------------------------------------------------------- + Unit Name: ChatClientMain + Author: matthew emson + Date: 06-Jul-2005 + Purpose: A very basic BeShare client, based on the code for the PythonChat + client. + History: +-----------------------------------------------------------------------------} + +//Portions based on BeShare and PythonChat, part of the MUSCLE distro. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +unit ChatClientMain; + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, + + MessageTransceiver, Message, ExtCtrls, StdCtrls, ScktComp, StorageReflectConstants; + +type + TDelphiShareClient = class(TForm) + Memo1: TMemo; + MessageTransceiver1: TMessageTransceiver; + users: TListBox; + Panel1: TPanel; + Edit1: TEdit; + Button1: TButton; + Splitter1: TSplitter; + Panel2: TPanel; + Label1: TLabel; + Button2: TButton; + Label2: TLabel; + Edit2: TEdit; + Edit3: TEdit; + procedure Button1Click(Sender: TObject); + procedure MessageTransceiver1MessageReceived( + const msgRef: IPortableMessage); + procedure Edit1KeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + procedure Button2Click(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure Edit2KeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + + private + procedure sendChatMessage; + procedure setUserName; + + public + mt: TMessageTransceiver; + username: string; + end; + +var + DelphiShareClient: TDelphiShareClient; + +implementation + +{$R *.DFM} + +const + NET_CLIENT_NEW_CHAT_TEXT = 2; + NET_CLIENT_PING = 5; + NET_CLIENT_PONG = 6; + +//# This method retrieves sessionID, e.g. +//# Given "/199.42.1.106/1308/beshare/name", it returns "1308" +function GetSessionID(x: string): string; +begin + if (x[1] = '/') then + begin + delete(x, 1, 1); + x := copy(x, pos('/', x) +1, length(x)); //x[x.find("/")+1:] //# remove leading slash, hostname and second slash + x := copy(x, 1, pos('/', x) -1); //x = x[:x.find("/")] //# remove everything after the session ID + result := x; + end + else + result := ''; +end; + +{----------------------------------------------------------------------------- + Procedure: Button1Click + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.Button1Click(Sender: TObject); +begin + sendChatMessage; +end; + +{----------------------------------------------------------------------------- + Procedure: MessageTransceiver1MessageReceived + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.MessageTransceiver1MessageReceived( + const msgRef: IPortableMessage); + +var + subscribeMsg, dataMsg: IPortableMessage; + sessionID, chatText, outBuff, user, str: string; + i, count: integer; +begin + case msgRef.what of + MTT_EVENT_CONNECTED: + begin + sleep(500); + memo1.lines.add('Connected...'); + setUserName; + + //# and subscribe to get updates regarding who else is on the server + subscribeMsg := TPortableMessage.Create(StorageReflectConstants.PR_COMMAND_SETPARAMETERS); + subscribeMsg.AddBoolean('SUBSCRIBE:beshare/name', true); + MessageTransceiver1.SendMessageToSessions(subscribeMsg); + end; + + MTT_EVENT_DISCONNECTED: + memo1.lines.add('Disconnected...'); + + NET_CLIENT_NEW_CHAT_TEXT: + begin + sessionID := msgRef.FindString('session'); + chatText := msgRef.FindString('text'); + + outBuff := ''; + if (msgRef.countFields('private') > 0) then + outBuff := ' '; + + //do the "users search" + i := users.Items.IndexOfObject(pointer(StrToInt(sessionID))); + if (i > -1) then + user := users.items[i] + else + user := ''; + + if (comparetext(copy(chatText, 1, 3), '/me') = 0) then + outbuff := outbuff + ' ' + user + ' ' +copy(chatText, 3, length(chatText)) //this is okay... Delphi protects buffer overruns + else + outbuff := outbuff + '(' + sessionid + ') ' + user + ' ' + chatText; + memo1.lines.add(outbuff); + end; + + NET_CLIENT_PING: + begin + sessionID := msgRef.FindString('session'); + if (sessionID <> '') then + begin + msgref.what := NET_CLIENT_PONG; + msgref.AddString(StorageReflectConstants.PR_NAME_KEYS, '/*/'+sessionID+'/beshare'); + msgref.AddString('session', 'blah'); //# server will set this + msgref.AddString('version', 'delphiChat v1.0'); + MessageTransceiver1.SendMessageToSessions(msgref); + end; + end; + + NET_CLIENT_PONG: ; + + StorageReflectConstants.PR_RESULT_DATAITEMS: + begin + //# Username/session list updates! Gotta scan it and see what it says + + //# First check for any node-removal notices + //removedList = nextEvent.GetStrings(StorageReflectConstants.PR_NAME_REMOVED_DATAITEMS) + + count := msgRef.countFields(StorageReflectConstants.PR_NAME_REMOVED_DATAITEMS); + //for str in removedList: + while msgRef.findString(StorageReflectConstants.PR_NAME_REMOVED_DATAITEMS, count -1, str) do + begin + sessionID := GetSessionID(str); + i := users.Items.IndexOfObject(pointer(StrToInt(sessionID))); + if (i > -1) then + begin + chatText := 'User ('+sessionID+') '+users.Items[i]+' has disconnected.'; + users.Items.Delete(i); + memo1.lines.add(chatText); + end; + dec(count); + end; + + + + //# Now check for any node-update notices + for count := 0 to msgRef.FieldNames.Count -1 do + begin + str := msgRef.FieldNames.Strings[count]; + sessionID := GetSessionID(str); + if (sessionID <> '') then + begin + datamsg := msgRef.findMessage(str); + + if (datamsg <> nil) then + begin + user := datamsg.FindString('name'); + if (user <> '') then + begin + i := users.Items.IndexOfObject(pointer(StrToInt(sessionID))); + if (i > -1) then + begin + chatText := 'User ('+sessionID+') (a.k.a. '+users.Items.strings[i]+') is now known as ' + user; + users.Items.Strings[i] := user; + end + else begin + chatText := 'User ('+sessionID+') (a.k.a. '+user+') has connected to the server.'; + users.Items.AddObject(user, pointer(StrToInt(sessionID))); + end; + memo1.lines.add(chatText); + end; + end; + end; + end; + end; + else + // unhandled + end; + + Application.ProcessMessages; +end; + +{----------------------------------------------------------------------------- + Procedure: sendChatMessage + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.sendChatMessage; +var + msg: IPortableMessage; +begin + msg := TPortableMessage.Create(NET_CLIENT_NEW_CHAT_TEXT); //(PR_COMMAND_TEXT_STRINGS); + msg.AddString(StorageReflectConstants.PR_NAME_KEYS, '/*/*/beshare'); + msg.AddString('session', 'blah'); //# server will set this for us + + if (comparetext(copy(Edit1.text, 1, 4), '/prv') = 0) then + begin + msg.AddString('text', copy(Edit1.text, 5, length(Edit1.text))); + msg.AddBoolean('private', true); + end + else msg.AddString('text', Edit1.text); + + if (comparetext(copy(Edit1.text, 1, 4), '/prv') = 0) then + begin + //msg.AddBoolean('private', true); + msg := nil; + raise Exception.Create('Private messages are broken at the moment.. sorry.'); + end; + + MessageTransceiver1.SendMessageToSessions(msg); + + memo1.lines.add(' ' + Edit1.text); + edit1.text := ''; + Application.ProcessMessages; +end; + +{----------------------------------------------------------------------------- + Procedure: Edit1KeyUp + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); +begin + if (key = 13) then sendChatMessage; +end; + +{----------------------------------------------------------------------------- + Procedure: Button2Click + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.Button2Click(Sender: TObject); +begin + if not (MessageTransceiver1.connected) then + begin + MessageTransceiver1.connect(Edit3.text, 2960); + Button2.caption := 'Disconnect'; + end + else begin + MessageTransceiver1.disconnect; + Button2.caption := 'Connect'; + end; +end; + +{----------------------------------------------------------------------------- + Procedure: FormCreate + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.FormCreate(Sender: TObject); +begin + username := 'DelphiBinky'; + Edit2.Text := username; +end; + +{----------------------------------------------------------------------------- + Procedure: setUserName + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.setUserName; +var + nameMsg, uploadMsg: IPortableMessage; +begin + //# Upload our user name so people know who we are + nameMsg := TPortableMessage.Create; + nameMsg.AddString('name', username); + nameMsg.AddInteger('port', 0); // # BeShare requires this, although we don't use it + nameMsg.AddString('version_name', 'delphiChat'); + nameMsg.AddString('version_num', '1.0'); + uploadMsg := TPortableMessage.Create(StorageReflectConstants.PR_COMMAND_SETDATA); + uploadMsg.AddMessage('beshare/name', nameMsg); + MessageTransceiver1.SendMessageToSessions(uploadMsg); + + memo1.lines.add('You are now known as ' + username); +end; + +{----------------------------------------------------------------------------- + Procedure: Edit2KeyUp + Author: mathew emson + Date: 06-Jul-2005 + Purpose: +-----------------------------------------------------------------------------} +procedure TDelphiShareClient.Edit2KeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +begin + username := edit2.text; + + if (key = 13) then + if MessageTransceiver1.connected then + begin + setUserName; + end; +end; + +end. diff --git a/delphi/test/basic_beshare_client/DelphiChat.cfg b/delphi/test/basic_beshare_client/DelphiChat.cfg new file mode 100644 index 00000000..d0e0a162 --- /dev/null +++ b/delphi/test/basic_beshare_client/DelphiChat.cfg @@ -0,0 +1,40 @@ +-$A+ +-$B- +-$C+ +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J+ +-$K- +-$L+ +-$M- +-$N+ +-$O- +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$YD +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$00400000 +-LE"c:\program files\borland\delphi5\Projects\Bpl" +-LN"c:\program files\borland\delphi5\Projects\Bpl" +-U"c:\program files\borland\delphi5\Lib\Debug" +-O"c:\program files\borland\delphi5\Lib\Debug" +-I"c:\program files\borland\delphi5\Lib\Debug" +-R"c:\program files\borland\delphi5\Lib\Debug" +-DVER120 diff --git a/delphi/test/basic_beshare_client/DelphiChat.dof b/delphi/test/basic_beshare_client/DelphiChat.dof new file mode 100644 index 00000000..64aa24e7 --- /dev/null +++ b/delphi/test/basic_beshare_client/DelphiChat.dof @@ -0,0 +1,81 @@ +[Compiler] +A=1 +B=0 +C=1 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=1 +K=0 +L=1 +M=0 +N=1 +O=0 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=1 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=4194304 +ExeDescription= +[Directories] +OutputDir= +UnitOutputDir= +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath=$(DELPHI)\Lib\Debug +Packages=Vcl50;Vclx50;VclSmp50;Vcldb50;vclado50;ibevnt50;Vclbde50;vcldbx50;Qrpt50;TeeUI50;TeeDB50;TeeQR50;VCLIB50;Vclmid50;vclie50;Inetdb50;Inet50;NMFast50;webmid50;dclocx50;dclaxserver50;rbTDBC75;rbRCL75;rbBDE75;rbUSER75;Indy50;B304_r50;ff2_r50;L207_r50;ZCore;ZParseSql;ZPlain;ZDbc;ZComponent;ZDbView5;EPCOTAUtils50 +Conditionals=VER120 +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=2057 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= +[Excluded Packages] +$(DELPHI)\Bin\dclite50.bpl=Borland Integrated Translation Environment +$(DELPHI)\Bin\DCLDSS50.bpl=Borland Decision Cube Components diff --git a/delphi/test/basic_beshare_client/DelphiChat.dpr b/delphi/test/basic_beshare_client/DelphiChat.dpr new file mode 100644 index 00000000..5163b610 --- /dev/null +++ b/delphi/test/basic_beshare_client/DelphiChat.dpr @@ -0,0 +1,57 @@ +//Portions based on BeShare and PythonChat, part of the MUSCLE distro. +// +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +program DelphiChat; + +uses + Forms, + ChatClientMain in 'ChatClientMain.pas' {DelphiShareClient}, + IOGateway, + MessageQueue, + Message, + StorageReflectConstants, + MessageTransceiver; + +{$R *.RES} + +begin + Application.Initialize; + Application.CreateForm(TDelphiShareClient, DelphiShareClient); + Application.Run; +end. diff --git a/delphi/test/basic_beshare_client/DelphiChat.dsk b/delphi/test/basic_beshare_client/DelphiChat.dsk new file mode 100644 index 00000000..ca190e5a --- /dev/null +++ b/delphi/test/basic_beshare_client/DelphiChat.dsk @@ -0,0 +1,429 @@ +[Closed Files] +File_0=SourceModule,'c:\program files\borland\delphi5\source\vcl\scktcomp.pas',0,1,639,1,656,0,0 +File_1=SourceModule,'c:\program files\borland\delphi5\source\vcl\Classes.pas',0,1,3029,1,3052,0,0 +File_2=SourceModule,'c:\program files\borland\delphi5\source\rtl\Sys\system.pas',0,1,3737,1,3760,0,0 +File_3=SourceModule,'C:\development\MattUtils\Message.pas',0,1,1,1,1,0,0 +File_4=SourceModule,'c:\program files\borland\delphi5\source\vcl\StdCtrls.pas',0,1,2869,67,2901,0,0 +File_5=SourceModule,'c:\program files\borland\delphi5\source\vcl\Controls.pas',0,1,4272,1,4289,0,0 +File_6=SourceModule,'c:\program files\borland\delphi5\source\vcl\Forms.pas',0,1,6692,1,6714,0,0 +File_7=SourceModule,'C:\Documents and Settings\matte\Desktop\musclepascal\Unit1.pas',0,1,28,1,40,0,0 +File_8=SourceModule,'C:\Documents and Settings\matte\Desktop\musclepascal\AtomicCounter.pas',0,1,1,1,1,0,0 +File_9=SourceModule,'C:\Documents and Settings\matte\Desktop\musclepascal\TypeConstants.pas',0,1,73,26,124,0,0 + +[Modules] +Module0=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\ChatClientMain.pas +Module1=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\DelphiChat.dpr +Count=2 +EditWindowCount=1 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\ChatClientMain.pas] +ModuleType=SourceModule +FormState=1 +FormOnTop=0 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\DelphiChat.dpr] +ModuleType=SourceModule +FormState=0 +FormOnTop=0 + +[C:\Program Files\Borland\Delphi5\Bin\ProjectGroup1.bpg] +FormState=0 +FormOnTop=0 + +[EditWindow0] +ViewCount=2 +CurrentView=0 +View0=0 +View1=1 +CodeExplorer=CodeExplorer@EditWindow0 +MessageView=MessageView@EditWindow0 +Create=1 +Visible=1 +State=2 +Left=287 +Top=217 +Width=696 +Height=482 +MaxLeft=-4 +MaxTop=192 +MaxWidth=1032 +MaxHeight=580 +ClientWidth=1024 +ClientHeight=553 +LeftPanelSize=0 +LeftPanelClients=CodeExplorer@EditWindow0 +LeftPanelData=00000400010000000C000000436F64654578706C6F7265720000000000000000000000000000000000FFFFFFFF +RightPanelSize=0 +BottomPanelSize=0 +BottomPanelClients=MessageView@EditWindow0 +BottomPanelData=00000400010000000B0000004D657373616765566965770000000000000000000000000000000000FFFFFFFF + +[View0] +Module=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\ChatClientMain.pas +CursorX=1 +CursorY=280 +TopLine=274 +LeftCol=1 + +[View1] +Module=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\basic_beshare_client\DelphiChat.dpr +CursorX=46 +CursorY=38 +TopLine=22 +LeftCol=1 + +[Watches] +Count=0 + +[Breakpoints] +Count=0 + +[AddressBreakpoints] +Count=0 + +[Main Window] +Create=1 +Visible=1 +State=0 +Left=0 +Top=0 +Width=1024 +Height=196 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=1016 +ClientHeight=169 + +[ProjectManager] +Create=1 +Visible=1 +State=0 +Left=369 +Top=372 +Width=438 +Height=303 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=430 +ClientHeight=279 +TBDockHeight=303 +LRDockWidth=438 +Dockable=1 + +[CPUWindow] +Create=1 +Visible=0 +State=0 +Left=0 +Top=311 +Width=533 +Height=353 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=525 +ClientHeight=326 +DumpPane=79 +DisassemblyPane=187 +RegisterPane=231 +FlagPane=64 + +[AlignmentPalette] +Create=1 +Visible=0 +State=0 +Left=200 +Top=152 +Width=156 +Height=82 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=150 +ClientHeight=60 + +[PropertyInspector] +Create=1 +Visible=1 +State=0 +Left=1242 +Top=42 +Width=277 +Height=676 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=267 +ClientHeight=650 +TBDockHeight=676 +LRDockWidth=190 +Dockable=1 +SplitPos=85 +ArrangeBy=Name +SelectedItem=Name +ExpandedItems= +HiddenCategories=Legacy +ShowStatusBar=1 + +[WatchWindow] +Create=1 +Visible=0 +State=0 +Left=205 +Top=149 +Width=187 +Height=417 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=179 +ClientHeight=393 +TBDockHeight=393 +LRDockWidth=179 +Dockable=1 + +[BreakpointWindow] +Create=1 +Visible=0 +State=0 +Left=1295 +Top=413 +Width=737 +Height=197 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=729 +ClientHeight=173 +TBDockHeight=197 +LRDockWidth=737 +Dockable=1 +Column0Width=100 +Column1Width=75 +Column2Width=200 +Column3Width=200 +Column4Width=75 +Column5Width=75 + +[CallStackWindow] +Create=1 +Visible=1 +State=0 +Left=553 +Top=444 +Width=394 +Height=375 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=386 +ClientHeight=351 +TBDockHeight=375 +LRDockWidth=394 +Dockable=1 + +[ThreadStatusWindow] +Create=1 +Visible=0 +State=0 +Left=193 +Top=107 +Width=624 +Height=152 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=616 +ClientHeight=128 +TBDockHeight=152 +LRDockWidth=624 +Dockable=1 +Column0Width=145 +Column1Width=100 +Column2Width=115 +Column3Width=250 + +[ModuleWindow] +Create=1 +Visible=0 +State=0 +Left=1221 +Top=130 +Width=638 +Height=355 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=630 +ClientHeight=331 +TBDockHeight=355 +LRDockWidth=638 +Dockable=1 +Column0Width=125 +Column1Width=100 +Column2Width=155 +EntryPointPane=225 +CompUnitPane=104 + +[DebugLogView] +Create=1 +Visible=0 +State=0 +Left=548 +Top=115 +Width=439 +Height=411 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=431 +ClientHeight=387 +TBDockHeight=291 +LRDockWidth=417 +Dockable=1 + +[LocalVarsWindow] +Create=1 +Visible=0 +State=0 +Left=1229 +Top=-36 +Width=187 +Height=804 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=179 +ClientHeight=780 +TBDockHeight=192 +LRDockWidth=179 +Dockable=1 + +[ToDo List] +Create=1 +Visible=0 +State=0 +Left=277 +Top=259 +Width=470 +Height=250 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=462 +ClientHeight=226 +TBDockHeight=250 +LRDockWidth=470 +Dockable=1 +Column0Width=200 +Column1Width=30 +Column2Width=100 +Column3Width=70 +Column4Width=70 +SortOrder=4 +ShowHints=1 +ShowChecked=1 + +[FPUWindow] +Create=1 +Visible=0 +State=0 +Left=271 +Top=248 +Width=457 +Height=250 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=449 +ClientHeight=223 +FOOO=121 +FlagPane=59 + +[fmGrepResults] +Create=1 +Visible=0 +State=0 +Left=932 +Top=312 +Width=468 +Height=340 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=460 +ClientHeight=316 +TBDockHeight=340 +LRDockWidth=468 +Dockable=1 + +[fmPeInformation] +Create=1 +Visible=0 +State=0 +Left=285 +Top=221 +Width=454 +Height=298 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=446 +ClientHeight=274 +TBDockHeight=298 +LRDockWidth=454 +Dockable=1 + +[fmProjOptionSets] +Create=1 +Visible=0 +State=0 +Left=282 +Top=205 +Width=459 +Height=357 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=451 +ClientHeight=333 +TBDockHeight=357 +LRDockWidth=459 +Dockable=1 + +[InstantModelExplorer] +Create=1 +Visible=0 +State=0 +Left=385 +Top=186 +Width=259 +Height=433 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=251 +ClientHeight=409 +TBDockHeight=433 +LRDockWidth=259 +Dockable=1 + +[CodeExplorer@EditWindow0] +Create=1 +Visible=0 +State=0 +Left=-87 +Top=-78 +Width=140 +Height=305 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=140 +ClientHeight=305 +TBDockHeight=305 +LRDockWidth=140 +Dockable=1 + +[MessageView@EditWindow0] +Create=1 +Visible=0 +State=0 +Left=12 +Top=0 +Width=1012 +Height=52 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=1012 +ClientHeight=52 +TBDockHeight=52 +LRDockWidth=443 +Dockable=1 + +[DockHosts] +DockHostCount=0 + diff --git a/delphi/test/basic_beshare_client/DelphiChat.res b/delphi/test/basic_beshare_client/DelphiChat.res new file mode 100644 index 00000000..564196d0 Binary files /dev/null and b/delphi/test/basic_beshare_client/DelphiChat.res differ diff --git a/delphi/test/basic_beshare_client/first dialog.txt b/delphi/test/basic_beshare_client/first dialog.txt new file mode 100644 index 00000000..e2031400 --- /dev/null +++ b/delphi/test/basic_beshare_client/first dialog.txt @@ -0,0 +1,20 @@ +Thisis the first successful dialog on the Delphi share client - before I implemented username lookups.. + +-------- + +Connected... +(0) +Welcome to TyCom Systems - BeShares' First Fiber-based (15000/2000) Server! + +Type "Atrus rules" for this server rules list + +(400) mervin-delphi is my delphi client ;-) +(400) somebody else say something ;-) +(406) woo +(369) something +(366) expensivelesbian (WinXP), i have -> www.bug-nordic.org/haiku.php (a lot of pics, so may take some time to load) +(379) something +(403) something +(406) you can tell the witty ones here +(400) yep... my delphi beshare client half works... +(369) it's late in the uk diff --git a/delphi/test/basic_beshare_client/readme.txt b/delphi/test/basic_beshare_client/readme.txt new file mode 100644 index 00000000..f003db43 --- /dev/null +++ b/delphi/test/basic_beshare_client/readme.txt @@ -0,0 +1,9 @@ +This is a basic BeShare client. It demonstrates more complex comms between the muscle server and multiple MUSCLE clients. + +Use: Run the client. Change the user and server text if desired - at the moment it is set to the primary BeShare server. You can use a local server if desired, if so 127.0.0.1 or LAN IP address should be used. To compile a server for use - see other documentation. + +Typing text in the edit box on client one and then pressing the return key or the "send" button will send the text to all other clients connected to the BeShare server. + +CAUTION: "/prv" is currently brokey. It will broadcase the private message to all clients.. but not so that you would see it. Not what you want at all!!! + +That's about it for now! Enjoy!!!! \ No newline at end of file diff --git a/delphi/test/simple_text_client/Project1.cfg b/delphi/test/simple_text_client/Project1.cfg new file mode 100644 index 00000000..522ee767 --- /dev/null +++ b/delphi/test/simple_text_client/Project1.cfg @@ -0,0 +1,40 @@ +-$A+ +-$B- +-$C+ +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J+ +-$K- +-$L+ +-$M- +-$N+ +-$O- +-$P+ +-$Q- +-$R- +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$YD +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$00400000 +-LE"c:\program files\borland\delphi5\Projects\Bpl" +-LN"c:\program files\borland\delphi5\Projects\Bpl" +-U"c:\program files\borland\delphi5\Lib\Debug" +-O"c:\program files\borland\delphi5\Lib\Debug" +-I"c:\program files\borland\delphi5\Lib\Debug" +-R"c:\program files\borland\delphi5\Lib\Debug" +-DUSERB603 diff --git a/delphi/test/simple_text_client/Project1.dof b/delphi/test/simple_text_client/Project1.dof new file mode 100644 index 00000000..d499a8aa --- /dev/null +++ b/delphi/test/simple_text_client/Project1.dof @@ -0,0 +1,83 @@ +[Compiler] +A=1 +B=0 +C=1 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=1 +K=0 +L=1 +M=0 +N=1 +O=0 +P=1 +Q=0 +R=0 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=1 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=4194304 +ExeDescription= +[Directories] +OutputDir= +UnitOutputDir= +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath=$(DELPHI)\Lib\Debug +Packages=Vcl50;Vclx50;VclSmp50;Vcldb50;vclado50;ibevnt50;Vclbde50;vcldbx50;qrpt50;VCLIB50;Vclmid50;vclie50;Inetdb50;Inet50;webmid50;dclocx50;TeeUI55;Tee55;TeeDB55;TeeLanguage55;TeePro55;TeeQR55;TeeImage55;TeeGL55;rbTDBC55;rbRCL55;rbCIDE55;rbIDE55;rbBDE55;rbDBDE55;rbDAD55;rbDIDE55;rbUSER55;rbDB55;rbADO55;CoolTrayIcon_D5;A403_R50;rbCT55;AuSPed_Package;FTPClientpkg5;ZCore;ZPlain;ZParseSql;ZDbc;ZComponent +Conditionals=USERB603 +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams= +HostApplication= +[Version Info] +IncludeVerInfo=0 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=2057 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= +[Excluded Packages] +C:\PROGRA~1\Borland\Delphi5\Projects\Bpl\xtradev5.bpl=Extra ReportBuilder Devices +$(DELPHI)\Bin\dclite50.bpl=Borland Integrated Translation Environment +$(DELPHI)\Bin\DCLDSS50.bpl=Borland Decision Cube Components +C:\WINNT\System32\rbIBE55.bpl=ReportBuilder Data Access for Interbase Express diff --git a/delphi/test/simple_text_client/Project1.dpr b/delphi/test/simple_text_client/Project1.dpr new file mode 100644 index 00000000..f6205252 --- /dev/null +++ b/delphi/test/simple_text_client/Project1.dpr @@ -0,0 +1,55 @@ +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +program Project1; + +uses + Forms, + Unit1 in 'Unit1.pas' {Form1}, + IOGateway, + MessageQueue, + Message, + StorageReflectConstants, + MessageTransceiver; + +{$R *.RES} + +begin + Application.Initialize; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. diff --git a/delphi/test/simple_text_client/Project1.dsk b/delphi/test/simple_text_client/Project1.dsk new file mode 100644 index 00000000..0c9af909 --- /dev/null +++ b/delphi/test/simple_text_client/Project1.dsk @@ -0,0 +1,463 @@ +[Closed Files] +File_0=SourceModule,'C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\simple_text_client\Unit1.pas',0,1,1,15,4,0,0 +File_1=SourceModule,'d:\mattutils\Message.pas',0,1,1,1,1,0,0 +File_2=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fSettings.pas',0,1,289,10,328,0,0 +File_3=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fGlobalData.pas',0,1,383,1,430,0,0 +File_4=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fAddCosts.pas',0,1,1,1,1,0,1 +File_5=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fAddBaseForm.pas',0,1,1,1,1,0,0 +File_6=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fVehicleCosts.pas',0,1,1,1,1,0,1 +File_7=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fAdminBaseForm.pas',0,1,1,1,1,0,0 +File_8=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fBaseForm.pas',0,1,1,1,1,0,0 +File_9=SourceModule,'D:\Projects\Transport\source_240\PC\Main Client\fCostCentres.pas',0,1,1,1,1,0,1 + +[Modules] +Module0=c:\program files\borland\delphi5\source\vcl\scktcomp.pas +Module1=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\Message.pas +Module2=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\IOGateway.pas +Module3=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\MessageTransceiver.pas +Count=4 +EditWindowCount=1 + +[c:\program files\borland\delphi5\source\vcl\scktcomp.pas] +ModuleType=SourceModule +FormState=0 +FormOnTop=0 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\Message.pas] +ModuleType=SourceModule +FormState=0 +FormOnTop=0 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\IOGateway.pas] +ModuleType=SourceModule +FormState=0 +FormOnTop=0 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\MessageTransceiver.pas] +ModuleType=SourceModule +FormState=0 +FormOnTop=0 + +[C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\test\simple_text_client\Project1.dpr] +FormState=0 +FormOnTop=0 + +[C:\Program Files\Borland\Delphi5\Bin\ProjectGroup1.bpg] +FormState=0 +FormOnTop=0 + +[EditWindow0] +ViewCount=4 +CurrentView=0 +View0=0 +View1=1 +View2=2 +View3=3 +CodeExplorer=CodeExplorer@EditWindow0 +MessageView=MessageView@EditWindow0 +Create=1 +Visible=1 +State=2 +Left=1251 +Top=193 +Width=870 +Height=642 +MaxLeft=-1 +MaxTop=-4 +MaxWidth=1288 +MaxHeight=1032 +ClientWidth=1280 +ClientHeight=1005 +LeftPanelSize=0 +LeftPanelClients=CodeExplorer@EditWindow0 +LeftPanelData=00000400010000000C000000436F64654578706C6F7265720000000000000000000000000000000000FFFFFFFF +RightPanelSize=0 +BottomPanelSize=0 +BottomPanelClients=MessageView@EditWindow0 +BottomPanelData=00000400010000000B0000004D657373616765566965770000000000000000000000000000000000FFFFFFFF + +[View0] +Module=c:\program files\borland\delphi5\source\vcl\scktcomp.pas +CursorX=1 +CursorY=656 +TopLine=1 +LeftCol=1 + +[View1] +Module=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\MessageTransceiver.pas +CursorX=22 +CursorY=58 +TopLine=48 +LeftCol=1 + +[View2] +Module=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\Message.pas +CursorX=22 +CursorY=218 +TopLine=184 +LeftCol=1 + +[View3] +Module=C:\Documents and Settings\mathew emson.AUSPED\Desktop\delphi\IOGateway.pas +CursorX=29 +CursorY=56 +TopLine=47 +LeftCol=1 + +[Watches] +Count=0 + +[Breakpoints] +Count=0 + +[AddressBreakpoints] +Count=0 + +[Main Window] +Create=1 +Visible=1 +State=2 +Left=-1148 +Top=69 +Width=1024 +Height=196 +MaxLeft=-1 +MaxTop=-1 +MaxWidth=1032 +MaxHeight=196 +ClientWidth=1024 +ClientHeight=169 + +[ProjectManager] +Create=1 +Visible=0 +State=0 +Left=369 +Top=372 +Width=438 +Height=303 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=430 +ClientHeight=279 +TBDockHeight=303 +LRDockWidth=438 +Dockable=1 + +[CPUWindow] +Create=1 +Visible=0 +State=0 +Left=0 +Top=311 +Width=533 +Height=353 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=525 +ClientHeight=326 +DumpPane=79 +DisassemblyPane=187 +RegisterPane=231 +FlagPane=64 + +[AlignmentPalette] +Create=1 +Visible=0 +State=0 +Left=200 +Top=168 +Width=156 +Height=82 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=150 +ClientHeight=60 + +[PropertyInspector] +Create=1 +Visible=1 +State=0 +Left=1092 +Top=291 +Width=366 +Height=751 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=356 +ClientHeight=725 +TBDockHeight=494 +LRDockWidth=190 +Dockable=1 +SplitPos=85 +ArrangeBy=Name +SelectedItem= +ExpandedItems=Title +HiddenCategories=Legacy +ShowStatusBar=1 + +[WatchWindow] +Create=1 +Visible=0 +State=0 +Left=205 +Top=149 +Width=187 +Height=417 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=179 +ClientHeight=393 +TBDockHeight=393 +LRDockWidth=179 +Dockable=1 + +[BreakpointWindow] +Create=1 +Visible=0 +State=0 +Left=1295 +Top=413 +Width=737 +Height=197 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=729 +ClientHeight=173 +TBDockHeight=197 +LRDockWidth=737 +Dockable=1 +Column0Width=100 +Column1Width=75 +Column2Width=200 +Column3Width=200 +Column4Width=75 +Column5Width=75 + +[CallStackWindow] +Create=1 +Visible=1 +State=0 +Left=-711 +Top=207 +Width=339 +Height=783 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=331 +ClientHeight=759 +TBDockHeight=161 +LRDockWidth=346 +Dockable=1 + +[ThreadStatusWindow] +Create=1 +Visible=0 +State=0 +Left=193 +Top=107 +Width=624 +Height=152 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=616 +ClientHeight=128 +TBDockHeight=152 +LRDockWidth=624 +Dockable=1 +Column0Width=145 +Column1Width=100 +Column2Width=115 +Column3Width=250 + +[ModuleWindow] +Create=1 +Visible=0 +State=0 +Left=1221 +Top=130 +Width=638 +Height=355 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=630 +ClientHeight=331 +TBDockHeight=355 +LRDockWidth=638 +Dockable=1 +Column0Width=125 +Column1Width=100 +Column2Width=155 +EntryPointPane=225 +CompUnitPane=104 + +[DebugLogView] +Create=1 +Visible=0 +State=0 +Left=548 +Top=115 +Width=439 +Height=411 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=431 +ClientHeight=387 +TBDockHeight=291 +LRDockWidth=417 +Dockable=1 + +[LocalVarsWindow] +Create=1 +Visible=0 +State=0 +Left=1229 +Top=-36 +Width=187 +Height=804 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=179 +ClientHeight=780 +TBDockHeight=192 +LRDockWidth=179 +Dockable=1 + +[ToDo List] +Create=1 +Visible=0 +State=0 +Left=277 +Top=259 +Width=470 +Height=250 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=462 +ClientHeight=226 +TBDockHeight=250 +LRDockWidth=470 +Dockable=1 +Column0Width=200 +Column1Width=30 +Column2Width=100 +Column3Width=70 +Column4Width=70 +SortOrder=4 +ShowHints=1 +ShowChecked=1 + +[FPUWindow] +Create=1 +Visible=0 +State=0 +Left=271 +Top=248 +Width=457 +Height=250 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=449 +ClientHeight=223 +FOOO=121 +FlagPane=59 + +[fmGrepResults] +Create=1 +Visible=0 +State=0 +Left=1430 +Top=312 +Width=468 +Height=340 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=460 +ClientHeight=316 +TBDockHeight=340 +LRDockWidth=468 +Dockable=1 + +[fmPeInformation] +Create=1 +Visible=0 +State=0 +Left=285 +Top=221 +Width=454 +Height=298 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=446 +ClientHeight=274 +TBDockHeight=298 +LRDockWidth=454 +Dockable=1 + +[fmProjOptionSets] +Create=1 +Visible=0 +State=0 +Left=282 +Top=205 +Width=459 +Height=357 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=451 +ClientHeight=333 +TBDockHeight=357 +LRDockWidth=459 +Dockable=1 + +[InstantModelExplorer] +Create=1 +Visible=0 +State=0 +Left=385 +Top=186 +Width=259 +Height=433 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=251 +ClientHeight=409 +TBDockHeight=433 +LRDockWidth=259 +Dockable=1 + +[CodeExplorer@EditWindow0] +Create=1 +Visible=0 +State=0 +Left=-1268 +Top=-74 +Width=140 +Height=305 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=140 +ClientHeight=305 +TBDockHeight=305 +LRDockWidth=140 +Dockable=1 + +[MessageView@EditWindow0] +Create=1 +Visible=0 +State=0 +Left=12 +Top=0 +Width=1268 +Height=52 +MaxLeft=-1 +MaxTop=-1 +ClientWidth=1268 +ClientHeight=52 +TBDockHeight=52 +LRDockWidth=443 +Dockable=1 + +[DockHosts] +DockHostCount=0 + diff --git a/delphi/test/simple_text_client/Project1.res b/delphi/test/simple_text_client/Project1.res new file mode 100644 index 00000000..564196d0 Binary files /dev/null and b/delphi/test/simple_text_client/Project1.res differ diff --git a/delphi/test/simple_text_client/Unit1.dfm b/delphi/test/simple_text_client/Unit1.dfm new file mode 100644 index 00000000..55e5c1db --- /dev/null +++ b/delphi/test/simple_text_client/Unit1.dfm @@ -0,0 +1,57 @@ +object Form1: TForm1 + Left = 208 + Top = 190 + Width = 403 + Height = 289 + Caption = 'Simple text demo' + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + OldCreateOrder = False + PixelsPerInch = 96 + TextHeight = 13 + object Button1: TButton + Left = 8 + Top = 8 + Width = 75 + Height = 25 + Caption = 'Send' + TabOrder = 0 + OnClick = Button1Click + end + object Edit1: TEdit + Left = 88 + Top = 8 + Width = 289 + Height = 21 + TabOrder = 1 + end + object Memo1: TMemo + Left = 8 + Top = 40 + Width = 377 + Height = 185 + Lines.Strings = ( + '') + TabOrder = 2 + end + object Button2: TButton + Left = 16 + Top = 232 + Width = 361 + Height = 25 + Caption = 'Connect' + TabOrder = 3 + OnClick = Button2Click + end + object MessageTransceiver1: TMessageTransceiver + Host = '127.0.0.1' + Port = 2960 + OnMessageReceived = MessageTransceiver1MessageReceived + Left = 112 + Top = 80 + end +end diff --git a/delphi/test/simple_text_client/Unit1.pas b/delphi/test/simple_text_client/Unit1.pas new file mode 100644 index 00000000..febf28fa --- /dev/null +++ b/delphi/test/simple_text_client/Unit1.pas @@ -0,0 +1,114 @@ +{----------------------------------------------------------------------------- + Unit Name: Unit1 + Author: mathew emson + Date: 04-Jul-2005 + Purpose: + History: +-----------------------------------------------------------------------------} + +// Copyright (c) 2005, Matthew Emson +// All rights reserved. +// +// Redistribution and use in source and binary forms, +// with or without modification, are permitted provided +// that the following conditions are met: +// +// * Redistributions of source code must retain the +// above copyright notice, this list of conditions +// and the following disclaimer. +// * Redistributions in binary form must reproduce +// the above copyright notice, this list of +// conditions and the following disclaimer in the +// documentation and/or other materials provided +// with the distribution. +// * Neither the name of "Matthew Emson", current or +// past employer of "Matthew Emson" nor the names +// of any contributors may be used to endorse or +// promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS +// AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +unit Unit1; + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, + + MessageTransceiver, Message, ExtCtrls, StdCtrls, ScktComp; + +type + TForm1 = class(TForm) + Button1: TButton; + Edit1: TEdit; + Memo1: TMemo; + MessageTransceiver1: TMessageTransceiver; + Button2: TButton; + procedure Button1Click(Sender: TObject); + procedure MessageTransceiver1MessageReceived(msgRef: IPortableMessage); + procedure Button2Click(Sender: TObject); + private + { Private declarations } + public + mt: TMessageTransceiver; + end; + +const + TEXT_MESSAGE = 1886681204; + +var + Form1: TForm1; + +implementation + +{$R *.DFM} + +procedure TForm1.Button1Click(Sender: TObject); +var + msg: IPortableMessage; +begin + msg := TPortableMessage.Create(TEXT_MESSAGE); //(PR_COMMAND_TEXT_STRINGS); + msg.AddString('tl', edit1.text); + MessageTransceiver1.SendMessageToSessions(msg); +end; + +procedure TForm1.MessageTransceiver1MessageReceived(msgRef: IPortableMessage); +begin + case msgRef.what of + TEXT_MESSAGE: + memo1.lines.add(msgRef.findstring('tl')); + + MTT_EVENT_CONNECTED: + memo1.lines.add('Connected to server'); + + MTT_EVENT_DISCONNECTED: + memo1.lines.add('Server has disconnected'); + + else + memo1.lines.add( + #13#10#13#10'----------'#13#10''#13#10#13#10+ + msgRef.toString + #13#10'----------'); + end; +end; + +procedure TForm1.Button2Click(Sender: TObject); +begin + MessageTransceiver1.connect; +end; + +end. diff --git a/dev-c++/README.txt b/dev-c++/README.txt new file mode 100644 index 00000000..d8bd5e7b --- /dev/null +++ b/dev-c++/README.txt @@ -0,0 +1,17 @@ +In this directory you will find Dev-C++ 5 beta (4.9.50) project files, +as contributed by Marcin "Shard" Konicki. + +(based on VC++ files made by Vitaliy Mikitchenko (aka "VitViper")) + +To compile muscle, do the following: + +1. Open the "libmuscle.dev" project file in dev-c++ + +2. Now press "Compile" button to build the library. + +3. This will create a "libmuscle.a" static library in the same folder + as the Dev-C++ projects ("dev-c++" folder). + +4. Now open the "muscled.dev" file and press "Compile" button. + This will create a "muscled.exe" file in the "Dev-C++" directory. + Now you can just double-click it to run the server :). diff --git a/dev-c++/libmuscle.dev b/dev-c++/libmuscle.dev new file mode 100644 index 00000000..8c9fc7fe --- /dev/null +++ b/dev-c++/libmuscle.dev @@ -0,0 +1,482 @@ +[Project] +FileName=libmuscle.dev +Name=muscle +Type=2 +Ver=1 +Compiler=-D__GNUWIN32__ -DWIN32 -DNDEBUG -D_MBCS -D_LIB +Includes=..\\;..\..\\;..\\regex\regex +Linker= +Libs= +Resources= +UnitCount=75 +ObjFiles= +PrivateResource= +ResourceIncludes= +MakeIncludes= +IsCpp=0 +Icon= +ExeOutput= +ObjectOutput= +Folders= +Order= +Focused=-1 + +[Unit1] +FileName=..\iogateway\AbstractMessageIOGateway.cpp +Open=0 +Folder= +Top=0 + +[Unit2] +FileName=..\reflector\AbstractReflectSession.cpp +Open=0 +Folder= +Top=0 + +[Unit3] +FileName=..\system\AcceptSocketsThread.cpp +Open=0 +Folder= +Top=0 + +[Unit4] +FileName=..\reflector\DumbReflectSession.cpp +Open=0 +Folder= +Top=0 + +[Unit5] +FileName=..\reflector\FilterSessionFactory.cpp +Open=0 +Folder= +Top=0 + +[Unit6] +FileName=..\system\GlobalMemoryAllocator.cpp +Open=0 +Folder= +Top=0 + +[Unit7] +FileName=..\util\MemoryAllocator.cpp +Open=0 +Folder= +Top=0 + +[Unit8] +FileName=..\message\Message.cpp +Open=0 +Folder= +Top=0 + +[Unit9] +FileName=..\iogateway\MessageIOGateway.cpp +Open=0 +Folder= +Top=0 + +[Unit10] +FileName=..\system\MessageTransceiverThread.cpp +Open=0 +Folder= +Top=0 + +[Unit11] +FileName=..\util\MiscUtilityFunctions.cpp +Open=0 +Folder= +Top=0 + +[Unit12] +FileName=..\util\NetworkUtilityFunctions.cpp +Open=0 +Folder= +Top=0 + +[Unit13] +FileName=..\regex\PathMatcher.cpp +Open=0 +Folder= +Top=0 + +[Unit14] +FileName=..\iogateway\PlainTextMessageIOGateway.cpp +Open=0 +Folder= +Top=0 + +[Unit15] +FileName=..\util\PulseNode.cpp +Open=0 +Folder= +Top=0 + +[Unit16] +FileName=..\reflector\RateLimitSessionIOPolicy.cpp +Open=0 +Folder= +Top=0 + +[Unit17] +FileName=..\reflector\ReflectServer.cpp +Open=0 +Folder= +Top=0 + +[Unit18] +FileName=..\regex\regex\regcomp.c +Open=0 +Folder= +Top=0 +CursorCol=1 +CursorRow=86 +TopLine=70 + +[Unit19] +FileName=..\regex\regex\regerror.c +Open=0 +Folder= +Top=0 + +[Unit20] +FileName=..\regex\regex\regexec.c +Open=0 +Folder= +Top=0 + +[Unit21] +FileName=..\regex\regex\regfree.c +Open=0 +Folder= +Top=0 + +[Unit22] +FileName=..\reflector\ServerComponent.cpp +Open=0 +Folder= +Top=0 + +[Unit23] +FileName=..\system\SetupSystem.cpp +Open=0 +Folder= +Top=0 + +[Unit24] +FileName=..\reflector\StorageReflectSession.cpp +Open=0 +Folder= +Top=0 + +[Unit25] +FileName=..\util\String.cpp +Open=0 +Folder= +Top=0 + +[Unit26] +FileName=..\regex\StringMatcher.cpp +Open=0 +Folder= +Top=0 + +[Unit27] +FileName=..\syslog\SysLog.cpp +Open=0 +Folder= +Top=0 + +[Unit28] +FileName=..\system\Thread.cpp +Open=0 +Folder= +Top=0 + +[Unit29] +FileName=..\iogateway\AbstractMessageIOGateway.h +Open=0 +Folder= +Top=0 + +[Unit30] +FileName=..\reflector\AbstractReflectSession.h +Open=0 +Folder= +Top=0 + +[Unit31] +FileName=..\reflector\AbstractSessionIOPolicy.h +Open=0 +Folder= +Top=0 + +[Unit32] +FileName=..\system\AcceptSocketsThread.h +Open=0 +Folder= +Top=0 + +[Unit33] +FileName=..\system\AtomicCounter.h +Open=0 +Folder= +Top=0 + +[Unit34] +FileName=..\dataio\DataIO.h +Open=0 +Folder= +Top=0 + +[Unit35] +FileName=..\reflector\DumbReflectSession.h +Open=0 +Folder= +Top=0 + +[Unit36] +FileName=..\dataio\FileDataIO.h +Open=0 +Folder= +Top=0 + +[Unit37] +FileName=..\reflector\FilterSessionFactory.h +Open=0 +Folder= +Top=0 + +[Unit38] +FileName=..\support\Flattenable.h +Open=0 +Folder= +Top=0 + +[Unit39] +FileName=..\system\GlobalMemoryAllocator.h +Open=0 +Folder= +Top=0 + +[Unit40] +FileName=..\util\Hashtable.h +Open=0 +Folder= +Top=0 + +[Unit41] +FileName=..\syslog\LogCallback.h +Open=0 +Folder= +Top=0 + +[Unit42] +FileName=..\util\MemoryAllocator.h +Open=0 +Folder= +Top=0 + +[Unit43] +FileName=..\dataio\MemoryBufferDataIO.h +Open=0 +Folder= +Top=0 + +[Unit44] +FileName=..\message\Message.h +Open=0 +Folder= +Top=0 + +[Unit45] +FileName=..\iogateway\MessageIOGateway.h +Open=0 +Folder= +Top=0 + +[Unit46] +FileName=..\system\MessageTransceiverThread.h +Open=0 +Folder= +Top=0 + +[Unit47] +FileName=..\util\MiscUtilityFunctions.h +Open=0 +Folder= +Top=0 + +[Unit48] +FileName=..\support\MuscleSupport.h +Open=0 +Folder= +Top=0 +CursorCol=15 +CursorRow=34 +TopLine=27 + +[Unit49] +FileName=..\system\Mutex.h +Open=0 +Folder= +Top=0 + +[Unit50] +FileName=..\util\NetworkUtilityFunctions.h +Open=0 +Folder= +Top=0 + +[Unit51] +FileName=..\dataio\NullDataIO.h +Open=0 +Folder= +Top=0 + +[Unit52] +FileName=..\util\ObjectPool.h +Open=0 +Folder= +Top=0 + +[Unit53] +FileName=..\regex\PathMatcher.h +Open=0 +Folder= +Top=0 + +[Unit54] +FileName=..\iogateway\PlainTextMessageIOGateway.h +Open=0 +Folder= +Top=0 + +[Unit55] +FileName=..\support\Point.h +Open=0 +Folder= +Top=0 + +[Unit56] +FileName=..\util\PulseNode.h +Open=0 +Folder= +Top=0 + +[Unit57] +FileName=..\util\Queue.h +Open=0 +Folder= +Top=0 + +[Unit58] +FileName=..\reflector\RateLimitSessionIOPolicy.h +Open=0 +Folder= +Top=0 + +[Unit59] +FileName=..\support\Rect.h +Open=0 +Folder= +Top=0 + +[Unit60] +FileName=..\util\RefCount.h +Open=0 +Folder= +Top=0 + +[Unit61] +FileName=..\reflector\ReflectServer.h +Open=0 +Folder= +Top=0 + +[Unit62] +FileName=..\reflector\ServerComponent.h +Open=0 +Folder= +Top=0 + +[Unit63] +FileName=..\system\SetupSystem.h +Open=0 +Folder= +Top=0 + +[Unit64] +FileName=..\iogateway\SignalMessageIOGateway.h +Open=0 +Folder= +Top=0 + +[Unit65] +FileName=..\util\SocketHolder.h +Open=0 +Folder= +Top=0 + +[Unit66] +FileName=..\reflector\StorageReflectConstants.h +Open=0 +Folder= +Top=0 + +[Unit67] +FileName=..\reflector\StorageReflectSession.h +Open=0 +Folder= +Top=0 + +[Unit68] +FileName=..\util\String.h +Open=0 +Folder= +Top=0 + +[Unit69] +FileName=..\regex\StringMatcher.h +Open=0 +Folder= +Top=0 + +[Unit70] +FileName=..\util\StringTokenizer.h +Open=0 +Folder= +Top=0 + +[Unit71] +FileName=..\syslog\SysLog.h +Open=0 +Folder= +Top=0 + +[Unit72] +FileName=..\dataio\TCPSocketDataIO.h +Open=0 +Folder= +Top=0 + +[Unit73] +FileName=..\system\Thread.h +Open=0 +Folder= +Top=0 + +[Unit74] +FileName=..\util\TimeUtilityFunctions.h +Open=0 +Folder= +Top=0 + +[Unit75] +FileName=..\support\Tuple.h +Open=0 +Folder= +Top=0 + +[Views] +ProjectView=1 + diff --git a/dev-c++/muscled.dev b/dev-c++/muscled.dev new file mode 100644 index 00000000..87c9bdce --- /dev/null +++ b/dev-c++/muscled.dev @@ -0,0 +1,98 @@ +[Project] +FileName=muscled.dev +Name=muscled +Type=1 +Ver=1 +Compiler=-D__GNUWIN32__ -DWIN32 -DNDEBUG -D_CONSOLE -D_MBCS_@@_ +Includes=..\\;..\..\\ +Linker=-lmuscle -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -lws2_32_@@_ -lstdc++ -lwinmm_@@_ -liphlpapi_@@_ +Libs=./ +UnitCount=4 +ObjFiles= +PrivateResource= +ResourceIncludes= +MakeIncludes= +IsCpp=0 +Icon= +ExeOutput= +ObjectOutput= +Folders= +CppCompiler= +OverrideOutput=0 +OverrideOutputName=muscled.exe +HostApplication= +CommandLine= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=000000000000000000 + +[Unit1] +FileName=..\server\muscled.cpp +Folder= +CompileCpp=0 +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Views] +ProjectView=1 + +[VersionInfo] +Major=0 +Minor=1 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription=Developed using the Dev-C++ IDE +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=muscled.exe +ProductName=muscled +ProductVersion=0.1 +AutoIncBuildNr=0 + +[Unit2] +FileName=..\regex\QueryFilter.cpp +CompileCpp=0 +Folder=muscled +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\util\ByteBuffer.cpp +CompileCpp=0 +Folder=muscled +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\Borland\BCC55\Lib\PSDK\winmm.lib +Folder=muscled +Compile=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\Borland\BCC55\Lib\PSDK\iphlpapi.lib +Folder=muscled +Compile=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/html/Beginners Guide.html b/html/Beginners Guide.html new file mode 100644 index 00000000..1f194eda --- /dev/null +++ b/html/Beginners Guide.html @@ -0,0 +1,677 @@ + + +

MUSCLE Overview and Beginner's Guide

+

v6.05 / Jeremy Friesner / Meyer Sound Laboratories Inc (jaf@meyersound.com) 6/27/2014

+ Click here for DOxygen class API documentation + + +

+ + +

+ +

Introduction

+ +The MUSCLE system is a robust, somewhat scalable, cross-platform client-server solution for dynamic distributed applications for Linux, BSD, Windows, MacOS/X, and other operating systems. It allows (n) client programs (each of which may be running on a separate computer and/or under a different OS) to communicate with each other in a many-to-many message-passing style. It employs a central server to which client programs may connect or disconnect at any time (This design is similar to other client-server systems such as Quake servers, IRC servers, and Napster servers, but more general in application). In addition to the client-server system, MUSCLE contains classes to support peer-to-peer message streaming connections, as well as some handy miscellaneous utility classes. As distributed, the server side of the software is ready to compile and run, but to do much with it you'll want to write your own client software. Example client software can be found in the "test" subdirectory.

+ +This document assumes you are familiar with C++ programming programming. However it should be understandable even if you aren't. + + +

Feature List

+
    +
  1. Powerful: Provides a centralized "message crossbar server" for up to (n) simultaneous client programs to connect to.
  2. +
  3. Easy: By default, all communication is done over TCP, by sending flattened Message objects through MessageIOGateways. UDP can also be used if desired, with a little extra work. Under most popular programming environments, it's even easier--see item 8.
  4. +
  5. Efficient: Messages sent to the server may be broadcast to all connected clients, or multicast intelligently using pattern-matching and/or boolean filtering logic.
  6. +
  7. Portable: All code (except for some platform-specific convenience classes in the support folders) uses only standard C++ and BSD socket calls, and should compile and run under any modern OS with minimal changes. All code has been compiled and tested on 32-bit and 64-bit platforms including Linux, MacOS/X, Windows, and BSD.
  8. +
  9. Flexible: Clients may store data (in the form of Messages) in the server's RAM, using a filesystem-like node hierarchy. Other clients may "subscribe" to this server-side data, and the server will then automatically send them updates to the data as it is changed. Subscriptions are also specified via wildcarding, for maximum flexibility. Server-side filtering of results using boolean tests of their content is also supported.
  10. +
  11. Open: All source code is licensed under the BSD Open Source License, and is freely distributable and usable for any purpose. The source code contains many useful classes, including platform-neutral analogs to Be's BMessage, BDataIO, BFlattenable, and BString classes. In addition, the archive also includes handy double-ended-queue, Hashtable, Reference-counting, and "I/O gateway" classes.
  12. +
  13. Customizable: All server-side session handlers are implemented by subclassing a standard interface (AbstractReflectSession) so that they can be easily augmented or replaced with custom logic. Message serialization and low-level I/O is handled in a similar fashion, making it easy to replace the byte-stream format or transport mechanism with your own.
  14. +
  15. Scalable: A vanilla MUSCLE server can handle up to 1024 simultaneous TCP connections (or more than that if you enable -DMUSCLE_USE_POLL or -DMUSCLE_USE_KQUEUE or -DMUSCLE_USE_EPOLL). Additional simultaneous connections can be supported in custom MUSCLE servers by adding multiple threads or processes
  16. +
  17. Convenient: For selected environments, including Windows, BeOS, Qt, Java, Delphi, and Python, special utility classes are provided to hide the synchronous TCP messaging interface behind an asynchronous send-and-receive-messages API that's easier to deal with.
  18. +
+

+ +

Is this software appropriate for use in my project?

+ +The space of possible networking applications is large, and MUSCLE may or may not be appropriate for any given networking application. Here are several questions you should ask yourself when deciding whether or not to use the MUSCLE system or APIs.

+ +

  1. Must my application be compatible with pre-existing Internet RFCs or other programs or data formats?

    +If yes, MUSCLE may not be for you. MUSCLE defines its own byte-stream formats and messaging protocol, and is not generally compatible with other software protocols (such as IRC or FTP). If your pre-existing protocol follows a "message-stream-over-TCP-stream" design pattern, you can customize MUSCLE (by defining your own subclass of AbstractMessageIOGateway) to make it use your protocol; if not, you're probably better off coding to lower level networking APIs.

  2. +

    +

  3. Is TCP stream communication fast enough for my app? Do I need to use lower level protocols such as UDP or ICMP?

    +By default, MUSCLE does its data transfer by serializing Messages over TCP streams. If your application is a particularly high-performance one (such as video streaming or action gaming), MUSCLE over TCP may not be able to provide you with the minimal latency you need. In this case, you might use MUSCLE TCP streams for your control data only, and use UDP for your high-bandwidth/low-latency packets. MUSCLE supports this, although you'll need to customize the server somewhat to enable it. I've used this pattern (TCP + UDP) in audio-over-Internet programs before and it works well.

    +In addition, you should be aware of the CPU and memory overhead added by MUSCLE to your communications. While MUSCLE has been designed for efficiency, and will not make unreasonable demands on systems that run it, it is necessarily somewhat less efficient that straight byte-stream TCP programming. Specifically:

    +

      +
    1. Its use of Messages means that there will be several dynamic allocations and deallocations, and an extra data copy, for each message sent and received. (note: ObjectPools are used to minimize the former)
    2. +
    3. It uses arbitrary-length message queues to avoid ever having to "block" your application's threads. While this will keep your application reliably responsive to the user, it can potentially use a lot of memory if you are producing messages faster than the network connection can send them.
    4. +
    5. If you use the MessageTransceiverThread class there will be one extra thread used for each MUSCLE TCP connection.
    6. +
    +
  4. +

    +

  5. Should my application use the muscled server, or just point-to-point messaging connections? +

    +There are two common ways to use the MUSCLE package: you can have each client connect to a muscled server running on a central server system, and use it to communicate with each other indirectly... or you can have clients connect to each other directly, without using a central server. Each style of communication is useful in the right context, but it is important to choose the one that best fits what your app is going to do. Using the muscled in a client/server pattern is great because it solves several problems for you: it provides a way of communicating with other client computers without first needing to know their host addresses (etc), it gives you intelligent "broadcast" and "multicast" capabilities, and it provides a centralized area to maintain "shared state information" amongst all clients. On the down side, because all data must travel first to the central server, and from there on to the other client(s), message passing through the server is only half as fast (on average) as a direct connection to another client. Of course, to get the best of both worlds, you can use a hybrid system: each client connects to the server, and uses the server to find out the host addresses of the other clients; after that, it can connect to the other clients directly whenever it wants.

  6. +

    +

  7. Which parts of MUSCLE should I use? Which parts should I extend? Which parts should I ignore? +

    +The MUSCLE package consists of dozens of classes, most of which are needed by the MUSCLE server, some of which are needed by MUSCLE clients, and some of which may be useful to you in their own right, as generic utility classes. For most applications, the standard MUSCLE server will be adequate: you can just compile it and run it, and concentrate solely on the client side of your app. For some specialized apps, you may want to make your own "custom" server--you can do this easily by creating your own subclass of AbstractReflectSession. Of course, if you do this you won't be able to use any of the "general purpose" muscled servers that may be available... +

  8. +
+

+ +

The Multi-Threaded messaging API

+ +MUSCLE supports a multi-threaded messaging model as well as the single-threaded model (described elsewhere in this document). In the multi-threaded model, a separate MUSCLE thread is started up to handle networking chores for you. The advantage of doing it this way is that your GUI will never lock up due to networking activity, since all networking activity happens asynchronously. Your GUI merely sends commands to the MUSCLE networking thread, and receives Messages back which contain information received from the network. +

+The multi-threaded messaging API for MUSCLE is represented mainly by the MessageTransceiverThread class. This class manages the interaction between you and its internal thread, which does all the networking operations. If you are using Qt, Windows, BeOS, or AtheOS, you are in luck--there are API-specific subclasses of MessageTransceiverThread included with MUSCLE to make things easier for you. If not, you can still use MessageTransceiverThread, but you will need to do a little extra work to integrate it with your native threading model (how to do this is not covered here -- email me if you need help with this). +

+There are five things that you'll need to do in a typical client: Set up the thread, connect to the server, send messages, receive messages, and disconnect. Here's how to do them: + +

  1. Setting up the MessageTransceiverThread object.

    +First thing you will need to do is create a (AMessageTransceiverThread/BMessageTransceiverThread/QMessageTransceiverThread) object (either on the stack or the heap). The AMessageTransceiverThread and BMessageTransceiverThread class constructors take a Messenger object; pass in a Messenger that points to the Looper object that will be handling network interactions. For Qt, it's even easier... just connect() the various signals of the QMessageTransceiverThread object to the various slots of your control object.

  2. + +
  3. Connecting to the server.

    +Once you have your MessageTransceiverThread object, you can tell it that you want to connect out to the server by calling AddNewConnectSession() on it with the server's hostname and port number. Then you call StartInternalThread() on it to start the networking thread going. Both AddNewConnectSession() and StartInternalThread() will return immediately, but when the background TCP thread connects to the server (or fails to do so) it will send an event-message to your target Messenger to notify you (in Qt, it will just emit the appropriate signal).

  4. + +
  5. Sending messages.

    +To send a message to the server, just call the MessageTransceiverThread's SendMessageToSessions() method. This method will return immediately, but the message you specify will be placed in an outbound-message queue for sending as soon as possible. Messages are passed in using a MessageRef reference object, to avoid needless data-copying. For example: +

    +

    MessageRef newMsg = GetMessageFromPool('HELO');
    +if (newMsg())
    +{
    +   newMsg()->AddString("testing", "please");
    +   if (myTransceiver.SendMessageToSessions(newMsg) != B_NO_ERROR) printf("Couldn't send message!\n");
    +}
    +else WARN_OUT_OF_MEMORY;
    +
  6. + +
  7. Receiving messages

    +If you're using Qt, this is easy--whenever a Message arrives from the server, the MessageReceived() signal will be emitted and your connected object can act on it. For AtheOS and BeOS, the process is slightly more involved: Whenever a new Message arrives from the server, a MUSCLE_THREAD_SIGNAL BMessage will be sent to you via the Messenger you specified in the MessageTransceiverThread constructor. When you receive such a message, your Looper should do something like this:

    + +

    MessageRef msg;
    +uint32 code;
    +while(myTransceiver.GetNextEventFromInternalThread(code, &msg) >= 0)
    +{
    +   switch(code)
    +   {
    +      case MTT_EVENT_INCOMING_MESSAGE:
    +      {
    +         Message * pMsg = msg();  // Get access to the reference's held Message object.
    +         HandleMessage(pMsg);     // Do whatever you gotta do
    +         /* do NOT delete (pMsg).  It will be deleted for you. */
    +      }
    +      break;
    +
    +      case MTT_EVENT_SESSION_CONNECTED:
    +         printf("Connection to server was successful!\n");
    +      break;
    +
    +      case MTT_EVENT_SESSION_DISCONNECTED:
    +         printf("Disconnected from server, or connection failed!\n");
    +      break;
    +    }
    +};

    +

  8. + +
  9. Disconnecting

    +When you've had enough of chatting with the server, you can end your session by calling ShutdownInternalThread() on the MessageTransceiverThread object, and then deleting it. Or if you wish to reuse the MessageTransceiverThread again, call Reset() on it, and it's ready to use again, as if you had just created it. +

    +

  10. + +
+ +

The Single-Threaded messaging API (available on all platforms)

+

+For code that needs to run on platforms other than Qt/Windows/BeOS/AtheOS (or even for Qt/Windows/BeOS/AtheOS code where you don't want to spawn an extra thread), you can use the single-threaded messaging API, as defined by the DataIO and MessageIOGateway classes. These classes allow you to decouple your TCP data transfer calls from your message processing calls, and yet still keep the same general message-queue semantics that we know and love.

+To create a connection to the MUSCLE server, you would first make a TCP connection using standard BSD sockets calls (see portablereflectclient.cpp for an example of this). Once you have a connected socket, you would use it to create a TCPSocketDataIO, which you would use to create a MessageIOGateway object:

+MessageIOGateway gw;  // create the gateway
+gw.SetDataIO(DataIORef(new TCPSocketDataIO(mysocketfd, false)));  // tell the gateway to use our TCP socket
+
+

+This gateway allows you to enqueue outgoing Message or dequeue incoming Messages at any time by calling AddOutgoingMessage() or GetNextIncomingMessage(), respectively. These methods are guaranteed never to block. Like the MessageTransceiverThread, the MessageIOGateway uses MessageRef objects to handle the freeing of Messages when they are no longer in use.

+

+To actually send and receive TCP data, you need to call DoOutput() and DoInput(), respectively. These methods will send/receive as many bytes of TCP data as they can (without blocking), and then return B_NO_ERROR (unless the connection has been cut, in which case they will return B_ERROR). Because these methods never block (unless your TCPSocketDataIO is set to blocking I/O mode, which in general it shouldn't be), you will need to employ a SocketMultiplexer object to keep your event loop from using 100% CPU time while waiting to send or receive data. Here is an example event loop that does this:

+ +

ConstSocketRef connectionSocket = Connect("servername.serverdomain.com", 2960);  // get a fresh TCP socket connection
+if (connectionSocket())
+{
+   MessageIOGateway gw;
+   gw.SetDataIO(DataIORef(new TCPSocketDataIO(connectionSocket, false)));
+
+   SocketMultiplexer multiplexer;
+   QueueGatewayMessageReceiver inputQueue;
+   while(1)
+   {
+      int socketFD = connectionSocket.GetFileDescriptor();
+      multiplexer.RegisterSocketForReadReady(socketFD);  // tell WaitForEvents() to return when there are incoming bytes to read
+      if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(socketFD);  // ditto for outgoing buffer space to write to
+
+      if (multiplexer.WaitForEvents() < 0)  // We'll block here until there is some more I/O we can do
+      {
+         perror("WaitForEvents() failed");
+         break;
+      }
+
+      /* Do as much TCP I/O as possible without blocking */
+      bool writeError = ((multiplexer.IsSocketReadyForWrite(socketFD))&&(gw.DoOutput()         < 0));
+      bool readError  = ((multiplexer.IsSocketReadyForRead(socketFD))&&(gw.DoInput(inputQueue) < 0));
+      if ((readError)||(writeError))
+      {
+         printf("%s error -- socket connection closed?  Aborting!\n", readError?"Read":"Write");
+         break;
+      }
+
+      /* handle any Messages that were made available by the gw.DoInput() call */
+      MessageRef msg;
+      while(inputQueue.RemoveHead(msg) == B_NO_ERROR)
+      {
+         printf("Received incoming TCP Message:\n");
+         if (msg()) msg()->PrintToStream();  // handle message here
+      }
+   }
+   printf("Connection was closed!\n");
+}
+else printf("Couldn't connect!\n");

+ +Alternatively, you can set the blocking-I/O parameter in the TCPSocketDataIO object to true, and use blocking I/O instead. If you do that, then you don't have to deal with the complexities of socket multiplexing... but then it becomes difficult to coordinate sending and receiving at the same time (i.e. how can you call DoOutput() if you are blocked waiting for data in DoInput()?) +

+ +

Message semantics for client/server connections

+

+Regardless of whether you are sending and receiving messages with a MessageTransceiverThread with direct calls to a IOGateway, the result looks the same to the program at the other end of the TCP connection: It always sees a just a sequence of flattened Message objects. How that program acts on those messages is of course up to it. However, the server included in this archive does have some minimal standard semantics that govern how it handles the messages it receives. The following sections describe those semantics. +

+

DumbReflectSession semantics

+

+If you are connected to a MUSCLE server that was compiled to use the DumbReflectSession class to handle its connections, then the semantics are extremely simple: Any Message you send to the server will be sent on, verbatim, to every other connected client. (Sort of a high-level version of Ethernet broadcast packets). This may be useful in some situations, but for applications where bandwidth is an issue you'll probably want to use the "regular" server with StorageReflectSession semantics. +

+

StorageReflectSession semantics

+

+The StorageReflectSession-based server (a.k.a. "muscled") is much more powerful than the DumbReflectSession server, for two reasons: First, it makes intelligent decisions about how to route client messages, so that your messages only go to the clients you specify. The second reason is because this server allows you to store messages (semi-permanently; they are retained for as long as you remain connected) in the server's RAM, where other clients can access them without having to communicate with you directly. If you imagine a situation where the server is running on 100Mbps Ethernet, and the clients are connecting through 28.8 modems, then you can see how this can be useful. +

+The StorageReflectSession server maintains a single tree data structure very much like the filesystem of your average desktop computer. Although this data structure exists only in memory (nothing is ever written to the server's disk), it shares many things in common with a multi-user file system. Each node in the tree has an ASCII label that uniquely identifies it from its siblings, and also contains a single Message object, which client machines may get or set (with certain restrictions). The root node of the tree contains no data, and is always present. Nodes underneath the root, on the other hand, may appear and dissappear as clients connect and disconnect. The first level of nodes beneath the root are automatically created whenever a client connects to the server, and are named after the host IP address of the client machine that connected. (For example, "192.168.0.150"). The second level of nodes are also automatically created, and these nodes are given unique names that the server makes up arbitrarily. (This second level is necessary to disambiguate multiple connections coming from the same host machine) The number of level 2 nodes in the tree is always the same as the number of currently active connections ("sessions") on the server. +

+

       ___________'/'_________               (level 0 -- "root")
+      |                      |
+  192.168.0.150         132.239.50.13        (level 1 -- host IP addresses)
+   |         |               |
+3217617   3217618         1829023            (level 2 -- unique session IDs)
+   |                         |
+SomeData                  MoreData           (level 3 -- user data nodes)
+                          |      |
+                       RedFish BlueFish      (level 4)

+ +Levels 1 and 2 of the tree reflect two simultaneous sessions connected from 192.168.0.150, and one connection from 132.239.50.13. In levels 3 and 4, we can see that the sessions have created some nodes of their own. These "user-created" nodes can be named anything you want, although no two siblings can have the same name. Each client may create data nodes only underneath its own "home directory" node in level 2--you aren't allowed to write into the "home directories" of other sessions. However, any client may read the contents of any node in the system.

+As in any good filesystem (e.g. UNIX's), nodes can be identified uniquely by a node-path. A node-path is simply the concatenation of all node names from the root to the node, separated by '/' characters. So, the tree in the above example contains the following node-paths: + +

/
+/192.168.0.150
+/192.168.0.150/3217617
+/192.168.0.150/3217617/SomeData
+/192.168.0.150/3217618
+/132.239.50.13
+/132.239.50.13/1829023
+/132.239.50.13/1829023/MoreData
+/132.239.50.13/1829023/MoreData/RedFish
+/132.239.50.13/1829023/MoreData/BlueFish

+ +

Creating or modifying nodes in your subtree (a.k.a. uploading data)

+ +One thing most clients will want to do is create one or more new nodes in their subtree on the server. Since each node contains a Message, creating a node is the same thing as uploading data to the server. To do this, you send the server a PR_COMMAND_SETDATA message. A single PR_COMMAND_SETDATA message can set any number of new nodes. For each node you wish to set, simply AddMessage() the value you wish to set it to, with a field name equal to the path of the node relative to your "home directory". For example, here's what the client from 132.239.50.13 could have done to create the MoreData, RedFish, and BlueFish nodes under his home directory:

+ +

Message redFishMessage('RedF');   // these messages could contain data
+Message blueFishMessage('BluF');  // you wish to upload to the server
+
+MessageRef msg = GetMessageFromPool(PR_COMMAND_SETDATA);
+if (msg())
+{
+   msg()->AddMessage("MoreData/RedFish", redFishMessage);
+   msg()->AddMessage("MoreData/BlueFish", blueFishMessage);
+   myMessageTranceiver->SendMessageToSessions(msg);
+}
+else printf("Out of memory?!\n");
+

+ +Note that the "MoreData" node did not need to be explicitely created in this message; the server will see that it doesn't exist and create it before adding RedFish and BlueFish to the tree. (Nodes created in this way have empty Messages associated with them). If 132.239.50.13 later wants to change the data in any of these nodes, he can just send another PR_COMMAND_SETDATA message with the same field names, but different messages.

+ +

Accessing nodes in the tree (a.k.a. downloading data)

+ +If you want to find out the current state of one or more nodes on the server, you should send a PR_COMMAND_GETDATA message. In this PR_COMMAND_GETDATA message, you should add one or more strings to the PR_NAME_KEYS field. Each of these strings may specify the full path-name of a node in the tree that you are interested in. For example: + +

MessageRef msg = GetMessageFromPool(PR_COMMAND_GETDATA);
+if (msg())
+{
+   msg()->AddString(PR_NAME_KEYS, "/192.168.0.150/3217617/SomeData");
+   msg()->AddString(PR_NAME_KEYS, "/132.239.50.13/1829023/MoreData/RedFish");
+   msg()->AddString(PR_NAME_KEYS, "/132.239.50.13/1829023");
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +Soon after you sent this message, the server would respond with a PR_RESULT_DATAITEMS message. This message would contain the values you asked for. Each value is stored in a separate message field, with the field's name being the full node-path of the node, and the field's value being the Message that was stored with that node on the server. So for the above request, the result would be: + +

Message: what = PR_RESULT_DATAITEMS  numFields = 3
+  field 0: name = "/192.168.0.150/3217617/SomeData" value = (a Message)
+  field 1: name = "/132.239.50.13/1829023/MoreData/RedFish" value = (a Message)
+  field 2: name = "/132.239.50.13/1829023" value = (an empty Message)

+ +

Pattern matching in node paths
+ +Of course, not all the nodes you specified may actually exist on the server; if the server cannot find a node that you requested it simply won't add it to the PR_RESULT_DATAITEMS message it sends you. Thus it's possible to get back an empty PR_RESULT_DATAITEMS message if you're unlucky.

+ +The above method of retrieving data is okay as far as it goes, but it only works if you know in advance the node-path(s) of the data you want. But in the real world, you won't usually know e.g. the host addresses of other connected clients. Fortunately, the MUSCLE server understands wildcard patterns in the node-paths you send it. Wildcarding allows you to specify a pattern to watch for rather than a particular unique string. A detailed discussion of pattern matching is outside the scope of this document, but if you've used any UNIX-style shell much at all you probably have a good idea how they work. For example, say we wanted to know the host address of every machine connected to the server:

+ +

MessageRef msg = GetMessageFromPool(PR_COMMAND_GETDATA);
+if (msg())
+{
+   msg()->AddString(PR_NAME_KEYS, "/*");
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +The "/*" pattern in the PR_NAME_KEYS field above matches both "/192.168.0.150" and "/132.239.50.13" in the tree, so we would get back the following: + +

Message: what = PR_RESULT_DATAITEMS  numFields = 2
+  field 0: name = "/192.168.0.150" value = (an empty Message)
+  field 1: name = "/132.239.50.13" value = (an empty Message)

+ +Or, perhaps we want to know about every node in every session's home directory that starts with the letters "Som". Then we could do: + +

msg()->AddString(PR_NAME_KEYS, "/*/*/Som*");

+ +And so on. And of course, you are still able to add multiple PR_NAME_KEYS values to a single PR_COMMAND_GETDATA message; the PR_RESULT_DATAITEMS message you get back will contain data for any node that matches at least one of your wildcard patterns.

+ +One more detail: Since patterns that start with "/*/*" turn out to be used a lot, they can be made implicit in your path requests. Specifically, any PR_NAME_KEYS value that does not start with a leading '/' character is taken to have an implicit '/*/*/' prefix. So doing + +

msg()->AddString(PR_NAME_KEYS, "Gopher");

+is semantically equivalent to doing + +

msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");

+ +

Deleting nodes in your subtree

+ +To remove nodes in your subtree, send a PR_COMMAND_REMOVEDATA message to the server. Add to this message one or more node-paths (relative your session directory) indicating the node(s) to remove. These paths may have wildcards in them. For example, if 132.239.50.13 wanted to remove all nodes from his subtree, he could do this:

+ +

MessageRef msg = GetMessageFromPool(PR_COMMAND_REMOVEDATA);
+if (msg())
+{
+   msg()->AddString(PR_NAME_KEYS, "MoreData/RedFish");
+   msg()->AddString(PR_NAME_KEYS, "MoreData/BlueFish");
+   msg()->AddString(PR_NAME_KEYS, "MoreData");
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +or this: + +

msg()->AddString(PR_NAME_KEYS, "MoreData");  /* Removing a node implicitely removes its children */

+ +or even just this: + +

msg()->AddString(PR_NAME_KEYS, "*");  /* wildcarding */

+ +You can only remove nodes within your own subtree. You can add as many PR_NAME_KEYS strings to your PR_COMMAND_REMOVEDATA message as you wish. + +

Sending messages to other clients

+ +Any message you send to the server whose 'what' value is not one of the PR_COMMAND_* constants is considered by the server to be a message meant to be forwarded to the other clients. But which ones? Again, the issue is decided by using pattern matching on node-paths. The server will examine your message for a PR_NAME_KEYS string field. If it finds one (or more) strings in this field, it will use these strings as node-paths; any other client whose has one or more nodes that match your node-path expressions will receive a copy of your message. For example: + +
MessageRef msg = GetMessageFromPool('HELO');
+if (msg())
+{
+   msg()->AddString(PR_NAME_KEYS, "/192.168.0.150/*");
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +would cause your 'HELO' message to be sent to all sessions connecting from 192.168.0.150. Or, more interestingly: + +

msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");

+ +Would cause your message to be sent to all sessions who have a node named "Gopher" in their home directory. This is very handy because it allows sessions to "advertise" for which types of message they want to receive: In the above example, everyone who was interested in your 'HELO' messages could signify that by putting a node named "Gopher" in their directory.

+ +Other examples of ways to address your messages: + +

msg()->AddString(PR_NAME_KEYS, "/*/*/J*")

+ +Will send your message to all clients who have a node in their home directory whose name begins with the letter 'J'.

+ +

msg()->AddString(PR_NAME_KEYS, "/*/*/J*/a*/F*")

+ +This (contrived) example would send your message only to clients who have something like "Jeremy/allen/Friesner" present...

+ +

msg()->AddString(PR_NAME_KEYS, "Gopher");

+ +This is equivalent to the "/*/*/Gopher" example used above; if no leading slash is present, then the "/*/*/" prefix is considered to be implied.

+ +

msg()->AddString(PR_NAME_KEYS, "Gopher");
+msg()->AddString(PR_NAME_KEYS, "Bunny");

+ +This message will go to clients who have node named either "Gopher" or "Bunny" in their home directory. Clients who have both "Gopher" AND "Bunny" will still only get one copy of this message.

+ +If your message does not have a PR_NAME_KEYS field, the server will check your client's parameter set for a string parameter named PR_NAME_KEYS. If this parameter is found, it will be used as a "default" setting for PR_NAME_KEYS. If a PR_NAME_KEYS parameter setting does not exist either, then the server will resort to its "dumb" behavior: broadcasting your message to all connected clients.

+ +

Subscriptions (a.k.a. automatic change notification triggers)

+ +Theoretically, getting and setting data nodes is all that is necessary for meaningful client-to-client data transfer to take place. In real life, however, it's not terribly useful on its own. After all, what good is it to download the data from a bunch of nodes if that data might be changed 50 milliseconds after it was sent to you? You'd end up having to issue another PR_COMMAND_GETDATA message every few seconds just to make sure you had the latest data. Now imagine fifty simultaneously connected clients, all doing that at once. No, that would never do.

+ +To deal with this problem, the StorageReflection Server allows your client to set "subscriptions". Each subscription is nothing more than the node-path of one or more nodes that your client is interested in. The path format and semantics of a subscription request are exactly the same as those in a PR_COMMAND_GETDATA message, but the way you compose them is quite different. Here is an example: + +

MessageRef msg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
+if (msg())
+{
+   msg()->AddBool("SUBSCRIBE:/*/*", true);
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +The above is a request to be notified whenever the state of a node whose path matches "/*/*" changes (which is actually the same as being notified whenever another session connects or disconnects--very handy for some applications). Note that the subscription path is part of the field's name, not the field's value. Note also that the field has been added as a boolean. That actually doesn't matter; you can add your subscribe request as any type of data you wish--the value won't even be looked at, it's only the field's name that is important.

+ +As soon as your PR_COMMAND_SETPARAMETERS message is received by the server, it will send back a PR_RESULT_DATAITEMS message containing values for all the nodes that matched your subscription path(s). In this respect, your subscription acts similarly to a PR_COMMAND_GETDATA message. But the difference is that the server keeps your subscription strings "on file", and afterwards, every time a node is created, changed, or deleted--and its node-path matches at least one of your subscription paths, the server will automatically send you another PR_COMMAND_DATAITEMS message containing the message(s) that have changed, and their newest values. Note that each PR_COMMAND_DATAITEMS message may have more than one changed node in it at a time (i.e. if someone else changes several nodes at a time).

+ +When the server wishes to notify you that a node matching one of your subscription paths has been deleted, it will do so by adding the node-path of the deceased node to the PR_NAME_REMOVED_DATAITEMS field of the PR_RESULT_DATAITEMS message it sends you. Again, there may be more than one PR_NAME_REMOVED_DATAITEMS value in a single PR_RESULT_DATAITEMS message. + +

Cancelling Subscriptions

+ +Cancelling a subscription is just the same as removing any other parameter from your parameter set; indeed subscriptions are just parameters whose names start with the magic prefix "SUBSCRIBE:". As such, see the next section on setting and removing parameters for how to do this. (One caveat: since parameters to remove are specified with pattern matching, you may need to escape any wildcard characters in your SUBSCRIBE: string to avoid removing additional parameters that you didn't intend to. There is a utility function in StringMatcher.h that you can use to do this if you like) + +

Setting parameters

+ +In addition to data-node storage, each client holds a set of name-value pairs called its parameter set. These parameters are used by the session to control certain session-specific policies. The names in this set may be any ASCII string (although only certain names are actually paid attention to by the server), and the values may be of any type that is allowed as a field in a Message. To set or modify a parameter for your session, just send a PR_COMMAND_SETPARAMETERS message to the server with the names and values included. For example: + +
MessageRef msg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
+if (msg())
+{
+   msg()->AddBool(PR_NAME_REFLECT_TO_SELF, true);  // enable wildcard matching on my own subdirectory
+   msg()->AddString(PR_NAME_KEYS, "/*/*/Gopher");  // set default message forwarding pattern
+   msg()->AddBool("SUBSCRIBE:/132.239.50.*", true);       // add a subscription to nodes matching "/132.239.50.*"
+   msg()->AddBool("SUBSCRIBE:*", true);            // add a subscription to nodes matching "/*/*/*"
+   msg()->AddInt32("Glorp", 666);                  // other parameters like this will be ignored
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +The fields included in your message will replace any like-named fields already existing in the parameter set. Any fields in the existing parameter set that aren't specified in your message will be left unaltered.

+ +

Getting the current parameter set

+ +If you wish to know the exact parameter set that your session is currently operating under, send a PR_COMMAND_GETPARAMETERS message to the server. This message need not contain any fields at all; only the 'what' code is looked at. When the server receives a PR_COMMAND_GETPARAMETERS message, it will respond by sending you back a PR_RESULT_PARAMETERS message that contains all the name->value pairs in your parameter set as fields.

+ +

Removing parameters

+ +To remove one or more parameters, send a PR_COMMAND_REMOVEPARAMETERS message. This message should contain a PR_NAME_KEYS field containing one or more strings that indicate which parameters to remove. For example: + +
MessageRef msg = GetMessageFromPool(PR_COMMAND_REMOVEPARAMETERS);
+if (msg())
+{
+   msg()->AddString(PR_NAME_KEYS, PR_NAME_REFLECT_TO_SELF);  // disable wildcard matching on my own subdirectory
+   msg()->AddString(PR_NAME_KEYS, "SUBSCRIBE:*");            // removes ALL subscriptions (compare with "SUBSCRIBE:\*" which would only remove one)
+   myMessageTranceiver->SendMessageToSessions(msg);
+}

+ +

Recognized parameter names

+ +Currently there are only a few parameters that whose values are acted upon by the StorageReflect Server. These are: + +
    +
  1. PR_NAME_KEYS
    + If present and set to a string value, this value is used as the default pattern to match for determining which sessions user messages (i.e. messages whose 'what' code is not one of the PR_COMMAND_* constants) should be routed to. See the section on "Sending Messages to Other Clients" for details. Defaults to unset (which is equivalent to "/*/*"). +
  2. +
  3. PR_NAME_FILTERS
    + If present, and a PR_NAME_KEYS parameter is present as well, this value can be used to specify a QueryFilter object that will refine the routing logic specified in the PR_NAME_KEYS parameter. See the section on QueryFilter objects for details. +
  4. +
  5. PR_NAME_REFLECT_TO_SELF
    + If present, all wild-card pattern matches will include your own session's nodes in their result sets. If not present (the default), only nodes from other sessions will be returned to your client. This field may be of any type or value; only its existence/non-existence is looked at. +
  6. +
  7. PR_NAME_MAX_UPDATE_MESSAGE_ITEMS
    + This parameter, which should be of type int32, can be used to specify the maximum number of updates that should be sent back to the client in a single PR_RESULT_DATAITEMS Message. You can use this to specify the "granularity" of your update notifications. Default value for this parameter is 50 updates per Message. +
  8. +
  9. PR_NAME_PRIVILEGE_BITS
    + This is a read-only parameter, and the int32 value of this parameter is a bit-chord indicating what special privileges (if any) your client has been given by the server. Currently defined privilege bits are PR_PRIVILEGE_KICK, PR_PRIVILEGE_ADDBANS, and PR_PRIVILEGE_REMOVEBANS. +
  10. +
  11. PR_NAME_REPLY_ENCODING
    + This int32 parameter can be used to control the way Messages going from the server back to your client are encoded. The default encoding (used if this parameter is not present) is MUSCLE_MESSAGE_ENCODING_DEFAULT, which is a vanilla, uncompressed format. Other currently supported encodings are MUSCLE_MESSGE_ENCODING_ZLIB_1 through MUSCLE_MESSAGE_ENCODING_ZLIB_9, which enable varying levels of transparent data compression on the data as it is sent back to your client. See iogateway/MessageIOGateway.h for further details. +
  12. +
  13. "SUBSCRIBE:..."
    + Any string parameter whose name starts with the prefix "SUBSCRIBE:" is treated as a subscription request. See the section on Subscriptions for details. +
  14. +
  15. PR_NAME_DISABLE_SUBSCRIPTIONS
    + If this parameter is present (it doesn't matter what its value is) then subscription-updates will be disabled to your client. To re-enable them send a PR_COMMAND_REMOVEPARAMETERS Message removing this parameter again. +
  16. +
  17. PR_NAME_SUBSCRIBE_QUIETLY
    + This isn't actually a parameter itself, but if added to the same PR_COMMAND_SETPARAMETERS message as one or more "SUBSCRIBE:" entries, it will suppress the initial notification of the new subscription data. That is to say, if you add an item with this name to your message, you won't receive the values of the nodes you subscribed to until the next time they are changed. (remember that by default you would receive the "current' values of the nodes immediately) +
  18. +
+ +

(Optional) Indexed Nodes and Orderly Children

+ +New for v1.40 of muscled is support for "indexed" nodes. An indexed node is the same as any other node, +except that it maintains an ordered index of some or all of its children. In most cases, this index is +unecessary, so by default no indexing is done. There are some cases where the ordering of child nodes +is important, however--for example, if you are advertising to other clients a list of instructions that +must be executed in order, and the list too large to upload an entire new list every time the list changes. +

+To enable indexing for a node, simply add child nodes to it using PR_COMMAND_INSERTORDEREDDATA +instead of the usual PR_COMMAND_SETDATA messages. In a PR_COMMAND_INSERTORDEREDDATA message, you +specify the parent node that the new child node is to be added to, and the data/Message the new +child node will contain--but muscled will be the one to assign the new child node an (algorithmically +generated) name. Generated names are guaranteed to start with a capital 'I'. Muscled will add the +child node in front of a previously added indexed child whose name you specify, or will add it to the +end of the index if you specify sibling name that is not found in the index. +

+Here is an example PR_COMMAND_INSERTORDEREDDATA message that will add an indexed child node to the +node "myNode": +

+Message imsg(PR_COMMAND_INSERTDATA);
+imsg.AddString(PR_NAME_KEYS, "myNode");     // specify node(s) to add insert the child node(s) under
+Message childData('Chld');   // any data you want to store can be placed in this message....
+imsg.AddMessage("I4", childData);    // add the new node before I4, if I4 exists.  Else append to the end.
+

+If myNode already contains an indexed child with the name I4, the new node will be inserted into +the index just before I4. If I4 is not found in the index, the new node will be appended to the +end of the index. If you want to be sure that your new child is always added to the end of the +index, you can just AddMessage() using a field name that doesn't start with a capital I. +

+You are allowed to specify more than one parent node in PR_NAME_KEYS (either via wildcarding, +or via multiple PR_NAME_KEYS values)--this will cause the same child nodes to be added to all matching +nodes. You are also allowed to specify multiple child messages to add in a single INSERTORDEREDDATA message +(either by adding sub-messages under several different field names, or by adding multiple +sub-messages under a single field name). +

+When a node contains an index (i.e. when it has at least one child under it that was added via +PR_COMMAND_INSERTORDERREDDATA) any clients that are subscribed to that node will receive +PR_RESULT_INDEXUPDATED messages when the index changes. These messages allow the subscribed +clients to update their local copy of the index incrementally. Each PR_RESULT_INDEXUPDATED +message will contain one or more string fields. Each string field's name will be the fully +qualified path of the indexed node whose index has been changed. Each string/value in a given string +field represents a single operation on the index. An example message might look like this: + +

+Message:  this=0x800c32f8, what='!Pr4' (558920244/0x21507234), entryCount=1, flatSize=79
+  Entry: Name=[/spork/0/hello], CountItems()=4, TypeCode()='CSTR' (1129534546) flatSize=40
+    0. [c]
+    1. [i0:I0]
+    2. [i1:I1]
+    3. [r1:I1]     
+

+ +The first letter of each string is an opcode, one of the INDEX_OP_* constants defined in +StorageReflectConstants.h. Here we see that the first instruction has a 'c', or +INDEX_OP_CLEARED, indicating that the index was cleared. The next instruction, i0:I0 starts +with a INDEX_OP_ENTRYINSERTED, and indicates that a child node named I0 was inserted at +index 0. After that, a child node named I1 was inserted at index 1. Lastly, the +INDEX_OP_ENTRYREMOVED opcode ('r') indicates that the node at index 1 (I1) was then removed +from the list. By parsing these instructions, the client can update its own local index +to follow that of the server. +

+Note that the index only contains node names and ordering information; the actual node data is kept in the child +nodes, in the normal fashion. So most clients will want to subscribe to both the indexed parent +node, and its children, in order to display the data that the index refers to. +

+An indexed node will also send the contents of its index (in the form of a PR_RESULT_INDEXUPDATED +message with a INDEX_OP_CLEARED op code, followed by one or more INDEX_OP_ENTRYINSERTED opcodes) +to any client that requests it via the PR_COMMAND_GETDATA command. This message is sent in +addition to the regular PR_RESULT_DATAITEMS message. +

+To remove a node entry from the index, simply remove the delete the child node in the normal +fashion, using a PR_COMMAND_REMOVEDATA message. You can, of course, update a child node's data +using a PR_COMMAND_SETDATA message, without affecting its place in the index. +

+One last note is that index data is always sent to all clients that ask for it; it is even +sent to the client who created/owns the indexed node. That is to say, the PR_NAME_REFLECT_TO_SELF +attribute may be considered always set as far as index data is concerned. This is because the +index is created on the server side, and so not even the client-side initiator of the index +creation can be exactly sure of the index's state. It's best for clients not to make assumptions +about the contents of the index, and update their local indices based solely on the +PR_RESULT_INDEXUPDATED messages they receive from the server. + +

(Optional) QueryFilters

+ +New for MUSCLE v2.40 is support for QueryFilter objects. QueryFilter objects offer you a way +to do content-based server-side filtering of subscription sets and other operations. Whereas +before you could specify a set of nodes based solely on their node-path, you can now combine +the wildcarded node-path logic with formulas based on the contents of node's data Message as well. +For example, you can now specify a subscription on "all nodes that match /*/*/* that have +an int32 field named 'joe' whose value is less than 5". Different types of QueryFilter subclass +exist for various purposes, and they can be combined using AND, OR, NOT, and XOR operations to +make fairly sophisticated filters. +

+The supported QueryFilter classes are defined in regex/QueryFilter.h. Here is a slightly abridged +reprinting of their shared base-class interface: + +

+class QueryFilter : public RefCountable
+{
+public:
+   virtual status_t SaveToArchive(Message & archive) const;
+   virtual status_t SetFromArchive(const Message & archive);
+
+   virtual bool Matches(const Message & msg) const = 0;
+};
+
+ +Of the three method shown above, Matches() is the one that embodies +a QueryFilter's primary purpose: To determine, given a Message object, whether that +Message matches the filter, or not. In our example above, we would use a QueryFilter +object whose Matches() method returns true only for Messages with an int32 field named "joe" +whose value is less than 5. + +The other two methods are also important -- they are the mechanism by which a C++ QueryFilter +object can travel from your client to the server. The SaveToArchive() method's job is to save +the QueryFilter's settings into the given Message, and the SetFromArchive() method's job is +to update the QueryFilter's settings to match those previously stored in the Message. +To use QueryFilters, your client will create the QueryFilter object it wants the server +to use, then SaveToArchive() that object into a Message, and send the Message to the server. +When the server gets the Message, it will use it to reconstruct an identical QueryFilter object, +which it can then use to filter Messages for you. + +Here is some example code your client might use to set up the aforementioned filtered subscription +query to the server: + +
+Int32QueryFilter filter("joe", Int32QueryFilter::OP_LESS_THAN, 5);
+MessageRef filterMsg = GetMessageFromPool();
+filter.SaveToArchive(*filterMsg());
+
+MessageRef msgRef = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
+msgRef()->AddMessage("SUBSCRIBE:/*/*/*", filterMsg);      
+myMtt->SendMessageToSessions(msgRef);
+
+ +As you can see, we set up the subscription in the normal way, except that this +time the SUBSCRIBE: parameter's value is a Message that represents the QueryFilter. +This tells the server to restrict the query ("/*/*/*") further, so that only +nodes whose Messages also meet the "joe < 5" criteria are considered to be matches. + +For PR_COMMAND_GETDATA, PR_COMMAND_REMOVEDATA, and other Messages where a node-path +pattern is specified in the PR_NAME_KEYS field, the procedure is slightly different. +For those Messages, you can now add a PR_NAME_FILTERS field that contains Messages +that are archived QueryFilter objects. For example, say you wanted to remove +all nodes in your "home folder" whose node-names start with the letter Z, and +whose Message's what-code is 666. You could do that like this: + +
+WhatCodeQueryFilter filter(666);
+MessageRef filterMsg = GetMessageFromPool();
+filter.SaveToArchive(*filterMsg());
+
+MessageRef msgRef = GetMessageFromPool(PR_COMMAND_REMOVEDATA);
+msgRef()->AddString(PR_NAME_KEYS, "Z*");
+msgRef()->AddMessage(PR_NAME_FILTERS, filterMsg);
+myMtt->SendMessageToSessions(msgRef);
+
+ +There is a fairly large number of different QueryFilter subclasses to choose from. +Here is a brief list of the various available types, and what they do: + +
    +
  1. WhatCodeQueryFilter
    + This filter matches only Messages whose 'what' code is the specified value, + or within a specified range of values. +
  2. +
  3. ValueExistsQueryFilter
    + This filter matches only Messages that contain the specified field name. + You can also optionally specify what data type that field must be of. +
  4. +
  5. BoolQueryFilter, Int8QueryFilter, Int16QueryFilter, Int32QueryFilter, + Int64QueryFilter, FloatQueryFilter, DoubleQueryFilter, PointQueryFilter, + and RectQueryFilter
    + These filters are all quite similar in that they let you specify a field name + and a value of the given type, and an operator to use to compare the + Message's value in that field with your specified value. Available operators + are: OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN, OP_LESS_THAN_OR_EQUAL_TO, + OP_GREATER_THAN_OR_EQUAL_TO, and OP_NOT_EQUAL_TO. +
  6. +
  7. StringQueryFilter
    + This filter looks for a string field inside the Message, and matches only + if the found string value passes the test specified by the StringQueryFilter. + You specify an operator and a second value for the operator to take. + Available string operators are OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN, + OP_LESS_THAN_OR_EQUAL_TO, OP_GREATER_THAN_OR_EQUAL_TO, OP_NOT_EQUAL_TO, + OP_STARTS_WITH, OP_ENDS_WITH, OP_CONTAINS, OP_START_OF, OP_END_OF, + OP_SUBSTRING_OF, OP_EQUAL_TO_IGNORECASE, OP_LESS_THAN_IGNORECASE, + OP_GREATER_THAN_IGNORECASE, OP_LESS_THAN_OR_EQUAL_TO_IGNORECASE, + OP_GREATER_THAN_OR_EQUAL_TO_IGNORECASE, OP_NOT_EQUAL_TO_IGNORECASE, + OP_STARTS_WITH_IGNORECASE, OP_ENDS_WITH_IGNORECASE, OP_CONTAINS_IGNORECASE, + OP_START_OF_IGNORECASE, OP_END_OF_IGNORECASE, OP_SUBSTRING_OF_IGNORECASE, + OP_SIMPLE_WILDCARD_MATCH, and OP_REGULAR_EXPRESSION_MATCH. +
  8. RawDataQueryFilter
    + This filter is similar to the StringQueryFilter, except that it operates + on raw byte sequences instead. Since any data item is ultimately a sequence of + bytes, that means that this filter can be used to match against data of + any type (although it is probably most useful in conjunction with B_RAW_TYPE + or flattened-user-object types). Available operaters for this filter are: + OP_EQUAL_TO, OP_LESS_THAN, OP_GREATER_THAN, OP_LESS_THAN_OR_EQUAL_TO, + OP_GREATER_THAN_OR_EQUAL_TO, OP_NOT_EQUAL_TO, OP_STARTS_WITH, OP_ENDS_WITH, + OP_CONTAINS, OP_START_OF, OP_END_OF, OP_SUBSET_OF +
  9. MessageQueryFilter
    + This filter lets you "drill down" into sub-Messages in a Message's content, + if you wish to. You can specify a field name and a "child filter", and + if the MessageQueryFilter finds a Message field inside the node's data Message + with the given field name, it will call the child QueryFilter on the + found Message and return the result. +
  10. AndOrQueryFilter
    + This filter is a "meta-filter", in that you can add one or more child + QueryFilters to it, and it will combine their operations together to make + a more complex operation. Its function is this: It will match the given + Message if at least (n) of its child filters match the Message. You get + to specify what (n) is. The two most common uses for this filter are OR + (set n=1, and this filter will match if any one of its children match), + and AND (set n equal to the number of child filters, and this filter will + match only if all of its children match). +
  11. +
  12. NandNotQueryFilter
    + This filter is another "meta-filter", and is useful primarily for combining + child QueryFilters together with NAND or NOT operations. Its function is + this: It will match the given Message if no more than (n) of its children + match the Message. So, to get a NOT filter, you could add a single child + to this filter and set (n=0). To get a NAND filter, you could add two + children and set (n=1). And so on. +
  13. +
  14. XorQueryFilter
    + This meta-filter is quite simple: it will return true if and only if + an odd number of its children match the provided Message. It is mainly + useful for XOR operations. +
  15. +
+ +As you can see, there is a considerable amount of flexibility and power +provided by QueryFilter objects. They are slightly harder to use and slightly +less efficient than the traditional node-path-only MUSCLE queries, but they +are there for you to use if you wish, or to ignore if you prefer not to use them. + + + diff --git a/html/Custom Servers.html b/html/Custom Servers.html new file mode 100644 index 00000000..df90d717 --- /dev/null +++ b/html/Custom Servers.html @@ -0,0 +1,403 @@ + + +

Implementing a Custom Server with MUSCLE

+

v6.05 / Jeremy Friesner / Meyer Sound Laboratories Inc / jaf@meyersound.com 6/27/2014

+ + +

Introduction

+As you may be aware, MUSCLE includes a fairly powerful general purpose server program named 'muscled'. muscled allows an arbitrary number of client machines to connect to it at once, share data, and send messages to each other in a many-to-many fashion. Because muscled includes a built-in database, 'live' database queries, pattern matching support, and so on, many applications can be implemented using muscled 'out of the box', without any modifications. BeShare is an example of an application for which no modifications to muscled are needed. +

+However, there are some things that muscled does not do, such as read or write from the hard disk, authenticate users, or implement application-specific logic. But if your client-server application needs to do such things, don't worry, the muscled architecture can be seamlessly extended to include the functionality you require. This document will show you how to do that. +

+This document assumes that you have some basic knowledge of C++, and how MUSCLE works (at least from a client program's perspective). If you don't, you may want to review the Beginner's Guide to MUSCLE programming. You will want to have the MUSCLE header files (muscle/*/*.h) handy as you read along, as this document does not attempt to be comprehensive in its API descriptions. +

+Note that the server side of MUSCLE is 100% single-threaded by default. It uses multiplexed non-blocking I/O and message queues to achieve a multithreaded effect, but is single-threaded for purposes of stability and portability. Trying to integrate multithreaded code into a MUSCLE server is possible, but usually not necessary. I suggest you stick with the single-thread model if you can, it's not as bad as you think ;^). + +

Customizing muscled

+ +Of course you could always customize muscled by simply going in and changing sections of the MUSCLE server code. Since the MUSCLE source code is available, it is easy to do this--however, I recommend not doing so unless it is absolutely necessary. Changing the code in the MUSCLE archive means that you have essentially 'forked' the code, and you won't be able to easily synchronize your code base with future versions of the MUSCLE archive. A better way to do it (in almost every case) is to consider the MUSCLE classes 'read-only', and implement your customizations by subclassing them as necessary. I have successfully created three different custom servers this way already, so it's very likely that what you want to do can be done by subclassing. With that in mind, I will begin by describing the server-side classes that are most useful to subclass: + +

class AbstractReflectSession (muscle/reflector/AbstractReflectSession.h)

+ +The AbstractReflectSession class is an abstract base class, each object of which represents a single client connection. The server holds a list of AbstractReflectSession objects, so e.g. if 12 clients are connected to the server, the server will have 12 items in this list. Each AbstractReflectSession object holds a MessageIOGateway that is used for communicating with the client. Just as importantly, each AbstractReflectSession object overrides several virtual methods that together implement most of the server-side logic. Here is a brief rundown of some of the more important methods in this interface: + +
    +
  1. +virtual status_t AttachedToServer(); +

    + When the server receives a new TCP connection from a client, it uses the factory object passed in to its constructor to create a new AbstractReflectSession object. After the session object is created and initialized, this method is called on it. Here is where you can do any setup work that needs to be done for your new client, and return B_NO_ERROR if everything is okay. If something has gone horribly wrong, you can return B_ERROR, and the connection to the client will be closed, and this session object deleted. Be sure to call your superclass's AttachedToServer() method as the first thing in your implementation, otherwise your session won't get set up properly! +

  2. +

    +

  3. +virtual void AboutToDetachFromServer(); +

    + Called by the ReflectServer when it is about to remove this session from its list of active sessions. Any last-minute cleanup work you need to do can be done here. Be sure to call your superclasses' AboutToDetachFromServer() as the last thing in your implementation, so it can clean up its stuff as well. +

  4. +

    +

  5. +virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); +

    + This method is called whenever your session's client computer has sent a Message to the MUSCLE server. You would generally implement this method to parse the Message and respond appropriately to your client's request. Be sure to call your superclass's implementation of this method for any messages that you can't parse yourself. +

  6. +

    +

  7. +virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * userData); +

    + This method is called whenever another AbstractReflectSession has sent you a Message. It is via this method that clients can interact with each other, e.g. for client A to send a message through the MUSCLE server to client B, it would go something like this: +

      +
    1. client computer A sends a Message over the TCP connection to the MUSCLE server.
    2. +
    3. MessageReceivedFromGateway() is called on client A's session object.
    4. +
    5. A's session object parses the Message, and decides that the message is for client B. So it calls MessageReceivedFromSession() on client B's session object.
    6. +
    7. B's session object's MessageReceivedFromSession() calls AddOutgoingMessage() to send the Message on to client B.
    8. +
    + Again, be sure to call your superclass's implementation of this method for any messages that you don't know how to handle yourself. +
  8. +
+ +There are quite a few other methods to call or override in the AbstractReflectSession class, but the three above are the ones that you will almost certainly want to handle in any custom server. +

+When implementing your own session logic, there are several different "pre-made" AbstractReflectSession subclasses to choose from. Depending on how much code you want to re-use, you can override AbstractReflectSession directly and handle everything yourself, or you can override one of the following, and take advantage of the logic already provided: +

+

    +
  1. DumbReflectSession (muscle/reflector/DumbReflectSession.h). As the name implies, this subclass of AbstractReflectSession doesn't do much. All it does is take each Message it receives from its client, and send it on to all the other connected clients, for a sort of "broadcast" effect. I'm not sure how often this logic is useful (unless you have bandwidth to burn), but it is there, and it also serves as a simple example of subclassing AbstractReflectSession. +
  2. +
  3. StorageReflectSession (muscle/reflector/StorageReflectSession.h). This is the class where practically all of muscled's standard features are implemented. The database, the pattern matching, the getting and setting of parameters, the live queries, and all of the other PR_COMMAND_* operations are implemented in this class. Given that, you may find it useful in many situations to subclass this class, handling any Messages you recognize in your class, and passing the rest down to StorageReflectSession for normal muscled-style handling. +
  4. +
+ + + + +

class ReflectServer (muscle/reflector/ReflectServer.h)

+ +The ReflectServer class is a singleton-style class in the MUSCLE framework; it represents the server itself. This is the object that holds the list of AbstractReflectSessions, listens for incoming TCP connections, and coordinates the data traffic and messaging between clients and sessions. Given all this, the ReflectServer class isn't very large; this is because most of the high-level functionality is handled by the AbstractReflectSession objects themselves. The ReflectServer class can be used as is, or you can subclass it to gain more customized behaviour. The following methods, in particular, might be useful to override: + +
    +
  1. +virtual status_t ReadyToRun(); +

    + Called when the server starts up. You can do any initialization work here, and return B_NO_ERROR if you are ready to go. (B_ERROR will abort startup). +

  2. +

    +

  3. +virtual void Cleanup(); +

    + Must be called just before the ReflectServer object is deleted. Default implementation detaches all sessions from the server. You can override this method to do any additional tidying up if you like. +

  4. +

    +

  5. +virtual uint64 GetPulseTime(const PulseArgs &); +
    +virtual void Pulse(const PulseArgs &); +

    + If you need actions to be performed at a specific time (as opposed to only doing things in response to incoming Messages), you can override these two methods. Note that this sort of behaviour is discouraged if you can avoid it, as it is easy to abuse. (Remember that polling is inefficient and evil!) GetPulseTime() can be overridden to specify when Pulse() should be called next. By default, Pulse() is never called (i.e. the default implementation of GetPulseTime() returns MUSCLE_NEVER_PULSE). +

  6. +
+ +When implementing your own custom server, you'll want to have your own main() function that instantiates the server. The regular muscled's main() function can be found in muscle/server/muscled.cpp -- don't link this file into your code, but you may want to copy it into your own server's directory and modify it to run your custom code. An extremely minimal main() might look like this: + +
// Factory class for my custom session type
+class MySessionFactory : public ReflectSessionFactory
+{
+public:
+   // Note that (clientAddress) and (serverAddress) are ignored, we don't care about them for now */
+   virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & serverAddress)
+   {
+      AbstractReflectSessionRef ret(newnothrow MyReflectSessionSubclass());
+      if (ret == NULL) WARN_OUT_OF_MEMORY;
+      return ret;
+   }
+};
+
+int main(int argc, char ** argv)
+{
+   CompleteSetupSystem css;
+   ReflectServer myServer;  // instantiate regular ReflectServer or your own subclass
+   myServer.PutAcceptFactory(2960, ReflectSessionFactoryRef(new MySessionFactory));  // Whenever we get a connection on port 2960, use my factory to create a new session object
+   status_t ret = myServer.ServerProcessLoop();  // everything happens here!  Will not return for a long time (if ever!)
+   if (ret == B_NO_ERROR) printf("Server terminated normally.\n");
+                     else printf("Server exited with an error condition!\n");
+   myServer.Cleanup();
+   return 0;
+}
+

+ +The ServerProcessLoop() method is where all the serving get done--it won't return until the server decides to quit--either due to a critical error, or because your code told it to shut down (by calling EndServer()). + +

class AbstractMessageIOGateway (muscle/iogateway/AbstractMessageIOGateway.h)

+ +The AbstractMessageIOGateway class is a class that represents the interface between the byte-stream-oriented world of a DataIO object, and the series-of-Messages world of MUSCLE. That is, the AbstractMessageIOGateway's job is to convert a stream of incoming bytes into a series of incoming Messages, and to convert a series of outgoing Messages into a stream of outgoing bytes. The AbstractMessageIOGateway class also contains code to handle FIFO queueing of outgoing Messages in case the TCP socket can't transmit fast enough to keep up. +

+Note that if you are creating a new, custom server application, you almost certainly WON'T need to make your own AbstractMessageIOGateway subclass. MUSCLE comes with a very nice MessageIOGateway class, which knows how to translate any Message into the standard MUSCLE byte-stream format. The MessageIOGateway class is used by default, and in most cases, it is all you need. An additional benefit to sticking with the standard MessageIOGateway class is that you will remain stream-compatible with other MUSCLE code that is out there--defining your own AbstractMessageIOGateway class means breaking protocol compatibility, which may cause headaches later on. Nonetheless, there are situations where a custom IO gateway is useful: + +

    +
  1. You need to use an existing non-MUSCLE protocol. For example, at LCS our custom server needed to be able to send and receive MIDI Sysex messages, so we created a custom IO gateway to translate MIDI into Messages and vice versa. You could create IO gateways to convert Messages into ASCII text, HTML, XML, or other formats. +
  2. +

    +

  3. The existing MUSCLE byte protocol uses too much bandwidth for you. While the MessageIOGateway's byte stream format doesn't go out its way to be inefficient, it is a generalized format, which means it can't make many assumptions about the data it is transmitting. For example, MessageIOGateway has to transmit all field names explicitely, whereas if you know you will always be putting just a single float value in each message, you could make a custom gateway that encodes each message in as little as four bytes. Before doing this, however, be sure to weigh the costs of incompatibility against the gained efficiency. If your messages aren't going to be sent very often, the saved bytes may not be worth the lost flexibility. +
  4. +
+ +If you have decided that a custom IO gateway is the way to go, here are some of the virtual methods you will want to implement/override: + +
    +
  1. +virtual bool HasBytesToOutput() const; +

    +This method must return true if and only if you have bytes ready to send out over the TCP connection. It is used to determine whether or not the MUSCLE server should wake up and call DoOutputImplementation() when there is room to send more bytes. +

  2. +

    +

  3. +virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); +

    +This method is called when you have bytes to send (see above) and there is space in the TCP buffers to send it. You should attempt to write up to (maxBytes) bytes of data to your DataIO object (i.e. get the next message from the outgoing message queue, convert it to bytes, send the bytes, repeat), and return the number of bytes you were actually able to send. If you encounter an error, return -1 to indicate that. Returning -1 is taken to mean that the stream has been broken, and will usually result in the termination of the session. This method should never block. +

  4. +

    + +

  5. +virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & target, uint32 maxBytes = MUSCLE_NO_LIMIT); +

    +This method is called whenever incoming bytes are available on your DataIO object's TCP stream. You should read up to (maxBytes) of data, convert as many bytes as possible into Message objects, and call target.CallMessageReceivedFromGateway(msg, NULL) for any Message that you create. Any bytes that represent a partial message (i.e. you don't have the entire Message's worth yet) should be held in a local buffer somehow, for next time. This method should also return the number of bytes that were actually read, or -1 if there was an error reading. +

  6. +
+ +For certain formats, you may be able to override MessageIOGateway instead of AbstractMessageIOGateway, and thus avoid having to do all the byte-by-byte parsing work yourself. MessageIOGateway can be used for any byte-stream format where each message consists of a fixed-size header followed by a variable number of bytes of message body. It must be possible to tell the number of bytes of message body by looking at the message header. If your protocol fits these criteria, you can simply override the FlattenedMessageBodySize(), FlattenMessage(), UnflattenMessage(), HeaderSize(), and GetBodySize() methods to fit your byte protocol, and MessageIOGateway can handle the rest of the parsing for you. + +To get your IO gateway subclass to actually be used in the server, you will need to be using your own AbstractReflectSession subclass, and override its CreateGateway() method to return an instance of your AbstractMessageIOGateway subclass. Your CreateGateway() method override should look something like this: + +
AbstractMessageIOGatewayRef
+MyReflectSession ::
+CreateGateway()
+{
+   AbstractMessageIOGatewayRef ret(newnothrow MyGateway);  // My custom I/O gateway!
+   if (ret() == NULL) WARN_OUT_OF_MEMORY;
+   return ret;
+}
+

+ + +

Accessing AbstractReflectSession functionality

+ +The AbstractReflectSession class is somewhat misnamed--it isn't completely abstract. There is some useful functionality built into it. What follows is a list of some of the more useful non-virtual functions in this class. Note that most of these shouldn't be called before AttachedToServer() has been called. + +
    +
  1. Message & GetCentralState() const; +

    + This method returns a reference to a Message that is shared by all sessions. Any session may access or alter this message, and other sessions can call GetCentralState() to see the changes. For example, StorageReflectSession uses this message to store the root of the node database. Please be gentle while altering the central state message, however, as other sessions may be relying on their data being there. +

  2. +

    +

  3. const Hashtable & GetSessions() const; +

    + Returns a read-only reference to the current set of sessions attached to + the server. Handy if you need "down and dirty" direct access to the other sessions. + (Note that this type of direct access usually leads to dynamic casting, and the same functionality + can often be implemented more cleanly by message passing... BroadcastToAllSessions() is helpful here) +

  4. +

    +

  5. AbstractReflectSession GetSession(const String & idStr) const; +
  6. AbstractReflectSession GetSession(uint32 id) const; +

    + Given a session ID (expressed either as a string or an integer), returns you a reference to that session... or a NULL reference if no such session exists. +

  7. +

    +

  8. status_t AddNewSession(const AbstractReflectSessionRef & session, const ConstSocketRef & socket); +

    + Adds the given new session to the session list. (socket) should be the socket used by the new session + (the server will watch this socket to determine when incoming data is available), or a NULL ConstSocketRef if + the new session has no TCP socket of its own (i.e. a 'fake' session) +

  9. +
  10. status_t AddNewConnectSession(const AbstractReflectSessionRef & session, uint32 targetIP, uint16 targetPort); +

    + Also adds the given new session to the session list. But instead of immediately being active using a given + socket, this method takes the IP address of a remote host that you would like your muscle server to connect out to. + The connection will be done asynchronously, and (session)'s AsyncConnectCompleted() method will be called when the + connection completes successfully. (If the connection fails, (session) will be detached from the server and discarded) +

  11. +

    +

  12. void EndSession(); +

    + Sort of a session's hara-kiri method. Call this on a session and that session will be marked for removal and deletion by the server at the next opportunity. The session isn't deleted immediately (as it may be in the middle of an operation), but will be ASAP. +

  13. +

    +

  14. status_t ReplaceSession(const AbstractReflectSessionRef & newSession); +

    + Causes the session to be terminated, and (newSession) to be put in its place. (newSession) will be + given the same TCP socket and IOGateway as the old session had. This method is a quick way to swap + out the server-side logic of a session while maintaining its connection to the client. +

  15. +
+ +

Accessing StorageReflectSession functionality

+ +If you have chosen to subclass the StorageReflectSession subclass, you may want to access some of the StorageReflectSession class's features directly. The easiest way to do this in simple cases is to just create your own Message and pass it to StorageReflectSession::MessageReceivedFromGateway(), where it will be handled just as if the message had come from the client itself. For example: + +
void 
+MyReflectSession ::
+SetupMyStuff(const Message & msg1, const Message & msg2, const Message & msg3)
+{
+   // Add our three messages as nodes in our database
+   MessageRef msg = GetMessageFromPool(PR_COMMAND_SETDATA);
+   if (msg())
+   {
+      msg()->AddMessage("appnodes/node1", msg1);
+      msg()->AddMessage("appnodes/node2", msg2);
+      msg()->AddMessage("appnodes/node3", msg3);
+      StorageReflectSession::MessageReceivedFromGateway(msg);
+   }
+   else WARN_OUT_OF_MEMORY;
+}
+

+ +The above technique will work for any message type that is supported for clients (i.e. PR_COMMAND_*). The semantics will be the same as they would be for the client, if it were to send the same message. +

+If you need more control than the client messaging API can provide, however, you can call the public and protected members of the StorageReflectSession class directly. For example, you can create or set nodes in your subtree of the database by calling SetDataNode(): + +

void 
+MyReflectSession ::
+SetupMyStuff2(const MessageRef & msg1, const MessageRef & msg2, const MessageRef & msg3)
+{
+   SetDataNode("appnodes/node1", msg1, true, true);
+   SetDataNode("appnodes/node2", msg2, true, true);
+   SetDataNode("appnodes/node3", msg3, true, true);
+}
+

+ +This method gives you some additional control, allowing you to specify whether or not nodes should be created or overwritten (the third and fourth arguments of SetDataNode()). +

+Sometimes you'll want to do your own queries of the server-side database. One way to do this would be to create a PR_COMMAND_GETDATA message, pass it to StorageReflectSession::MessageReceivedFromGateway(), and then parse the resulting PR_RESULTS_DATAITEMS message that is given to your MessageReceivedFromSession() method. But that method is somewhat inefficient, and a little bit error-prone (how are you to know which PR_REZULTS_DATAITEMS message corresponds with which PR_COMMAND_GETDATA message?). A better way to do it is by setting up your own query callback. In the following example, we will execute a query for all connected clients' session nodes ("*/*"), and our callback will be executed once for every node that matches the query string. + +

void
+MyReflectSession ::
+FindAllSessionNodes()
+{
+   Queue myList;  // collect results here
+
+   WildPathMatcher matcher;
+   matcher.PutPathString("*/*", QueryFilterRef());
+   matcher.DoTraversal((PathMatchCallback)MyCallback, this, *_globalRoot, true, &myList);
+
+   // (myList) now contains path names of all session nodes...
+}
+
+int
+MyReflectSession ::
+MyCallback(DataNode & node, void * userData)
+{
+   printf("MyCallback called for node: [%s]\n", node.GetNodePath());
+   printf("Message for this node is: ");
+   const Message * nodeData = node.GetData()->GetItemPointer();
+   if (nodeData) nodeData->PrintToStream();
+            else printf("\n");
+   Queue * myList = (Queue *) userData;  // as passed in to DoTraversal()
+   myList->AddTail(node.GetNodePath());
+   return node.GetDepth();   // continue traversal as usual
+}
+

+ +The above code also demonstrates the use of the (userData) field to carry additional information into the callback. Whatever value you pass in as the last argument of DoTraversal() is passed back to your callback method, to do with as you wish. Here it is used to pass in a pointer to a Queue to which the path names of the matching nodes are added. +

+The return value of your callback function is also important. It should specify the node-depth at which to continue the traversal. This value can be used to dynamically prune the search of the database tree, for efficiency. For a full traversal, you should always return node.GetDepth(). On the other hand, if you have found the data you wanted and wish to terminate the search immediately, you would return 0. Or, if you wanted the search to continue at the next session node, you could return 2 (which is the level of the session nodes in the tree). As a final example, if you want the search to continue, but not to recurse into the subtree below the current node, you would return node.GetDepth()-1. +

+A WildPathMatcher can do a query made up of several path strings at once. For example, if you wanted to do a query on all host nodes AND all session nodes, you could do this: + +

WildPathMatcher matcher;
+matcher.PutPathString("*", QueryFilterRef());   // query on all host nodes (level 1)
+matcher.PutPathString("*/*", QueryFilterRef()); // query on all session nodes (level 2)
+matcher.DoTraversal((PathMatchCallback)MyCustomCallback, this, *_globalRoot, true, NULL);
+

+ +A single traversal will not trigger the callback function for any given node more than once, even if that node is matched by more than one path string. +

+The third argument to DoTraversal() (*_globalRoot in the examples above) is the node to start the search at. For searches of the entire database, *_globalRoot is the correct value to place here; however you may wish to limit your search to only a subtree of the database tree. For example, if you wish to make your search relative to the current session's node only (and thus search only nodes that your own session created), you could put *_sessionDir.GetItemPointer() here instead. Note that using a different starting point does change the semantics of the path strings... e.g. in that case "*" would mean all children underneath the session node, rather than all children beneath the root node. + +

+Leading slashes in the path strings are NOT handled by the WildPathMatcher--all WildPathMatcher path strings are taken to be relative paths, and are relative to the node passed in as the third argument to DoTraversal(). If you want to be able to handle leading slashes and give a default prefix to relative path strings, you may find the method WildPathMatcher::AdjustStringPrefix() to be useful. + +

+ +

PushSubscriptionMessages()

+ +As nodes in the database are created or updated, MUSCLE generates messages to send back to clients who have posted subscriptions that match the updated nodes. These messages are not sent the clients' outgoing message queues immediately, but rather are stored in a separate collection cache until either a sufficient number of update messages are gathered, or the server is ready to go back to sleep for lack of activity. This behaviour allows the server to reduce messaging overhead by sending out fewer update messages. In some cases, however, the ordering of outgoing messages is important. In these cases, you can call StorageReflectSession::PushSubscriptionMessages() to force the collection cache to be purged immediately. Note that PushSubscriptionMessages() will clear the collection caches of all sessions, not just your own. + + +

Fake Clients

+ +One feature of the MUSCLE server architecture is that it is very session-oriented. All database and messaging operations are handled on a per-session level, with each session owning its own subtree of the database. Each session (and hence each session's subtree) only lasts as long as that client remains connected to the server. This is fine if your database is to be equally shared amongst all clients, but sometimes you want to have an area of the database that is not uniquely tied to any individual client; one that will persist even if all clients disconnect. The MUSCLE architecture doesn't have direct support for this type of database storage; however, you can fake it nicely by creating a "fake" session. This session is "fake" in that it doesn't correspond to any client program, and has no TCP connection of its own; rather, it exists solely to talk to other server-side sessions, and provide a storage area that will last as long as the server is running. Such a session would generally be created by your ReflectServer subclass's ReadyToRun() method, although there is nothing that says it couldn't be created at another time. Example code to create such a session: + +
status_t
+MyReflectServer ::
+ReadyToRun()
+{
+   status_t ret = ReflectServer::ReadyToRun();
+   if (ret != B_NO_ERROR) return ret;
+
+   FakeSession * fakeSession = newnothrow FakeSession();
+   if (fakeSession)
+   {
+      // Add with socket = ConstSocketRef(), meaning there is no client for this session.
+      if (AddNewSession(AbstractReflectSessionRef(fakeSession), ConstSocketRef()) == B_NO_ERROR)
+      {
+         return B_NO_ERROR;  // success!
+      }            
+   }
+   else WARN_OUT_OF_MEMORY;
+
+   return B_ERROR;
+}
+

+ +Once created and added, this session can handle messages from the other sessions, create database nodes, and do just about anything any other session can do (except send messages to its client, as it doesn't have one). + + +

Logging

+ +MUSCLE comes with a built in logging facility, which can be used to log debug info to stdout and/or to a file. The MUSCLE logging API is defined in muscle/syslog/SysLog.h. The most commonly used logging function is LogTime(), which work much like printf(), except that it takes an additional parameter, the minimum log level. The minimum log level is used to determine whether or not this log message will actually be recorded or not. There are separate logging thresholds for logging-to-console and logging-to-file, and log entries will only be recorded if their log level is less than or equal to these thresholds. These thresholds default to MUSCLE_LOG_INFO for console logging, and MUSCLE_LOG_NONE for file logging, but these thresholds can be changed at any time by calling SetConsolLogLevel() or SetFileLogLevel(), respectively. When the first log entry to a file is recorded, a log file will be created in the current directory, with the current date and time as part of the log file name. Here is an example LogTime() call: + +
LogTime(MUSCLE_LOG_CRITICALERROR, "The sky is falling!  You have %i minutes to live!\n", minutesToLive);

+ +This call would cause a log entry like this to be generated: + +

[C 11/27 15:03:33] The sky is falling!  You have 5 minutes to live!

+ +The letter in the second column indicates the log level, followed by the date and time, and finally the log message. Note that the carriage return should be included in your messages text. This is so that you can assemble a log message from several log calls if you wish. Log() is the same as LogTime(), except that the bracketed level/time/date header isn't printed. +

+You may, in some cases, wish to do other things with the log message besides printing it to stdout or to a log file. In that case, you can use AddLogCallback() to install your own LogCallback object. This object's Log() method will be called whenever a message is logged, and you can then handle it as you wish. + + +

Handling Out-of-Memory Conditions

+ +Handling out-of-memory errors well is a very tricky problem, to the extent that many programs don't attempt to handle out-of-memory conditions at all; they just assume that problem won't happen, and crash if it does. This methodology may be okay for games, or client programs where a crash isn't catastrophic, but if a central program like a muscled server crashes, it can inconvenience many people (or in LCS's case, stop a musical in mid-chorus!), so it is very important that the server not crash, no matter what. Because of this, muscled is designed to handle out-of-memory conditions without crashing; if you want, your extension code can too. +

+MUSCLE code doesn't use C++ exceptions. This is because I'm not comfortable with the way exceptions integrate into C++; it's far too easy to get a memory leak or other unexpected result when an exception is thrown. Instead, MUSCLE uses the classic C-style method of handling errors--error codes are returned, and manually propogated up to the code that can handle them. (yes, it's a bit tedious. Yes, I still like it better than C++ exceptions. No whining! ;^)) +

+The standard C++ new operator throws a bad_alloc exception when it cannot allocate the requested amount of memory. To avoid this, MUSCLE code uses the new (nothrow) operator instead. To make things a little more flexible and easier to key in, MUSCLE #defines a synonym, "newnothrow", which means the same thing. The canonical MUSCLE style of dynamically allocating something is this: + +

Something * thing = newnothrow Something();
+if (thing)
+{
+   DoStuffWith(thing);
+   [...]
+   delete thing;  // now that we're done with it
+}
+else WARN_OUT_OF_MEMORY;
+

+ +The WARN_OUT_OF_MEMORY macro simply logs a message like this: + +

[C 11/27 15:03:33] ERROR--OUT OF MEMORY (MyCode.cpp:396)
+

+ +So later on, when you are trying to figure out why your code didn't get executed, you will know there was a problem allocating memory. +

+Another feature that MUSCLE offers (via the MemoryAllocator classes) is automatic memory-usage tracking and out-of-memory callbacks. The memory-usage-tracking allows you to place an upper limit on dynamic memory allocation, either to guarantee that your server won't overtax the machine it is running on, or just to test your out-of-memory handling code. See the code at the beginning of main() in muscled.cpp for an example of how to set up memory tracking/limiting. (Note that malloc/free calls aren't tracked--but you should always use new/delete for all your allocations anyway). +

+Out-of-memory callbacks let you free up unecessary memory in the event of a memory shortage. For example, MUSCLE uses ObjectPools to reduce the number of memory allocations and deletions that must be done on a regular basis. As such, MUSCLE may have several hundred unused objects lying around, awaiting reuse. When the new operator detects a shortage, however, it calls an out-of-memory callback that knows how to drain these pools, thus freeing up some extra memory and allowing the server a little more breathing room. You can use a AutoCleanupProxyMemoryAllocator and some OutOfMemoryCallback objects to configure this behaviour (again, see the beginning of muscle.cpp's main() function for an example). +

+ +

Conclusion

+ +That's what I can think of to document server-side MUSCLE programming for now. I'm sure there is plenty more that I haven't covered here; let me know if there is something you think should be included that I haven't mentioned, or you have a question that isn't answered clearly in this text. My email address is jaf@lcsaudio.com. +

+-Jeremy + + diff --git a/html/autodoc/genDocs.sh b/html/autodoc/genDocs.sh new file mode 100755 index 00000000..c510d8f6 --- /dev/null +++ b/html/autodoc/genDocs.sh @@ -0,0 +1,8 @@ +#/bin/sh +echo "Generating doxygen documentation. " +echo "You will need to have doxygen installed in" +echo "order for this script to work." +echo "" +export MUSCLE_VERSION=`grep MUSCLE_VERSION_STRING ../../support/MuscleSupport.h | cut -c 32-35` +doxygen muscle.dox +echo "Done!" diff --git a/html/autodoc/muscle.dox b/html/autodoc/muscle.dox new file mode 100644 index 00000000..a7c5c9d2 --- /dev/null +++ b/html/autodoc/muscle.dox @@ -0,0 +1,1240 @@ +# Doxyfile 1.4.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a 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 +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = MUSCLE + +# 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 = $(MUSCLE_VERSION) + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# 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 = . + +# 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 cause performance problems for the file system. + +CREATE_SUBDIRS = 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. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) 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. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) 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. + +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" "the" + +ABBREVIATE_BRIEF = NO + +# 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. + +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. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then 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. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then 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. + +STRIP_FROM_PATH = + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) +# is documented as struct 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 where the coding convention is that all structs are typedef'ed +# and only the typedef is referenced never the struct's name. + +TYPEDEF_HIDES_STRUCT = YES + +# 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 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. + +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 +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# 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 behaviour. +# 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 behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +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. + +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. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# 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 = + +# 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. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# 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); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# 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. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) 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. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# 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 and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +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. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When 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 (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) 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. + +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 (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +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 (the default) these declarations will be included in the +# documentation. + +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 (the default) these blocks will be appended to the +# function's detailed documentation block. + +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 (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +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. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) 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. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = 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 default), 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. + +SORT_BY_SCOPE_NAME = 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. + +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. + +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. + +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. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of 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 initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +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. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = 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 , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED 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. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR 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. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = 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) + +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 stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be 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. + +INPUT = ../.. + +# If the value of the INPUT tag contains directories, you can use the +# FILE_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 the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +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 = */test/* */regex/regex/* */zlib/zlib/* */qtsupport/*/* */atheossupport/* */besupport/* + +# 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. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are 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. + +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 +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# 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 also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# 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 documentstion. + +REFERENCES_LINK_SOURCE = 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. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) 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. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# 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. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +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 one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +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. If left blank `html' will be used as the default path. + +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). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +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 the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, 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. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, 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. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, 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). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, 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. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag 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 (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# 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. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = YES + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = "HT_UniversalSinkKeyRef=" \ + "HT_UniversalSinkValueRef=" \ + "HT_UniversalSinkKeyValueRef=" \ + "HT_SinkKeyParam=const KeyType &" \ + "HT_SinkValueParam=const ValueType &" \ + "HT_KeyParam=const KeyType &" \ + "HT_ValueParam=const ValueType &" \ + "HT_UniversalSinkItemRef=" \ + "HT_SinkItemParam=const ItemType &" \ + "QQ_UniversalSinkItemRef=" \ + "QQ_SinkItemParam=const ItemType &" + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES diff --git a/iogateway/AbstractMessageIOGateway.cpp b/iogateway/AbstractMessageIOGateway.cpp new file mode 100644 index 00000000..3657be8b --- /dev/null +++ b/iogateway/AbstractMessageIOGateway.cpp @@ -0,0 +1,94 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "iogateway/AbstractMessageIOGateway.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +namespace muscle { + +AbstractMessageIOGateway :: AbstractMessageIOGateway() : _hosed(false), _flushOnEmpty(true) +{ + // empty +} + +AbstractMessageIOGateway :: ~AbstractMessageIOGateway() +{ + // empty +} + +void +AbstractMessageIOGateway :: +SetFlushOnEmpty(bool f) +{ + _flushOnEmpty = f; +} + +bool +AbstractMessageIOGateway :: +IsReadyForInput() const +{ + return true; +} + +uint64 +AbstractMessageIOGateway :: +GetOutputStallLimit() const +{ + return _ioRef() ? _ioRef()->GetOutputStallLimit() : MUSCLE_TIME_NEVER; +} + +void +AbstractMessageIOGateway :: +Shutdown() +{ + if (_ioRef()) _ioRef()->Shutdown(); +} + +void +AbstractMessageIOGateway :: +Reset() +{ + _outgoingMessages.Clear(); + _hosed = false; +} + +/** Used to funnel callbacks from DoInput() back through the AbstractMessageIOGateway's own API, so that subclasses can insert their logic as necessary */ +class ScratchProxyReceiver : public AbstractGatewayMessageReceiver +{ +public: + ScratchProxyReceiver(AbstractMessageIOGateway * gw, AbstractGatewayMessageReceiver * r) : _gw(gw), _r(r) {/* empty */} + + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData) {_gw->SynchronousMessageReceivedFromGateway(msg, userData, *_r);} + virtual void AfterMessageReceivedFromGateway(const MessageRef & msg, void * userData) {_gw->SynchronousAfterMessageReceivedFromGateway(msg, userData, *_r);} + virtual void BeginMessageReceivedFromGatewayBatch() {_gw->SynchronousBeginMessageReceivedFromGatewayBatch(*_r);} + virtual void EndMessageReceivedFromGatewayBatch() {_gw->SynchronousEndMessageReceivedFromGatewayBatch(*_r);} + +private: + AbstractMessageIOGateway * _gw; + AbstractGatewayMessageReceiver * _r; +}; + +status_t +AbstractMessageIOGateway :: +ExecuteSynchronousMessaging(AbstractGatewayMessageReceiver * optReceiver, uint64 timeoutPeriod) +{ + int readFD = GetDataIO()() ? GetDataIO()()->GetReadSelectSocket().GetFileDescriptor() : -1; + int writeFD = GetDataIO()() ? GetDataIO()()->GetWriteSelectSocket().GetFileDescriptor() : -1; + if ((readFD < 0)||(writeFD < 0)) return B_ERROR; // no socket to transmit or receive on! + + ScratchProxyReceiver scratchReceiver(this, optReceiver); + uint64 endTime = (timeoutPeriod == MUSCLE_TIME_NEVER) ? MUSCLE_TIME_NEVER : (GetRunTime64()+timeoutPeriod); + SocketMultiplexer multiplexer; + while(IsStillAwaitingSynchronousMessagingReply()) + { + if (GetRunTime64() >= endTime) return B_ERROR; + if (optReceiver) multiplexer.RegisterSocketForReadReady(readFD); + if (HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(writeFD); + if ((multiplexer.WaitForEvents(endTime) < 0) || + ((multiplexer.IsSocketReadyForWrite(writeFD))&&(DoOutput() < 0)) || + ((multiplexer.IsSocketReadyForRead(readFD))&&(DoInput(scratchReceiver) < 0))) return IsStillAwaitingSynchronousMessagingReply() ? B_ERROR : B_NO_ERROR; + } + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/iogateway/AbstractMessageIOGateway.h b/iogateway/AbstractMessageIOGateway.h new file mode 100644 index 00000000..a0de1ea4 --- /dev/null +++ b/iogateway/AbstractMessageIOGateway.h @@ -0,0 +1,311 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAbstractMessageIOGateway_h +#define MuscleAbstractMessageIOGateway_h + +#include "dataio/DataIO.h" +#include "message/Message.h" +#include "util/Queue.h" +#include "util/RefCount.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/PulseNode.h" + +namespace muscle { + +class AbstractMessageIOGateway; + +/** Interface for any object that wishes to be notified by AbstractMessageIOGateway::DoInput() about received Messages. */ +class AbstractGatewayMessageReceiver +{ +public: + /** Default constructor */ + AbstractGatewayMessageReceiver() : _inBatch(false), _doInputCount(0) {/* empty */} + + /** Destructor */ + virtual ~AbstractGatewayMessageReceiver() {/* empty */} + + /** This method calls MessageReceivedFromGateway() and then AfterMessageReceivedFromGateway(). + * AbstractMessageIOGateway::DoInput() should call this method whenever it has received a new + * Message from its DataIO object.. + * @param msg MessageRef containing the new Message + * @param userData This is a miscellaneous value that may be used by some gateways for various purposes. + * Or it may be ignored if the MessageRef is sufficient. + */ + void CallMessageReceivedFromGateway(const MessageRef & msg, void * userData = NULL) + { + if ((_doInputCount > 0)&&(_inBatch == false)) + { + _inBatch = true; + BeginMessageReceivedFromGatewayBatch(); + } + MessageReceivedFromGateway(msg, userData); + AfterMessageReceivedFromGateway(msg, userData); + } + +protected: + /** Called whenever a new incoming Message is available for us to look at. + * @param msg Reference to the new Message to process. + * @param userData This is a miscellaneous value that may be used by some gateways for various purposes. + */ + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData) = 0; + + /** Called after each call to MessageReceivedFromGateway(). Useful when there + * is something that needs to be done after the subclass has finished its processing. + * Default implementation is a no-op. + * @param msg MessageRef containing the Message that was just passed to MessageReceivedFromGateway() + * @param userData userData value that was just passed to MessageReceivedFromGateway() + */ + virtual void AfterMessageReceivedFromGateway(const MessageRef & msg, void * userData) {(void) msg; (void) userData;} + + /** This method will be called just before MessageReceivedFromGateway() and AfterMessageReceivedFromGateway() + * are called one or more times. Default implementation is a no-op. + */ + virtual void BeginMessageReceivedFromGatewayBatch() {/* empty */} + + /** This method will be called just after MessageReceivedFromGateway() and AfterMessageReceivedFromGateway() + * have been called one or more times. Default implementation is a no-op. + */ + virtual void EndMessageReceivedFromGatewayBatch() {/* empty */} + +private: + friend class AbstractMessageIOGateway; + + void DoInputBegins() {_doInputCount++;} + void DoInputEnds() + { + if ((--_doInputCount == 0)&&(_inBatch)) + { + EndMessageReceivedFromGatewayBatch(); + _inBatch = false; + } + } + + bool _inBatch; + uint32 _doInputCount; +}; + +/** Handy utility class for programs that don't want to define their own custom subclass + * just to gather incoming Messages a gateway -- this receiver just adds the received + * Messages to the tail of the Queue, which your code can then pick up later on at its leisure. + * (For high-bandwidth stuff, this isn't as memory efficient, but for simple programs it's good enough) + */ +class QueueGatewayMessageReceiver : public AbstractGatewayMessageReceiver, public Queue +{ +public: + /** Default constructor */ + QueueGatewayMessageReceiver() {/* empty */} + +protected: + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData) {(void) userData; (void) AddTail(msg);} +}; + +/** + * Abstract base class representing an object that can send/receive + * Messages via a DataIO byte-stream. + */ +class AbstractMessageIOGateway : public RefCountable, public AbstractGatewayMessageReceiver, public PulseNode, private CountedObject +{ +public: + /** Default Constructor. */ + AbstractMessageIOGateway(); + + /** Destructor. */ + virtual ~AbstractMessageIOGateway(); + + /** + * Appends the given message reference to the end of our list of outgoing messages to send. Never blocks. + * @param messageRef A reference to the Message to send out through the gateway. + * @return B_NO_ERROR on success, B_ERROR iff for some reason the message can't be queued (out of memory?) + */ + virtual status_t AddOutgoingMessage(const MessageRef & messageRef) {return _hosed ? B_ERROR : _outgoingMessages.AddTail(messageRef);} + + /** + * Writes some of our outgoing message bytes to the wire. + * Not guaranteed to write all outgoing messages (it will try not to block) + * @note Do not override this method! Override DoOutputImplementation() instead! + * @param maxBytes optional limit on the number of bytes that should be sent out. + * Defaults to MUSCLE_NO_LIMIT (which is a very large number) + * @return The number of bytes written, or a negative value if the connection has been broken + * or some other catastrophic condition has occurred. + */ + int32 DoOutput(uint32 maxBytes = MUSCLE_NO_LIMIT) {return DoOutputImplementation(maxBytes);} + + /** + * Reads some more incoming message bytes from the wire. + * Any time a new Message is received, MessageReceivedFromGateway() should be + * called on the provided AbstractGatewayMessageReceiver to notify him about it. + * @note Do not override this method! Override DoInputImplementation() instead! + * @param receiver An object to call MessageReceivedFromGateway() on whenever a new + * incoming Message is available. + * @param maxBytes optional limit on the number of bytes that should be read in. + * Defaults to MUSCLE_NO_LIMIT (which is a very large number) + * Tries not to block, but may (depending on implementation) + * @return The number of bytes read, or a negative value if the connection has been broken + * or some other catastrophic condition has occurred. + */ + int32 DoInput(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT) + { + receiver.DoInputBegins(); + int32 ret = DoInputImplementation(receiver, maxBytes); + receiver.DoInputEnds(); + return ret; + } + + /** + * Should return true if this gateway is willing to receive from bytes + * from the wire. Should return false if (for some reason) the gateway + * doesn't want to read any bytes right now. The default implementation + * of this method always returns true. + */ + virtual bool IsReadyForInput() const; + + /** + * Should return true if this gateway has bytes that are queued up + * and waiting to be sent across the wire. Should return false if + * there are no bytes ready to send, or if the connection has been + * closed or hosed. + */ + virtual bool HasBytesToOutput() const = 0; + + /** Returns the number of microseconds that output to this gateway's + * client should be allowed to stall for. If the output stalls for + * longer than this amount of time, the connection will be closed. + * Return MUSCLE_TIME_NEVER to disable stall limit checking. + * Default behaviour is to forward this call to the held DataIO object. + */ + virtual uint64 GetOutputStallLimit() const; + + /** Shuts down the gateway. Default implementation calls Shutdown() on + * the held DataIO object. + */ + virtual void Shutdown(); + + /** This method must resets the gateway's encoding and decoding state to its default state. + * Any partially completed sends and receives should be cleared, so that the gateway + * is ready to send and receive fresh data streams. + * Default implementation clears the "hosed" flag and clears the outgoing-Messages queue. + * Subclasses should override this to reset their parse-state variables appropriately too. + */ + virtual void Reset(); + + /** + * By default, the AbstractMessageIOGateway calls Flush() on its DataIO's + * output stream whenever the last outgoing message in the outgoing message queue + * is sent. Call SetFlushOnEmpty(false) to inhibit this behavior (e.g. for bandwidth + * efficiency when low message latency is not a requirement). + * @param flush If true, auto-flushing will be enabled. If false, it will be disabled. + */ + void SetFlushOnEmpty(bool flush); + + /** Accessor for the current state of the FlushOnEmpty flag */ + bool GetFlushOnEmpty() const {return _flushOnEmpty;} + + /** Returns A reference to our outgoing messages queue. */ + Queue & GetOutgoingMessageQueue() {return _outgoingMessages;} + + /** Returns A const reference to our outgoing messages queue. */ + const Queue & GetOutgoingMessageQueue() const {return _outgoingMessages;} + + /** Installs (ref) as the DataIO object we will use for our I/O. + * Typically called by the ReflectServer object. + */ + virtual void SetDataIO(const DataIORef & ref) {_ioRef = ref;} + + /** As above, but returns a reference instead of the raw pointer. */ + const DataIORef & GetDataIO() const {return _ioRef;} + + /** Returns true iff we are hosed--that is, we've experienced an unrecoverable error. */ + bool IsHosed() const {return _hosed;} + + /** This is a convenience method for when you want to do simple synchronous + * (RPC-style) communications. This method will run its own little event loop and not + * return until all of this I/O gateway's outgoing Messages have been sent out. + * Subclasses of the AbstractMessageIOGateway class that support doing so may augment + * this method's logic so that this method does not return until the corresponding + * reply Messages have been received and passed to (optReceiver) as well, but since that + * functionality is dependent on the particulars of the gateway subclass's protocol, + * this base method does not do that. + * @param optReceiver If non-NULL, then any Messages are received from the remote end + * of the during the time that ExecuteSynchronousMessaging() is + * executing will be passed to (optReceiver)'s MessageReceivedFromGateway() + * method. If NULL, then no data will be read from the socket. + * @param timeoutPeriod A timeout period for this method. If left as MUSCLE_TIME_NEVER + * (the default), then no timeout is applied. If set to another value, + * then this method will return B_ERROR after (timeoutPeriod) microseconds + * if the operation hasn't already completed by then. + * @returns B_NO_ERROR on success (all pending outgoing Messages were sent) or B_ERROR + * on failure (usually because the connection was severed while + * outgoing data was still pending) + * @see SynchronousMessageReceivedFromGateway(), SynchronousAfterMessageReceivedFromGateway(), + * SynchronousBeginMessageReceivedFromGatewayBatch(), SynchronousEndMessageReceivedFromGatewayBatch(), + * and IsStillAwaitingSynchronousMessagingReply(). + * @note Even though this is a blocking call, you should still have the DataIO's socket + * set to non-blocking mode, otherwise you risk this call never returning due to a blocking recv(). + */ + virtual status_t ExecuteSynchronousMessaging(AbstractGatewayMessageReceiver * optReceiver, uint64 timeoutPeriod = MUSCLE_TIME_NEVER); + +protected: + /** + * Writes some of our outgoing message bytes to the wire. + * Not guaranteed to write all outgoing messages (it will try not to block) + * @param maxBytes optional limit on the number of bytes that should be sent out. + * Defaults to MUSCLE_NO_LIMIT (which is a very large number) + * @return The number of bytes written, or a negative value if the connection has been broken + * or some other catastrophic condition has occurred. + */ + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT) = 0; + + /** + * Reads some more incoming message bytes from the wire. + * Any time a new Message is received, MessageReceivedFromGateway() should be + * called on the provided AbstractGatewayMessageReceiver to notify him about it. + * @param receiver An object to call MessageReceivedFromGateway() on whenever a new + * incoming Message is available. + * @param maxBytes optional limit on the number of bytes that should be read in. + * Defaults to MUSCLE_NO_LIMIT (which is a very large number) + * Tries not to block, but may (if the held DataIO object is in blocking mode) + * @return The number of bytes read, or a negative value if the connection has been broken + * or some other catastrophic condition has occurred. + */ + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT) = 0; + + /** Call this method to flag this gateway as hosed--that is, to say that an unrecoverable error has occurred. */ + void SetHosed() {_hosed = true;} + +protected: + /** Called by ExecuteSynchronousMessaging() to see if we are still awaiting our reply Messages. Default implementation calls HasBytesToOutput() and returns that value. */ + virtual bool IsStillAwaitingSynchronousMessagingReply() const {return HasBytesToOutput();} + + /** Called by ExecuteSynchronousMessaging() when a Message is received. Default implementation just passes the call on to the like-named method in (r) */ + virtual void SynchronousMessageReceivedFromGateway(const MessageRef & msg, void * userData, AbstractGatewayMessageReceiver & r) {r.MessageReceivedFromGateway(msg, userData);} + + /** Called by ExecuteSynchronousMessaging() after a Message is received. Default implementation just passes the call on to the like-named method in (r) */ + virtual void SynchronousAfterMessageReceivedFromGateway(const MessageRef & msg, void * userData, AbstractGatewayMessageReceiver & r) {r.AfterMessageReceivedFromGateway(msg, userData);} + + /** Called by ExecuteSynchronousMessaging() when a batch of Messages is about to be received. Default implementation just passes the call on to the like-named method in (r) */ + virtual void SynchronousBeginMessageReceivedFromGatewayBatch(AbstractGatewayMessageReceiver & r) {r.BeginMessageReceivedFromGatewayBatch();} + + /** Called by ExecuteSynchronousMessaging() when all Messages in a batch have been received. Default implementation just passes the call on to the like-named method in (r) */ + virtual void SynchronousEndMessageReceivedFromGatewayBatch(AbstractGatewayMessageReceiver & r) {r.EndMessageReceivedFromGatewayBatch();} + + /** Implementation of the AbstracteMessageIOGatewayReceiver interface: calls AddOutgoingMessage(msg). + * This way you can have the input of one gateway go directly to the output of another, without any intermediate step. + */ + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * /*userData*/) {(void) AddOutgoingMessage(msg);} + +private: + friend class ScratchProxyReceiver; + Queue _outgoingMessages; + + DataIORef _ioRef; + + bool _hosed; // set true on error + bool _flushOnEmpty; + + friend class ReflectServer; +}; +DECLARE_REFTYPES(AbstractMessageIOGateway); + +}; // end namespace muscle + +#endif diff --git a/iogateway/MessageIOGateway.cpp b/iogateway/MessageIOGateway.cpp new file mode 100644 index 00000000..19fa5f5c --- /dev/null +++ b/iogateway/MessageIOGateway.cpp @@ -0,0 +1,540 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" // for PR_COMMAND_PING, PR_RESULT_PONG +#include "dataio/TCPSocketDataIO.h" + +namespace muscle { + +MessageIOGateway :: MessageIOGateway(int32 encoding) : + _maxIncomingMessageSize(MUSCLE_NO_LIMIT), + _outgoingEncoding(encoding), + _aboutToFlattenCallback(NULL), _aboutToFlattenCallbackData(NULL), + _flattenedCallback(NULL), _flattenedCallbackData(NULL), + _unflattenedCallback(NULL), _unflattenedCallbackData(NULL) +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + , _sendCodec(NULL), _recvCodec(NULL) +#endif + , _syncPingCounter(0), _pendingSyncPingCounter(-1) +{ + _scratchRecvBuffer.AdoptBuffer(sizeof(_scratchRecvBufferBytes), _scratchRecvBufferBytes); +} + +MessageIOGateway :: ~MessageIOGateway() +{ + (void) _scratchRecvBuffer.ReleaseBuffer(); // otherwise it will try to delete[] _scratchRecvBufferBytes and that would be bad + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + delete _sendCodec; + delete _recvCodec; +#endif +} + +status_t +MessageIOGateway :: +PopNextOutgoingMessage(MessageRef & retMsg) +{ + return GetOutgoingMessageQueue().RemoveHead(retMsg); +} + +// For this method, B_NO_ERROR means "keep sending", and B_ERROR means "stop sending for now", and isn't fatal to the stream +// If there is a fatal error in the stream it will call SetHosed() to indicate that. +status_t +MessageIOGateway :: SendMoreData(int32 & sentBytes, uint32 & maxBytes) +{ + TCHECKPOINT; + + const ByteBuffer * bb = _sendBuffer._buffer(); + int32 attemptSize = muscleMin(maxBytes, bb->GetNumBytes()-_sendBuffer._offset); + int32 numSent = GetDataIO()()->Write(bb->GetBuffer()+_sendBuffer._offset, attemptSize); + if (numSent >= 0) + { + maxBytes -= numSent; + sentBytes += numSent; + _sendBuffer._offset += numSent; + } + else SetHosed(); + + return (numSent < attemptSize) ? B_ERROR : B_NO_ERROR; +} + +int32 +MessageIOGateway :: +DoOutputImplementation(uint32 maxBytes) +{ + TCHECKPOINT; + + int32 sentBytes = 0; + while((maxBytes > 0)&&(IsHosed() == false)) + { + // First, make sure our outgoing byte-buffer has data. If it doesn't, fill it with the next outgoing message. + if (_sendBuffer._buffer() == NULL) + { + while(true) + { + MessageRef nextRef; + if (PopNextOutgoingMessage(nextRef) != B_NO_ERROR) + { + if ((GetFlushOnEmpty())&&(sentBytes > 0)) GetDataIO()()->FlushOutput(); + return sentBytes; // nothing more to send, so we're done! + } + + const Message * nextSendMsg = nextRef(); + if (nextSendMsg) + { + if (_aboutToFlattenCallback) _aboutToFlattenCallback(nextRef, _aboutToFlattenCallbackData); + + _sendBuffer._offset = 0; + _sendBuffer._buffer = FlattenHeaderAndMessage(nextRef); + if (_sendBuffer._buffer() == NULL) {SetHosed(); return -1;} + + if (_flattenedCallback) _flattenedCallback(nextRef, _flattenedCallbackData); + +#ifdef DELIBERATELY_INJECT_ERRORS_INTO_OUTGOING_MESSAGE_FOR_TESTING_ONLY_DONT_ENABLE_THIS_UNLESS_YOU_LIKE_CHAOS + uint32 hs = GetHeaderSize(); + uint32 bs = _sendBuffer._buffer()->GetNumBytes() - hs; + uint32 start = rand()%bs; + uint32 end = (start+5)%bs; + if (start > end) muscleSwap(start, end); + printf("Bork! %u->%u\n", start, end); + for (uint32 i=start; i<=end; i++) _sendBuffer._buffer()->GetBuffer()[i+hs] = (uint8) (rand()%256); +#endif + + break; // now go on to the sending phase + } + } + if (IsHosed()) break; // in case our callbacks called SetHosed() + } + + // At this point, _sendBuffer._buffer() is guaranteed not to be NULL! + const uint32 mtuSize = GetDataIO()()->GetPacketMaximumSize(); + if (mtuSize > 0) + { + const ByteBuffer * bb = _sendBuffer._buffer(); + int32 numSent = GetDataIO()()->Write(bb->GetBuffer(), bb->GetNumBytes()); + if (numSent > 0) + { + maxBytes = (maxBytes>(uint32)numSent)?(maxBytes-numSent):0; + sentBytes += numSent; + _sendBuffer.Reset(); + } + else if (numSent < 0) SetHosed(); + else break; + } + else + { + if (SendMoreData(sentBytes, maxBytes) != B_NO_ERROR) break; // output buffer is temporarily full + if (_sendBuffer._offset == _sendBuffer._buffer()->GetNumBytes()) _sendBuffer.Reset(); + } + } + return IsHosed() ? -1 : sentBytes; +} + +int32 +MessageIOGateway :: +DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + TCHECKPOINT; + + const uint32 hs = GetHeaderSize(); + bool firstTime = true; // always go at least once, to avoid live-lock + int32 readBytes = 0; + while((maxBytes > 0)&&(IsHosed() == false)&&((firstTime)||(IsSuggestedTimeSliceExpired() == false))) + { + firstTime = false; + + const uint32 mtuSize = GetDataIO()()->GetPacketMaximumSize(); + if (mtuSize > 0) + { + // For UDP-style I/O, we'll read all header data and body data at once from a single packet + if (_recvBuffer._buffer() == NULL) + { + (void) _scratchRecvBuffer.SetNumBytes(sizeof(_scratchRecvBufferBytes), false); // return our scratch buffer to its "full size" in case it was sized smaller earlier + _recvBuffer._offset = 0; + _recvBuffer._buffer = (mtuSize<=_scratchRecvBuffer.GetNumBytes()) ? ByteBufferRef(&_scratchRecvBuffer,false) : GetByteBufferFromPool(mtuSize); + if (_recvBuffer._buffer() == NULL) {SetHosed(); break;} // out of memory? + } + + int32 numRead = GetDataIO()()->Read(_recvBuffer._buffer()->GetBuffer(), mtuSize); + if (numRead < 0) {SetHosed(); break;} + else if (numRead > 0) + { + readBytes += numRead; + maxBytes = (maxBytes>(uint32)numRead)?(maxBytes-numRead):0; + (void) _recvBuffer._buffer()->SetNumBytes(numRead, true); // trim off any unused bytes + + // Finished receiving message bytes... now reconstruct that bad boy! + MessageRef msg = UnflattenHeaderAndMessage(_recvBuffer._buffer); + _recvBuffer.Reset(); // reset our state for the next one! + if (msg()) // for UDP, unexpected data shouldn't be fatal + { + if (_unflattenedCallback) _unflattenedCallback(msg, _unflattenedCallbackData); + receiver.CallMessageReceivedFromGateway(msg); + } + } + else break; + } + else + { + // For TCP-style I/O, we need to read the header first, and then the body, in as many steps as it takes + if (_recvBuffer._buffer() == NULL) + { + (void) _scratchRecvBuffer.SetNumBytes(sizeof(_scratchRecvBufferBytes), false); // return our scratch buffer to its "full size" in case it was sized smaller earlier + _recvBuffer._offset = 0; + _recvBuffer._buffer = (hs<=_scratchRecvBuffer.GetNumBytes()) ? ByteBufferRef(&_scratchRecvBuffer,false) : GetByteBufferFromPool(hs); + if (_recvBuffer._buffer() == NULL) {SetHosed(); break;} // out of memory? + } + + ByteBuffer * bb = _recvBuffer._buffer(); // guaranteed not to be NULL, if we got here! + if (_recvBuffer._offset < hs) + { + // We don't have the entire header yet, so try and read some more of it + if (ReceiveMoreData(readBytes, maxBytes, hs) != B_NO_ERROR) break; + if (_recvBuffer._offset >= hs) // how about now? + { + // Now that we have the full header, parse it and allocate space for the message-body-bytes per its instructions + int32 bodySize = GetBodySize(bb->GetBuffer()); + if ((bodySize >= 0)&&(((uint32)bodySize) <= _maxIncomingMessageSize)) + { + int32 availableBodyBytes = bb->GetNumBytes()-hs; + if (bodySize <= availableBodyBytes) (void) bb->SetNumBytes(hs+bodySize, true); // trim off any extra space we don't need + else + { + // Oops, the Message body is greater than our buffer has bytes to store! We're going to need a bigger buffer! + ByteBufferRef bigBuf = GetByteBufferFromPool(hs+bodySize); + if (bigBuf() == NULL) {SetHosed(); break;} + memcpy(bigBuf()->GetBuffer(), bb->GetBuffer(), hs); // copy over the received header bytes to the big buffer now + _recvBuffer._buffer = bigBuf; + bb = bigBuf(); + } + } + else + { + LogTime(MUSCLE_LOG_DEBUG, "MessageIOGateway %p: bodySize " INT32_FORMAT_SPEC " is out of range, limit is " UINT32_FORMAT_SPEC "\n", this, bodySize, _maxIncomingMessageSize); + SetHosed(); + break; + } + } + } + + if (_recvBuffer._offset >= hs) // if we got here, we're ready to read the body of the incoming Message + { + if ((_recvBuffer._offset < bb->GetNumBytes())&&(ReceiveMoreData(readBytes, maxBytes, bb->GetNumBytes()) != B_NO_ERROR)) break; + if (_recvBuffer._offset == bb->GetNumBytes()) + { + // Finished receiving message bytes... now reconstruct that bad boy! + MessageRef msg = UnflattenHeaderAndMessage(_recvBuffer._buffer); + _recvBuffer.Reset(); // reset our state for the next one! + if (msg() == NULL) {SetHosed(); break;} + if (_unflattenedCallback) _unflattenedCallback(msg, _unflattenedCallbackData); + receiver.CallMessageReceivedFromGateway(msg); + } + } + } + } + return IsHosed() ? -1 : readBytes; +} + +// For this method, B_NO_ERROR means "We got all the data we had room for", and B_ERROR +// means "short read". A real network error will also cause SetHosed() to be called. +status_t +MessageIOGateway :: ReceiveMoreData(int32 & readBytes, uint32 & maxBytes, uint32 maxArraySize) +{ + TCHECKPOINT; + + int32 attemptSize = muscleMin(maxBytes, (uint32)((maxArraySize>_recvBuffer._offset)?(maxArraySize-_recvBuffer._offset):0)); + int32 numRead = GetDataIO()()->Read(_recvBuffer._buffer()->GetBuffer()+_recvBuffer._offset, attemptSize); + if (numRead >= 0) + { + maxBytes -= numRead; + readBytes += numRead; + _recvBuffer._offset += numRead; + } + else SetHosed(); + + return (numRead < attemptSize) ? B_ERROR : B_NO_ERROR; +} + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING +ZLibCodec * +MessageIOGateway :: +GetCodec(int32 newEncoding, ZLibCodec * & setCodec) const +{ + TCHECKPOINT; + + if (muscleInRange(newEncoding, (int32)MUSCLE_MESSAGE_ENCODING_ZLIB_1, (int32)MUSCLE_MESSAGE_ENCODING_ZLIB_9)) + { + int newLevel = (newEncoding-MUSCLE_MESSAGE_ENCODING_ZLIB_1)+1; + if ((setCodec == NULL)||(newLevel != setCodec->GetCompressionLevel())) + { + delete setCodec; // oops, encoding change! Throw out the old codec, if any + setCodec = newnothrow ZLibCodec(newLevel); + if (setCodec == NULL) WARN_OUT_OF_MEMORY; + } + return setCodec; + } + return NULL; +} +#endif + +ByteBufferRef +MessageIOGateway :: +FlattenHeaderAndMessage(const MessageRef & msgRef) const +{ + TCHECKPOINT; + + ByteBufferRef ret; + if (msgRef()) + { + uint32 hs = GetHeaderSize(); + ret = GetByteBufferFromPool(hs+msgRef()->FlattenedSize()); + if (ret()) + { + msgRef()->Flatten(ret()->GetBuffer()+hs); + + int32 encoding = MUSCLE_MESSAGE_ENCODING_DEFAULT; + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + if (ret()->GetNumBytes() >= 32) // below 32 bytes, the compression headers usually offset the benefits + { + ZLibCodec * enc = GetCodec(_outgoingEncoding, _sendCodec); + if (enc) + { + ByteBufferRef compressedRef = enc->Deflate(ret()->GetBuffer()+hs, ret()->GetNumBytes()-hs, AreOutgoingMessagesIndependent(), hs); + if (compressedRef()) + { + encoding = MUSCLE_MESSAGE_ENCODING_ZLIB_1+enc->GetCompressionLevel()-1; + ret = compressedRef; + } + else ret.Reset(); // uh oh, the compressor failed + } + } +#endif + + if (ret()) + { + uint32 * lhb = (uint32 *) ret()->GetBuffer(); + lhb[0] = B_HOST_TO_LENDIAN_INT32(ret()->GetNumBytes()-hs); + lhb[1] = B_HOST_TO_LENDIAN_INT32(encoding); + } + } + } + return ret; +} + +MessageRef +MessageIOGateway :: +UnflattenHeaderAndMessage(const ByteBufferRef & bufRef) const +{ + TCHECKPOINT; + + MessageRef ret; + if (bufRef()) + { + ret = GetMessageFromPool(); + if (ret()) + { + uint32 offset = GetHeaderSize(); + + const uint32 * lhb = (const uint32 *) bufRef()->GetBuffer(); + if ((offset+((uint32) B_LENDIAN_TO_HOST_INT32(lhb[0]))) != bufRef()->GetNumBytes()) + { + LogTime(MUSCLE_LOG_DEBUG, "MessageIOGateway %p: Unexpected lhb size " UINT32_FORMAT_SPEC ", expected " INT32_FORMAT_SPEC "\n", this, (uint32) B_LENDIAN_TO_HOST_INT32(lhb[0]), bufRef()->GetNumBytes()-offset); + return MessageRef(); + } + + int32 encoding = B_LENDIAN_TO_HOST_INT32(lhb[1]); + + const ByteBuffer * bb = bufRef(); // default; may be changed below + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + ByteBufferRef expRef; // must be declared outside the brackets below! + ZLibCodec * enc = GetCodec(encoding, _recvCodec); + if (enc) + { + expRef = enc->Inflate(bb->GetBuffer()+offset, bb->GetNumBytes()-offset); + if (expRef()) + { + bb = expRef(); + offset = 0; + } + else + { + LogTime(MUSCLE_LOG_DEBUG, "MessageIOGateway %p: Error inflating compressed byte buffer!\n", this); + bb = NULL; + } + } +#else + if (encoding != MUSCLE_MESSAGE_ENCODING_DEFAULT) bb = NULL; +#endif + + if ((bb == NULL)||(ret()->Unflatten(bb->GetBuffer()+offset, bb->GetNumBytes()-offset) != B_NO_ERROR)) ret.Reset(); + } + } + return ret; +} + +// Returns the size of the pre-flattened-message header section, in bytes. +// The default format has an 8-byte header (4 bytes for encoding ID, 4 bytes for message length) +uint32 +MessageIOGateway :: +GetHeaderSize() const +{ + return 2 * sizeof(uint32); // one long for the encoding ID, and one long for the body length +} + +int32 +MessageIOGateway :: +GetBodySize(const uint8 * headerBuf) const +{ + const uint32 * h = (const uint32 *) headerBuf; + return (muscleInRange((uint32)B_LENDIAN_TO_HOST_INT32(h[1]), (uint32)MUSCLE_MESSAGE_ENCODING_DEFAULT, (uint32)MUSCLE_MESSAGE_ENCODING_END_MARKER-1)) ? (int32)(B_LENDIAN_TO_HOST_INT32(h[0])) : -1; +} + +bool +MessageIOGateway :: +HasBytesToOutput() const +{ + return ((IsHosed() == false)&&((_sendBuffer._buffer())||(GetOutgoingMessageQueue().HasItems()))); +} + +void +MessageIOGateway :: +Reset() +{ + TCHECKPOINT; + + AbstractMessageIOGateway::Reset(); + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + delete _sendCodec; _sendCodec = NULL; + delete _recvCodec; _recvCodec = NULL; +#endif + + _sendBuffer.Reset(); + _recvBuffer.Reset(); +} + +MessageRef MessageIOGateway :: CreateSynchronousPingMessage(uint32 syncPingCounter) const +{ + MessageRef pingMsg = GetMessageFromPool(PR_COMMAND_PING); + return ((pingMsg())&&(pingMsg()->AddInt32("_miosp", syncPingCounter) == B_NO_ERROR)) ? pingMsg : MessageRef(); +} + +status_t MessageIOGateway :: ExecuteSynchronousMessaging(AbstractGatewayMessageReceiver * optReceiver, uint64 timeoutPeriod) +{ + const DataIO * dio = GetDataIO()(); + if ((dio == NULL)||(dio->GetReadSelectSocket().GetFileDescriptor() < 0)||(dio->GetWriteSelectSocket().GetFileDescriptor() < 0)) return B_ERROR; + + MessageRef pingMsg = CreateSynchronousPingMessage(_syncPingCounter); + if ((pingMsg())&&(AddOutgoingMessage(pingMsg) == B_NO_ERROR)) + { + _pendingSyncPingCounter = _syncPingCounter; + _syncPingCounter++; + return AbstractMessageIOGateway::ExecuteSynchronousMessaging(optReceiver, timeoutPeriod); + } + else return B_ERROR; +} + +void MessageIOGateway :: SynchronousMessageReceivedFromGateway(const MessageRef & msg, void * userData, AbstractGatewayMessageReceiver & r) +{ + if ((_pendingSyncPingCounter >= 0)&&(IsSynchronousPongMessage(msg, _pendingSyncPingCounter))) + { + // Yay, we found our pong Message, so we are no longer waiting for one. + _pendingSyncPingCounter = -1; + } + else AbstractMessageIOGateway::SynchronousMessageReceivedFromGateway(msg, userData, r); +} + +bool MessageIOGateway :: IsSynchronousPongMessage(const MessageRef & msg, uint32 pendingSyncPingCounter) const +{ + return ((msg()->what == PR_RESULT_PONG)&&((uint32)msg()->GetInt32("_miosp", -1) == pendingSyncPingCounter)); +} + +MessageRef MessageIOGateway :: ExecuteSynchronousMessageRPCCall(const Message & requestMessage, const IPAddressAndPort & targetIAP, uint64 timeoutPeriod) +{ + MessageRef ret; + + uint64 timeBeforeConnect = GetRunTime64(); + ConstSocketRef s = Connect(targetIAP, NULL, NULL, true, timeoutPeriod); + if (s()) + { + if (timeoutPeriod != MUSCLE_TIME_NEVER) + { + uint64 connectDuration = GetRunTime64()-timeBeforeConnect; + timeoutPeriod = (timeoutPeriod > connectDuration) ? (timeoutPeriod-connectDuration) : 0; + } + + DataIORef oldIO = GetDataIO(); + TCPSocketDataIO tsdio(s, false); + SetDataIO(DataIORef(&tsdio, false)); + QueueGatewayMessageReceiver receiver; + if ((AddOutgoingMessage(MessageRef(const_cast(&requestMessage), false)) == B_NO_ERROR)&&(ExecuteSynchronousMessaging(&receiver, timeoutPeriod) == B_NO_ERROR)) ret = receiver.HasItems() ? receiver.Head() : GetMessageFromPool(); + SetDataIO(oldIO); // restore any previous I/O + } + return ret; +} + +status_t MessageIOGateway :: ExecuteSynchronousMessageSend(const Message & requestMessage, const IPAddressAndPort & targetIAP, uint64 timeoutPeriod) +{ + status_t ret = B_ERROR; + uint64 timeBeforeConnect = GetRunTime64(); + ConstSocketRef s = Connect(targetIAP, NULL, NULL, true, timeoutPeriod); + if (s()) + { + if (timeoutPeriod != MUSCLE_TIME_NEVER) + { + uint64 connectDuration = GetRunTime64()-timeBeforeConnect; + timeoutPeriod = (timeoutPeriod > connectDuration) ? (timeoutPeriod-connectDuration) : 0; + } + + DataIORef oldIO = GetDataIO(); + TCPSocketDataIO tsdio(s, false); + SetDataIO(DataIORef(&tsdio, false)); + QueueGatewayMessageReceiver receiver; + if (AddOutgoingMessage(MessageRef(const_cast(&requestMessage), false)) == B_NO_ERROR) + { + NestCountGuard ncg(_noRPCReply); // so that we'll return as soon as we've sent the request Message, and not wait for a reply Message. + ret = ExecuteSynchronousMessaging(&receiver, timeoutPeriod); + } + SetDataIO(oldIO); + } + return ret; +} + +CountedMessageIOGateway :: CountedMessageIOGateway(int32 outgoingEncoding) : MessageIOGateway(outgoingEncoding), _outgoingByteCount(0) +{ + // empty +} + +status_t CountedMessageIOGateway :: AddOutgoingMessage(const MessageRef & messageRef) +{ + if (MessageIOGateway::AddOutgoingMessage(messageRef) != B_NO_ERROR) return B_ERROR; + + uint32 msgSize = messageRef()?messageRef()->FlattenedSize():0; + if (GetOutgoingMessageQueue().GetNumItems() > 1) _outgoingByteCount += msgSize; + else _outgoingByteCount = msgSize; // semi-paranoia about meddling via GetOutgoingMessageQueue() access + return B_NO_ERROR; +} + +void CountedMessageIOGateway :: Reset() +{ + MessageIOGateway::Reset(); + _outgoingByteCount = 0; +} + +status_t CountedMessageIOGateway :: PopNextOutgoingMessage(MessageRef & ret) +{ + if (MessageIOGateway::PopNextOutgoingMessage(ret) != B_NO_ERROR) return B_ERROR; + + if (GetOutgoingMessageQueue().HasItems()) + { + uint32 retSize = ret()?ret()->FlattenedSize():0; + _outgoingByteCount = (retSize<_outgoingByteCount) ? (_outgoingByteCount-retSize) : 0; // paranoia to avoid underflow + } + else _outgoingByteCount = 0; // semi-paranoia about meddling via GetOutgoingMessageQueue() access + + return B_NO_ERROR; +} + +}; // end namespace muscle diff --git a/iogateway/MessageIOGateway.h b/iogateway/MessageIOGateway.h new file mode 100644 index 00000000..83716dc0 --- /dev/null +++ b/iogateway/MessageIOGateway.h @@ -0,0 +1,385 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleMessageIOGateway_h +#define MuscleMessageIOGateway_h + +#include "iogateway/AbstractMessageIOGateway.h" +#include "util/ByteBuffer.h" +#include "util/NestCount.h" + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING +# include "zlib/ZLibCodec.h" +#endif + +namespace muscle { + +/** + * Encoding IDs. As of MUSCLE 2.40, we support vanilla MUSCLE_MESSAGE_ENCODING_DEFAULT and 9 levels of zlib compression! + */ +enum { + MUSCLE_MESSAGE_ENCODING_DEFAULT = 1164862256, // 'Enc0', /**< just plain ol' flattened Message objects, with no special encoding */ +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + MUSCLE_MESSAGE_ENCODING_ZLIB_1, /**< lowest level of zlib compression (most CPU-efficient) */ + MUSCLE_MESSAGE_ENCODING_ZLIB_2, + MUSCLE_MESSAGE_ENCODING_ZLIB_3, + MUSCLE_MESSAGE_ENCODING_ZLIB_4, + MUSCLE_MESSAGE_ENCODING_ZLIB_5, + MUSCLE_MESSAGE_ENCODING_ZLIB_6, /**< This is the recommended CPU vs space-savings setting for zlib */ + MUSCLE_MESSAGE_ENCODING_ZLIB_7, + MUSCLE_MESSAGE_ENCODING_ZLIB_8, + MUSCLE_MESSAGE_ENCODING_ZLIB_9, /**< highest level of zlib compression (most space-efficient) */ +#endif + MUSCLE_MESSAGE_ENCODING_END_MARKER = MUSCLE_MESSAGE_ENCODING_DEFAULT+10 /**< Not a valid -- just here to mark the end of the range */ +}; + +/** Callback function type for flatten/unflatten notification callbacks */ +typedef void (*MessageFlattenedCallback)(const MessageRef & msgRef, void * userData); + +/** + * A "gateway" object that knows how to send/receive Messages over a wire, via a provided DataIO object. + * May be subclassed to change the byte-level protocol, or used as-is if the default protocol is desired. + * If ZLib compression is desired, be sure to compile with -DMUSCLE_ENABLE_ZLIB_ENCODING + * + * The default protocol format used by this class is: + * -# 4 bytes (uint32) indicating the flattened size of the message + * -# 4 bytes (uint32) indicating the encoding type (should always be MUSCLE_MESSAGE_ENCODING_DEFAULT for now) + * -# n bytes of flattened Message (where n is the value specified in 1) + * -# goto 1 ... + * + * An example flattened Message byte structure is provided at the bottom of the + * MessageIOGateway.h header file. + */ +class MessageIOGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** + * Constructor. + * @param outgoingEncoding The byte-stream format the message should be encoded into. + * Should be one of the MUSCLE_MESSAGE_ENCODING_* values. + * Default is MUSCLE_MESSAGE_ENCODING_DEFAULT, meaning that no + * compression will be done. Note that to use any of the + * MUSCLE_MESSAGE_ENCODING_ZLIB_* encodings, you MUST have + * defined the compiler symbol -DMUSCLE_ENABLE_ZLIB_ENCODING. + */ + MessageIOGateway(int32 outgoingEncoding = MUSCLE_MESSAGE_ENCODING_DEFAULT); + + /** + * Destructor. + * Deletes the held DataIO object. + */ + virtual ~MessageIOGateway(); + + virtual bool HasBytesToOutput() const; + virtual void Reset(); + + /** + * Lets you specify a function that will be called every time an outgoing + * Message is about to be flattened by this gateway. You may alter the + * Message at this time, if you need to. + * @param cb Callback function to call. + * @param ud User data; set this to any value you like. + */ + void SetAboutToFlattenMessageCallback(MessageFlattenedCallback cb, void * ud) {_aboutToFlattenCallback = cb; _aboutToFlattenCallbackData = ud;} + + /** + * Lets you specify a function that will be called every time an outgoing + * Message has been flattened by this gateway. + * @param cb Callback function to call. + * @param ud User data; set this to any value you like. + */ + void SetMessageFlattenedCallback(MessageFlattenedCallback cb, void * ud) {_flattenedCallback = cb; _flattenedCallbackData = ud;} + + /** + * Lets you specify a function that will be called every time an incoming + * Message has been unflattened by this gateway. + * @param cb Callback function to call. + * @param ud User data; set this to any value you like. + */ + void SetMessageUnflattenedCallback(MessageFlattenedCallback cb, void * ud) {_unflattenedCallback = cb; _unflattenedCallbackData = ud;} + + /** + * Lets you specify the maximum allowable size for an incoming flattened Message. + * Doing so lets you limit the amount of memory a remote computer can cause your + * computer to attempt to allocate. Default max message size is MUSCLE_NO_LIMIT + * (or about 4 gigabytes) + * @param maxBytes New incoming message size limit, in bytes. + */ + void SetMaxIncomingMessageSize(uint32 maxBytes) {_maxIncomingMessageSize = maxBytes;} + + /** Returns the current maximum incoming message size, as was set above. */ + uint32 GetMaxIncomingMessageSize() const {return _maxIncomingMessageSize;} + + /** Returns our encoding method, as specified in the constructor or via SetOutgoingEncoding(). */ + int32 GetOutgoingEncoding() const {return _outgoingEncoding;} + + /** Call this to change the encoding this gateway applies to outgoing Messages. + * Note that the encoding change will take place starting with the next Message + * that is actually sent, so if any Messages are currently Queued up to be sent, + * they will be sent using the new encoding. + * Note that to use any of the MUSCLE_MESSAGE_ENCODING_ZLIB_* encodings, + * you MUST have defined the compiler symbol -DMUSCLE_ENABLE_ZLIB_ENCODING. + * @param ec Encoding type to use. Should be one of the MUSCLE_MESSAGE_ENCODING_* constants. + */ + void SetOutgoingEncoding(int32 ec) {_outgoingEncoding = ec;} + + /** Overwritten to augment AbstractMessageIOGateway::ExecuteSynchronousMessaging() + * with some additional logic that prepends a PR_COMMAND_PING to the outgoing Message queue + * and then makes sure that ExecuteSynchronousMessaging() doesn't return until the + * corresponding PR_COMMAND_PONG is received. That way we are guaranteed that + * the server's results are returned before this method returns. + * @param optReceiver optional object to call MessageReceivedFromGateway() on when a reply Message is received. + * @param timeoutPeriod Optional timeout period in microseconds, or MUSCLE_TIME_NEVER if no timeout is requested. + * @returns B_NO_ERROR on success, or B_ERROR on failure (timeout or network error) + */ + virtual status_t ExecuteSynchronousMessaging(AbstractGatewayMessageReceiver * optReceiver, uint64 timeoutPeriod = MUSCLE_TIME_NEVER); + + /** Convenience method: Connects to the specified IPAddressAndPort via TCP, sends the specified Message, waits + * for a reply Message, and returns the reply Message. This is useful if you want a client/server transaction + * to act like a function call, although it is a bit inefficient since the TCP connection is re-established + * and then closed every time this function is called. + * @param requestMessage the request Message to send + * @param targetIAP Where to connect to (via TCP) to send (requestMessage) + * @param timeoutPeriod The maximum amount of time this function should wait for a reply before returning. + * Defaults to MUSCLE_TIME_NEVER, i.e. no timeout. + * @returns A reference to a reply Message, or a NULL MessageRef() if we were unable to connect to the specified + * address, or an empty Message if we connected and send our request okay, but received no reply Message. + */ + MessageRef ExecuteSynchronousMessageRPCCall(const Message & requestMessage, const IPAddressAndPort & targetIAP, uint64 timeoutPeriod = MUSCLE_TIME_NEVER); + + /** This method is similar to ExecuteSynchronousMessageRPCCall(), except that it doesn't wait for a reply Message. + * Instead, it sends the specified (requestMessage), and returns B_NO_ERROR if the Message successfully goes out + * over the TCP socket, or B_ERROR otherwise. + * @param requestMessage the request Message to send + * @param targetIAP Where to connect to (via TCP) to send (requestMessage) + * @param timeoutPeriod The maximum amount of time this function should wait for TCP to connect, before returning. + * @returns B_NO_ERROR if the Message was sent, or B_ERROR if we couldn't connect (or if we connected but couldn't send the data). + * Note that there is no way to know what (if anything) the receiving client did with the Message. + */ + status_t ExecuteSynchronousMessageSend(const Message & requestMessage, const IPAddressAndPort & targetIAP, uint64 timeoutPeriod = MUSCLE_TIME_NEVER); + +protected: + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT); + + /** + * Should flatten the specified Message object into a newly allocated ByteBuffer + * object and return the ByteBufferRef. The returned ByteBufferRef's contents + * should consiste of (GetHeaderSize()) bytes of header, followed by the flattened + * Message data. + * @param msgRef Reference to a Message to flatten into a byte array. + * @return A reference to a ByteBuffer object (containing the appropriate header + * bytes, followed flattened Message data) on success, or a NULL reference on failure. + * The default implementation uses msg.Flatten() and then (optionally) ZLib compression to produce + * the flattened bytes. + */ + virtual ByteBufferRef FlattenHeaderAndMessage(const MessageRef & msgRef) const; + + /** + * Unflattens a specified ByteBuffer object back into a MessageRef object. + * @param bufRef Reference to a ByteBuffer object that contains the appropriate header + * bytes, followed by some flattened Message bytes. + * @returns a Reference to a Message object containing the Message that was encoded in + * the ByteBuffer on success, or a NULL reference on failure. + * The default implementation uses (optional) ZLib decompression (depending on the header bytes) + * and then msg.Unflatten() to produce the Message. + */ + virtual MessageRef UnflattenHeaderAndMessage(const ByteBufferRef & bufRef) const; + + /** + * Returns the size of the pre-flattened-message header section, in bytes. + * The default Message protocol uses an 8-byte header (4 bytes for encoding ID, 4 bytes for message size), + * so the default implementation of this method always returns 8. + */ + virtual uint32 GetHeaderSize() const; + + /** + * Must Extract and returns the buffer body size from the given header. + * Note that the returned size should NOT count the header bytes themselves! + * @param header Points to the header of the message. The header is GetHeaderSize() bytes long. + * @return The number of bytes in the body of the message associated with (header), on success, + * or a negative value to indicate an error (invalid header, etc). + */ + virtual int32 GetBodySize(const uint8 * header) const; + + /** Overridden to return true until our PONG Message is received back */ + virtual bool IsStillAwaitingSynchronousMessagingReply() const {return _noRPCReply.IsInBatch() ? HasBytesToOutput() : (_pendingSyncPingCounter >= 0);} + + /** Overridden to filter out our PONG Message and pass everything else on to (r). */ + virtual void SynchronousMessageReceivedFromGateway(const MessageRef & msg, void * userData, AbstractGatewayMessageReceiver & r); + + /** Allocates and returns a Message to send as a Ping Message for its synchronization. + * Default implementation calls GetMessageFromPool(PR_COMMAND_PING) and adds the tag value as an int32 field. + * @param syncPingCounter the value to add as a tag. + */ + virtual MessageRef CreateSynchronousPingMessage(uint32 syncPingCounter) const; + + /** + * Returns true iff (msg) is a pong-Message corresponding to a ping-Message + * that was created by CreateSynchronousPingMessage(syncPingCounter). + * @param msg a Message received from the remote peer + * @param syncPingCounter The value of the ping-counter that we are interested in checking against. + */ + virtual bool IsSynchronousPongMessage(const MessageRef & msg, uint32 syncPingCounter) const; + + /** + * Removes the next MessageRef from our outgoing Message queue and returns it in (retMsg). + * @param retMsg on success, the next MessageRef to send will be written into this MessageRef. + * @returns B_NO_ERROR on success, or B_ERROR on failure (queue was empty) + */ + virtual status_t PopNextOutgoingMessage(MessageRef & retMsg); + + /** + * Should return true iff we need to make sure that any outgoing Messages that we've deflated + * are inflatable independently of each other. The default method always returns false, since it + * allowed better compression ratios if we can assume the receiver will be re-inflating the + * Messages is FIFO order. However, in some cases it's not possible to make that assumption. + * In those cases, reimplementing this method to return true will cause each outgoing Message + * to be deflated independently of its predecessors, giving more flexibility at the expense of + * less compression. + */ + virtual bool AreOutgoingMessagesIndependent() const {return false;} + +private: +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + ZLibCodec * GetCodec(int32 newEncoding, ZLibCodec * & setCodec) const; +#endif + + class TransferBuffer + { + public: + TransferBuffer() : _offset(0) {/* empty */} + + void Reset() + { + _buffer.Reset(); + _offset = 0; + } + + ByteBufferRef _buffer; + uint32 _offset; + }; + + status_t SendMoreData(int32 & sentBytes, uint32 & maxBytes); + status_t ReceiveMoreData(int32 & readBytes, uint32 & maxBytes, uint32 maxArraySize); + + TransferBuffer _sendBuffer; + TransferBuffer _recvBuffer; + + uint8 _scratchRecvBufferBytes[2048]; // so we can receive smaller Messages without constantly allocating and freeing data + ByteBuffer _scratchRecvBuffer; + + uint32 _maxIncomingMessageSize; + int32 _outgoingEncoding; + + MessageFlattenedCallback _aboutToFlattenCallback; + void * _aboutToFlattenCallbackData; + + MessageFlattenedCallback _flattenedCallback; + void * _flattenedCallbackData; + + MessageFlattenedCallback _unflattenedCallback; + void * _unflattenedCallbackData; + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + mutable ZLibCodec * _sendCodec; + mutable ZLibCodec * _recvCodec; +#endif + + NestCount _noRPCReply; + int32 _syncPingCounter; + int32 _pendingSyncPingCounter; +}; + +/** This class is similar to MessageIOGateway, but it also keep a running tally + * of the total number of bytes of data currently in its outgoing-Messages queue. + * Message sizes are calculated via FlattenedSize(); zlib compression is not + * taken into account. + */ +class CountedMessageIOGateway : public MessageIOGateway +{ +public: + /** + * Constructor. + * @param outgoingEncoding See MessageIOGateway constructor for details. + */ + CountedMessageIOGateway(int32 outgoingEncoding = MUSCLE_MESSAGE_ENCODING_DEFAULT); + + virtual status_t AddOutgoingMessage(const MessageRef & messageRef); + uint32 GetNumOutgoingDataBytes() const {return _outgoingByteCount;} + virtual void Reset(); + +protected: + virtual status_t PopNextOutgoingMessage(MessageRef & ret); + +private: + uint32 _outgoingByteCount; +}; + +////////////////////////////////////////////////////////////////////////////////// +// +// Here is a commented example of a flattened Message's byte structure, using +// the MUSCLE_MESSAGE_ENCODING_DEFAULT encoding. +// +// When one uses a MessageIOGateway with the default encoding to send Messages, +// it will send out series of bytes that looks like this. +// +// Note that this information is only helpful if you are trying to implement +// your own MessageIOGateway-compatible serialization/deserialization code. +// C++, Java, and Python programmers will have a much easier time if they use +// the MessageIOGateway class provided in the MUSCLE archive, rather than +// coding at the byte-stream level. +// +// The Message used in this example has a 'what' code value of 2 and the +// following name/value pairs placed in it: +// +// String field, name="!SnKy" value="/*/*/beshare" +// String field, name="session" value="123" +// String field, name="text" value="Hi!" +// +// Bytes in single quotes represent ASCII characters, bytes without quotes +// means literal decimal byte values. (E.g. '2' means 50 decimal, 2 means 2 decimal) +// +// All occurrences of '0' here indicate the ASCII digit zero (decimal 48), not the letter O. +// +// The bytes shown here should be sent across the TCP socket in +// 'normal reading order': left to right, top to bottom. +// +// 88 0 0 0 (int32, indicates that total message body size is 88 bytes) (***) +// '0' 'c' 'n' 'E' ('Enc0' == MUSCLE_MESSAGE_ENCODING_DEFAULT) (***) +// +// '0' '0' 'M' 'P' ('PM00' == CURRENT_PROTOCOL_VERSION) +// 2 0 0 0 (2 == NET_CLIENT_NEW_CHAT_TEXT, the message's 'what' code) +// 3 0 0 0 (3 == Number of name/value pairs in this message) +// 6 0 0 0 (6 == Length of first name, "!SnKy", include NUL byte) +// '!' 'S' 'n' 'K' (Field name ASCII bytes.... "!SnKy") +// 'y' 0 (last field name ASCII byte and the NUL terminator byte) +// 'R' 'T' 'S' 'C' ('CSTR' == B_STRING_TYPE; i.e. this value is a string) +// 13 0 0 0 (13 == Length of value string including NUL byte) +// '/' '*' '/' '*' (Field value ASCII bytes.... "/*/*/beshare") +// '/' 'b' 'e' 's' (....) +// 'h' 'a' 'r' 'e' (....) +// 0 (NUL terminator byte for the ASCII value) +// 8 0 0 0 (8 == Length of second name, "session", including NUL) +// 's' 'e' 's' 's' (Field name ASCII Bytes.... "session") +// 'i' 'o' 'n' 0 (rest of field name ASCII bytes and NUL terminator) +// 'R' 'T' 'S' 'C' ('CSTR' == B_STRING_TYPE; i.e. this value is a string) +// 4 0 0 0 (4 == Length of value string including NUL byte) +// '1' '2' '3' 0 (Field value ASCII bytes... "123" plus NUL byte) +// 5 0 0 0 (5 == Length of third name, "text", including NUL) +// 't' 'e' 'x' 't' (Field name ASCII bytes... "text") +// 0 (NUL byte terminator for field name) +// 'R' 'T' 'S' 'C' ('CSTR' == B_STRING_TYPE; i.e. this value is a string) +// 4 0 0 0 (3 == Length of value string including NUL byte) +// 'H' 'i' '!' 0 (Field value ASCII Bytes.... "Hi!" plus NUL byte) +// +// [that's the complete byte sequence; to transmit another message, +// you would start again at the top, with the next message's +// message-body-length-count] +// +// (***) The bytes in this field should not be included when tallying the message body size! +// +////////////////////////////////////////////////////////////////////////////////// + +}; // end namespace muscle + +#endif diff --git a/iogateway/PacketTunnelIOGateway.cpp b/iogateway/PacketTunnelIOGateway.cpp new file mode 100644 index 00000000..1adb616f --- /dev/null +++ b/iogateway/PacketTunnelIOGateway.cpp @@ -0,0 +1,248 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/UDPSocketDataIO.h" // for retrieving the source IP address and port, where possible +#include "iogateway/PacketTunnelIOGateway.h" + +namespace muscle { + +// Each chunk header has the following fields in it: +// uint32 magic_number +// uint32 source_exclusion_id +// uint32 message_id +// uint32 subchunk_offset +// uint32 subchunk_size +// uint32 message_total_size +static const uint32 FRAGMENT_HEADER_SIZE = 6*(sizeof(uint32)); + +// The maximum number of bytes of memory to keep in a ByteBuffer to avoid reallocations +static const uint32 MAX_CACHE_SIZE = 20*1024; + +PacketTunnelIOGateway :: PacketTunnelIOGateway(const AbstractMessageIOGatewayRef & slaveGateway, uint32 maxTransferUnit, uint32 magic) : _magic(magic), _maxTransferUnit(muscleMax(maxTransferUnit, FRAGMENT_HEADER_SIZE+1)), _allowMiscData(false), _sexID(0), _slaveGateway(slaveGateway), _outputPacketSize(0), _sendMessageIDCounter(0), _maxIncomingMessageSize(MUSCLE_NO_LIMIT) +{ + _fakeSendIO.SetBuffer(ByteBufferRef(&_fakeSendBuffer, false)); + // _fakeReceiveIO's buffer will be set just before it is used +} + +int32 PacketTunnelIOGateway :: DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + if (_inputPacketBuffer.SetNumBytes(_maxTransferUnit, false) != B_NO_ERROR) return -1; + + bool firstTime = true; + uint32 totalBytesRead = 0; + while((totalBytesRead < maxBytes)&&((firstTime)||(IsSuggestedTimeSliceExpired() == false))) + { + firstTime = false; + + int32 bytesRead = GetDataIO()()->Read(_inputPacketBuffer.GetBuffer(), _inputPacketBuffer.GetNumBytes()); +//printf(" READ " INT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " bytes\n", bytesRead, _inputPacketBuffer.GetNumBytes()); + if (bytesRead > 0) + { + totalBytesRead += bytesRead; + + IPAddressAndPort fromIAP; + UDPSocketDataIO * udpIO = dynamic_cast(GetDataIO()()); + if (udpIO) fromIAP = udpIO->GetSourceOfLastReadPacket(); + + const uint8 * p = (const uint8 *) _inputPacketBuffer.GetBuffer(); + if ((_allowMiscData)&&((bytesRead < (int32)FRAGMENT_HEADER_SIZE)||((uint32)B_LENDIAN_TO_HOST_INT32(*((const uint32 *)p)) != _magic))) + { + // If we're allowed to handle miscellaneous data, we'll just pass it on through verbatim + ByteBuffer temp; + temp.AdoptBuffer(bytesRead, const_cast(p)); + HandleIncomingMessage(receiver, ByteBufferRef(&temp, false), fromIAP); + (void) temp.ReleaseBuffer(); + } + else + { + const uint8 * invalidByte = p+bytesRead; + while(invalidByte-p >= (int32)FRAGMENT_HEADER_SIZE) + { + const uint32 * h32 = (const uint32 *) p; + uint32 magic = B_LENDIAN_TO_HOST_INT32(h32[0]); + uint32 sexID = B_LENDIAN_TO_HOST_INT32(h32[1]); + uint32 messageID = B_LENDIAN_TO_HOST_INT32(h32[2]); + uint32 offset = B_LENDIAN_TO_HOST_INT32(h32[3]); + uint32 chunkSize = B_LENDIAN_TO_HOST_INT32(h32[4]); + uint32 totalSize = B_LENDIAN_TO_HOST_INT32(h32[5]); +//printf(" PARSE magic=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " sex=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " messageID=" UINT32_FORMAT_SPEC " offset=" UINT32_FORMAT_SPEC " chunkSize=" UINT32_FORMAT_SPEC " totalSize=" UINT32_FORMAT_SPEC "\n", magic, _magic, sexID, _sexID, messageID, offset, chunkSize, totalSize); + + p += FRAGMENT_HEADER_SIZE; + if ((magic == _magic)&&((_sexID == 0)||(_sexID != sexID))&&((invalidByte-p >= (int32)chunkSize)&&(totalSize <= _maxIncomingMessageSize))) + { + ReceiveState * rs = _receiveStates.Get(fromIAP); + if (rs == NULL) + { + if (offset == 0) rs = _receiveStates.PutAndGet(fromIAP, ReceiveState(messageID)); + if (rs) + { + rs->_buf = GetByteBufferFromPool(totalSize); + if (rs->_buf() == NULL) + { + _receiveStates.Remove(fromIAP); + rs = NULL; + } + } + } + if (rs) + { + if ((offset == 0)||(messageID != rs->_messageID)) + { + // A new message... start receiving it (but only if we are starting at the beginning) + rs->_messageID = messageID; + rs->_offset = 0; + rs->_buf()->SetNumBytes(totalSize, false); + } + + uint32 rsSize = rs->_buf()->GetNumBytes(); +//printf(" CHECK: offset=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " %s\n", offset, rs->_offset, (offset==rs->_offset)?"":"DISCONTINUITY!!!"); + if ((messageID == rs->_messageID)&&(totalSize == rsSize)&&(offset == rs->_offset)&&(offset+chunkSize <= rsSize)) + { + memcpy(rs->_buf()->GetBuffer()+offset, p, chunkSize); + rs->_offset += chunkSize; + if (rs->_offset == rsSize) + { + HandleIncomingMessage(receiver, rs->_buf, fromIAP); + rs->_offset = 0; + rs->_buf()->Clear(rsSize > MAX_CACHE_SIZE); + } + } + else + { + LogTime(MUSCLE_LOG_DEBUG, "Unknown fragment (" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ") received from %s, ignoring it.\n", messageID, offset, chunkSize, totalSize, fromIAP.ToString()()); + rs->_offset = 0; + rs->_buf()->Clear(rsSize > MAX_CACHE_SIZE); + } + } + p += chunkSize; + } + else break; + } + } + } + else if (bytesRead < 0) return -1; + else break; + } + return totalBytesRead; +} + +void PacketTunnelIOGateway :: HandleIncomingMessage(AbstractGatewayMessageReceiver & receiver, const ByteBufferRef & buf, const IPAddressAndPort & fromIAP) +{ + if (_slaveGateway()) + { + DataIORef oldIO = _slaveGateway()->GetDataIO(); // save slave gateway's old state + + _fakeReceiveIO.SetBuffer(buf); (void) _fakeReceiveIO.Seek(0, DataIO::IO_SEEK_SET); + _slaveGateway()->SetDataIO(DataIORef(&_fakeReceiveIO, false)); + + uint32 slaveBytesRead = 0; + while(slaveBytesRead < buf()->GetNumBytes()) + { + _scratchReceiver = &receiver; + _scratchReceiverArg = (void *) &fromIAP; + int32 nextBytesRead = _slaveGateway()->DoInput(*this, buf()->GetNumBytes()-slaveBytesRead); + if (nextBytesRead > 0) slaveBytesRead += nextBytesRead; + else break; + } + + _slaveGateway()->SetDataIO(oldIO); // restore slave gateway's old state + _fakeReceiveIO.SetBuffer(ByteBufferRef()); + } + else + { + MessageRef inMsg = GetMessageFromPool(); + if ((inMsg())&&(inMsg()->UnflattenFromByteBuffer(*buf()) == B_NO_ERROR)) receiver.CallMessageReceivedFromGateway(inMsg, (void *) &fromIAP); + } +} + +int32 PacketTunnelIOGateway :: DoOutputImplementation(uint32 maxBytes) +{ + if (_outputPacketBuffer.SetNumBytes(_maxTransferUnit, false) != B_NO_ERROR) return -1; + + uint32 totalBytesWritten = 0; + bool firstTime = true; + while((totalBytesWritten < maxBytes)&&((firstTime)||(IsSuggestedTimeSliceExpired() == false))) + { + firstTime = false; + + // Step 1: Add as much data to our output packet buffer as we can fit into it + while((_outputPacketSize+FRAGMENT_HEADER_SIZE < _maxTransferUnit)&&(HasBytesToOutput())) + { + // Demand-create the next send-buffer + if (_currentOutputBuffer() == NULL) + { + MessageRef msg; + if (GetOutgoingMessageQueue().RemoveHead(msg) == B_NO_ERROR) + { + _currentOutputBufferOffset = 0; + _currentOutputBuffer.Reset(); + + if (_slaveGateway()) + { + DataIORef oldIO = _slaveGateway()->GetDataIO(); // save slave gateway's old state + + // Get the slave gateway to generate its output into our ByteBuffer + _fakeSendBuffer.SetNumBytes(0, false); + _fakeSendIO.Seek(0, DataIO::IO_SEEK_SET); + _slaveGateway()->SetDataIO(DataIORef(&_fakeSendIO, false)); + _slaveGateway()->AddOutgoingMessage(msg); + while(_slaveGateway()->DoOutput() > 0) {/* empty */} + + _slaveGateway()->SetDataIO(oldIO); // restore slave gateway's old state + _currentOutputBuffer.SetRef(&_fakeSendBuffer, false); + } + else if (_fakeSendBuffer.SetNumBytes(msg()->FlattenedSize(), false) == B_NO_ERROR) + { + // Default algorithm: Just flatten the Message into the buffer + msg()->Flatten(_fakeSendBuffer.GetBuffer()); + _currentOutputBuffer.SetRef(&_fakeSendBuffer, false); + } + } + } + if (_currentOutputBuffer() == NULL) break; // oops, out of mem? + + uint32 sbSize = _currentOutputBuffer()->GetNumBytes(); + uint32 dataBytesToSend = muscleMin(_maxTransferUnit-(_outputPacketSize+FRAGMENT_HEADER_SIZE), sbSize-_currentOutputBufferOffset); + + uint8 * p = ((uint8 *)_outputPacketBuffer.GetBuffer()) + _outputPacketSize; + uint32 * h32 = (uint32 *) p; + h32[0] = B_HOST_TO_LENDIAN_INT32(_magic); // a well-known magic number, for sanity checking + h32[1] = B_HOST_TO_LENDIAN_INT32(_sexID); // source exclusion ID + h32[2] = B_HOST_TO_LENDIAN_INT32(_sendMessageIDCounter); // message ID tag so the receiver can track what belongs where + h32[3] = B_HOST_TO_LENDIAN_INT32(_currentOutputBufferOffset); // start offset (within its message) for this sub-chunk + h32[4] = B_HOST_TO_LENDIAN_INT32(dataBytesToSend); // size of this sub-chunk + h32[5] = B_HOST_TO_LENDIAN_INT32(sbSize); // total size of this message +//printf("CREATING PACKET magic=" UINT32_FORMAT_SPEC " msgID=" UINT32_FORMAT_SPEC " offset=" UINT32_FORMAT_SPEC " chunkSize=" UINT32_FORMAT_SPEC " totalSize=" UINT32_FORMAT_SPEC "\n", _magic, _sendMessageIDCounter, _currentOutputBufferOffset, dataBytesToSend, sbSize); + memcpy(p+FRAGMENT_HEADER_SIZE, _currentOutputBuffer()->GetBuffer()+_currentOutputBufferOffset, dataBytesToSend); + + _outputPacketSize += (FRAGMENT_HEADER_SIZE+dataBytesToSend); + _currentOutputBufferOffset += dataBytesToSend; + if (_currentOutputBufferOffset == sbSize) + { + _currentOutputBuffer.Reset(); + _fakeSendBuffer.Clear(_fakeSendBuffer.GetNumBytes() > MAX_CACHE_SIZE); // don't keep too much memory around! + _sendMessageIDCounter++; + } + } + + // Step 2: If we have a non-empty packet to send, send it! + if (_outputPacketSize > 0) + { + // If bytesWritten is set to zero, we just hold this buffer until our next call. + int32 bytesWritten = GetDataIO()()->Write(_outputPacketBuffer.GetBuffer(), _outputPacketSize); +//printf("WROTE " INT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " bytes %s\n", bytesWritten, _outputPacketSize, (bytesWritten==(int32)_outputPacketSize)?"":"******** SHORT ***********"); + if (bytesWritten > 0) + { + if (bytesWritten != (int32)_outputPacketSize) LogTime(MUSCLE_LOG_ERROR, "PacketTunnelIOGateway::DoOutput(): Short write! (" INT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " bytes)\n", bytesWritten, _outputPacketSize); + _outputPacketBuffer.Clear(); + _outputPacketSize = 0; + totalBytesWritten += bytesWritten; + } + else if (bytesWritten == 0) break; // no more space to write, for now + else return -1; + } + else break; // nothing more to do! + } + return totalBytesWritten; +} + +}; // end namespace muscle diff --git a/iogateway/PacketTunnelIOGateway.h b/iogateway/PacketTunnelIOGateway.h new file mode 100644 index 00000000..5a296d49 --- /dev/null +++ b/iogateway/PacketTunnelIOGateway.h @@ -0,0 +1,135 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/ByteBufferDataIO.h" +#include "iogateway/AbstractMessageIOGateway.h" + +namespace muscle { + +#define DEFAULT_TUNNEL_IOGATEWAY_MAGIC 1114989680 // 'Budp' + +/** This I/O gateway class is a "wrapper" class that you can use in conjunction with any other + * AbstractMessageIOGateway class. It will take the output of that class and packetize it in + * such a way that the resulting data can be sent efficiently and correctly over an I/O channel that + * would otherwise not accept datagrams larger than a certain size. You can also use it by itself, + * (without a slave I/O gateway), in which case it will use the standard Message::Flatten() encoding. + * + * In particular, this class will combine several small messages into a single packet, for efficiency, + * and also fragment overly-large data into multiple sub-packets, if necessary, in order to keep + * packet size smaller than the physical layer's MTU (Max Transfer Unit) size. Note that this + * class does not do any automated retransmission of lost data, so if you do use it over UDP + * (or some other lossy I/O channel), you will need to handle lost Messages at a higher level. + * If a message fragment is lost over the I/O channel, this class will simply drop the entire message + * and continue. + */ +class PacketTunnelIOGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** @param slaveGateway This is the gateway we will call to generate data to send, etc. + * If you leave this argument unset (or pass in a NULL reference), + * a general-purpose default algorithm will be used. + * @param maxTransferUnit The largest packet size this I/O gateway will be allowed to send. + * Default value is MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET (aka + * 1404 if MUSCLE_AVOID_IPV6 is defined, 1388 otherwise). If the number + * passed in here is less than (FRAGMENT_HEADER_SIZE+1), it will be + * intepreted as (FRAGMENT_HEADER_SIZE+1). (aka 21 bytes) + * @param magic The "magic number" that is expected to be at the beginning of each packet + * sent and received. You can usually leave this as the default, unless you + * are doing several separate instances of this class with different protocols, + * and you want to make sure they don't interfere with each other. + */ + PacketTunnelIOGateway(const AbstractMessageIOGatewayRef & slaveGateway = AbstractMessageIOGatewayRef(), uint32 maxTransferUnit = MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET, uint32 magic = DEFAULT_TUNNEL_IOGATEWAY_MAGIC); + + virtual bool HasBytesToOutput() const {return ((_currentOutputBuffer())||(GetOutgoingMessageQueue().HasItems()));} + + /** Sets our slave gateway. Only necessary if you didn't specify a slave gateway in the constructor. */ + void SetSlaveGateway(const AbstractMessageIOGatewayRef & slaveGateway) {_slaveGateway = slaveGateway;} + + /** Returns our current slave gateway, or a NULL reference if we don't have one. */ + const AbstractMessageIOGatewayRef & GetSlaveGateway() const {return _slaveGateway;} + + /** Sets the maximum size message we will allow ourself to receive. Defaults to MUSCLE_NO_LIMIT. */ + void SetMaxIncomingMessageSize(uint32 messageSize) {_maxIncomingMessageSize = messageSize;} + + /** Returns the current setting of the maximum-message-size value. Default to MUSCLE_NO_LIMIT. */ + uint32 GetMaxIncomingMessageSize() const {return _maxIncomingMessageSize;} + + /** If set to true, any incoming UDP packets that aren't in our packetizer-format will be + * be interpreted as separate, independent incoming messages. If false (the default state), + * then any incoming UDP packets that aren't in the packetizer-format will simply be discarded. + */ + void SetAllowMiscIncomingData(bool allowMisc) {_allowMiscData = allowMisc;} + + /** Returns true iff we are accepting non-packetized incoming UDP messages. */ + bool GetAllowMiscIncomingData() const {return _allowMiscData;} + + /** Sets the source exclusion ID number for this gateway. The source exclusion ID is + * useful when you are broadcasting in such a way that your broadcast packets will come + * back to your own PacketTunnelIOGateway and you don't want to receive them. When this + * value is set to non-zero, any packets we send out will be tagged with this value, and + * any packets that come in tagged with this value will be ignored. + * @param sexID The source-exclusion ID to use. Set to non-zero to enable source-exclusion + * filtering, or to zero to disable it again. Default state is zero. + */ + void SetSourceExclusionID(uint32 sexID) {_sexID = sexID;} + + /** Returns the current source-exclusion ID. See above for details. */ + uint32 GetSourceExclusionID() const {return _sexID;} + +protected: + /** Implemented to receive packets from various sources and re-assemble them together into + * the appropriate Message objects. Note that when MessageReceived() is called on the + * AbstractGatewayMessageReceiver object, the void-pointer argument will point to an + * IPAddressAndPort object that the callee can use to find out where the incoming Message + * came from. + */ + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes); + + /** Implemented to send outgoing Messages in a packet-friendly way... i.e. by chopping up + * too-large Messages, and batching together too-small Messages. + */ + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); + +private: + void HandleIncomingMessage(AbstractGatewayMessageReceiver & receiver, const ByteBufferRef & buf, const IPAddressAndPort & fromIAP); + + const uint32 _magic; // our magic number, used to sanity check packets + const uint32 _maxTransferUnit; // max number of bytes to try to fit in a packet + + bool _allowMiscData; // If true, we'll pass on non-magic UDP packets also, as if they were fragments + uint32 _sexID; + + AbstractMessageIOGatewayRef _slaveGateway; + + ByteBuffer _inputPacketBuffer; + ByteBuffer _outputPacketBuffer; + uint32 _outputPacketSize; + + uint32 _sendMessageIDCounter; + ByteBufferRef _currentOutputBuffer; + uint32 _currentOutputBufferOffset; + + ByteBufferDataIO _fakeSendIO; + ByteBuffer _fakeSendBuffer; + + ByteBufferDataIO _fakeReceiveIO; + uint32 _maxIncomingMessageSize; + + class ReceiveState + { + public: + ReceiveState() : _messageID(0), _offset(0) {/* empty */} + ReceiveState(uint32 messageID) : _messageID(messageID), _offset(0) {/* empty */} + + uint32 _messageID; + uint32 _offset; + ByteBufferRef _buf; + }; + Hashtable _receiveStates; + + // Pass the call back through to our own caller, but with the appropriate argument. + virtual void MessageReceivedFromGateway(const MessageRef & msg, void *) {_scratchReceiver->CallMessageReceivedFromGateway(msg, _scratchReceiverArg);} + AbstractGatewayMessageReceiver * _scratchReceiver; + void * _scratchReceiverArg; +}; + +}; // end namespace muscle diff --git a/iogateway/PlainTextMessageIOGateway.cpp b/iogateway/PlainTextMessageIOGateway.cpp new file mode 100644 index 00000000..e7206fee --- /dev/null +++ b/iogateway/PlainTextMessageIOGateway.cpp @@ -0,0 +1,209 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "iogateway/PlainTextMessageIOGateway.h" + +namespace muscle { + +PlainTextMessageIOGateway :: +PlainTextMessageIOGateway() : _eolString("\r\n"), _prevCharWasCarriageReturn(false), _flushPartialIncomingLines(false) +{ + // empty +} + +PlainTextMessageIOGateway :: +~PlainTextMessageIOGateway() +{ + // empty +} + +int32 +PlainTextMessageIOGateway :: +DoOutputImplementation(uint32 maxBytes) +{ + TCHECKPOINT; + + const Message * msg = _currentSendingMessage(); + if (msg == NULL) + { + // try to get the next message from our queue + (void) GetOutgoingMessageQueue().RemoveHead(_currentSendingMessage); + msg = _currentSendingMessage(); + _currentSendLineIndex = _currentSendOffset = -1; + } + + if (msg) + { + if ((_currentSendOffset < 0)||(_currentSendOffset >= (int32)_currentSendText.Length())) + { + // Try to get the next line of text from our message + if (msg->FindString(PR_NAME_TEXT_LINE, ++_currentSendLineIndex, _currentSendText) == B_NO_ERROR) + { + _currentSendOffset = 0; + _currentSendText += _eolString; + } + else + { + _currentSendingMessage.Reset(); // no more text available? Go to the next message then. + return DoOutputImplementation(maxBytes); + } + } + if ((msg)&&(_currentSendOffset >= 0)&&(_currentSendOffset < (int32)_currentSendText.Length())) + { + // Send as much as we can of the current text line + const char * bytes = _currentSendText.Cstr() + _currentSendOffset; + int32 bytesWritten = GetDataIO()()->Write(bytes, muscleMin(_currentSendText.Length()-_currentSendOffset, maxBytes)); + if (bytesWritten < 0) return -1; + else if (bytesWritten > 0) + { + _currentSendOffset += bytesWritten; + int32 subRet = DoOutputImplementation(maxBytes-bytesWritten); + return (subRet >= 0) ? subRet+bytesWritten : -1; + } + } + } + return 0; +} + +MessageRef +PlainTextMessageIOGateway :: +AddIncomingText(const MessageRef & inMsg, const char * s) +{ + MessageRef ret = inMsg; + if (ret() == NULL) ret = GetMessageFromPool(PR_COMMAND_TEXT_STRINGS); + if (ret()) + { + if (_incomingText.HasChars()) + { + (void) ret()->AddString(PR_NAME_TEXT_LINE, _incomingText.Append(s)); + _incomingText.Clear(); + } + else (void) ret()->AddString(PR_NAME_TEXT_LINE, s); + } + return ret; +} + +int32 +PlainTextMessageIOGateway :: +DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + TCHECKPOINT; + + int32 ret = 0; + const int tempBufSize = 2048; + char buf[tempBufSize]; + int32 bytesRead = GetDataIO()()->Read(buf, muscleMin(maxBytes, (uint32)(sizeof(buf)-1))); + if (bytesRead < 0) + { + FlushInput(receiver); + return -1; + } + if (bytesRead > 0) + { + uint32 filteredBytesRead = bytesRead; + FilterInputBuffer(buf, filteredBytesRead, sizeof(buf)-1); + ret += filteredBytesRead; + buf[filteredBytesRead] = '\0'; + + MessageRef inMsg; // demand-allocated + int32 beginAt = 0; + for (uint32 i=0; iAddString(PR_NAME_TEXT_LINE, _incomingText) == B_NO_ERROR)) + { + _incomingText.Clear(); + receiver.CallMessageReceivedFromGateway(inMsg); + } + } +} + +bool +PlainTextMessageIOGateway :: +HasBytesToOutput() const +{ + return ((_currentSendingMessage() != NULL)||(GetOutgoingMessageQueue().HasItems())); +} + +void +PlainTextMessageIOGateway :: +Reset() +{ + TCHECKPOINT; + + AbstractMessageIOGateway::Reset(); + _currentSendingMessage.Reset(); + _currentSendText.Clear(); + _prevCharWasCarriageReturn = false; + _incomingText.Clear(); +} + +TelnetPlainTextMessageIOGateway :: TelnetPlainTextMessageIOGateway() : _inSubnegotiation(false), _commandBytesLeft(0) +{ + // empty +} + +TelnetPlainTextMessageIOGateway :: ~TelnetPlainTextMessageIOGateway() +{ + // empty +} + +void +TelnetPlainTextMessageIOGateway :: +FilterInputBuffer(char * buf, uint32 & bufLen, uint32 /*maxLen*/) +{ + // Based on the document at http://support.microsoft.com/kb/231866 + static const unsigned char IAC = 255; + static const unsigned char SB = 250; + static const unsigned char SE = 240; + + char * output = buf; + for (uint32 i=0; i 0) {--_commandBytesLeft; keepChar = false;} + if (_inSubnegotiation) keepChar = false; + if (keepChar) *output++ = c; // strip out any telnet control/escape codes + } + bufLen = output-buf; +} + +}; // end namespace muscle diff --git a/iogateway/PlainTextMessageIOGateway.h b/iogateway/PlainTextMessageIOGateway.h new file mode 100644 index 00000000..43833313 --- /dev/null +++ b/iogateway/PlainTextMessageIOGateway.h @@ -0,0 +1,102 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MusclePlainTextMessageIOGateway_h +#define MusclePlainTextMessageIOGateway_h + +#include "iogateway/AbstractMessageIOGateway.h" + +namespace muscle { + +/** This is the name of the string field used to hold text lines. */ +#define PR_NAME_TEXT_LINE "tl" + +/** The 'what' code that will be found in incoming Messages. */ +#define PR_COMMAND_TEXT_STRINGS 1886681204 // 'ptxt' + +/** + * This gateway translates lines of text (separated by "\r", "\n", or "\r\n") into + * Messages. It can be used for "telnet-style" net interactions. + * Incoming and outgoing messages may have one or more strings in their PR_NAME_TEXT_LINE field. + * Each of these strings represents a line of text (separator chars not included) + */ +class PlainTextMessageIOGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** Default constructor */ + PlainTextMessageIOGateway(); + + /** Destructor */ + virtual ~PlainTextMessageIOGateway(); + + virtual bool HasBytesToOutput() const; + virtual void Reset(); + + /** Set the end-of-line string to be attached to outgoing text lines. + * @param str New end-of-line string to use. (Default value is "\r\n") + */ + virtual void SetOutgoingEndOfLineString(const char * str) {_eolString = str;} + + /** If set to true, then any "leftover" text after the last carriage return + * will be added to the incoming Message. If false (the default), then incoming + * text without a carriage return will be buffered internally until the next + * carriage return is received. + */ + void SetFlushPartialIncomingLines(bool f) {_flushPartialIncomingLines = f;} + + /** Returns the flush-partial-incoming-lines value, as set by SetFlushPartialIncomingLines(). */ + bool GetFlushPartialIncomingLines() const {return _flushPartialIncomingLines;} + + /** Force any pending input to be immediately flushed out + * @param receiver The object to call MessageReceivedFromGateway() on, if necessary. + */ + void FlushInput(AbstractGatewayMessageReceiver & receiver); + +protected: + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT); + + /** Called after each block of data is read from the IO device. Default implementation + * is a no-op. A subclass may override this to modify the input data, if necessary. + * @param buf a pointer to the just-read data, to be inspected and/or modified. + * @param bufLen current number of valid bytes in the buffer. May be modified by method code. + * @param maxLen The total size of (buf). (bufLen) must not be set greater than this value. + */ + virtual void FilterInputBuffer(char * buf, uint32 & bufLen, uint32 maxLen); + +private: + MessageRef AddIncomingText(const MessageRef & msg, const char * s); + + MessageRef _currentSendingMessage; + String _currentSendText; + String _eolString; + int32 _currentSendLineIndex; + int32 _currentSendOffset; + bool _prevCharWasCarriageReturn; + String _incomingText; + bool _flushPartialIncomingLines; +}; + +/** This class is the same as a PlainTextMessageIOGateway, except that + * some filtering logic has been added to strip out telnet control codes. + * This class is useful when accepting TCP connections from telnet clients. + */ +class TelnetPlainTextMessageIOGateway : public PlainTextMessageIOGateway +{ +public: + /** Default constructor */ + TelnetPlainTextMessageIOGateway(); + + /** Destructor */ + virtual ~TelnetPlainTextMessageIOGateway(); + +protected: + virtual void FilterInputBuffer(char * buf, uint32 & bufLen, uint32 maxLen); + +private: + bool _inSubnegotiation; + int _commandBytesLeft; +}; + +}; // end namespace muscle + +#endif diff --git a/iogateway/RawDataMessageIOGateway.cpp b/iogateway/RawDataMessageIOGateway.cpp new file mode 100644 index 00000000..f773ba7f --- /dev/null +++ b/iogateway/RawDataMessageIOGateway.cpp @@ -0,0 +1,256 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "iogateway/RawDataMessageIOGateway.h" + +namespace muscle { + +RawDataMessageIOGateway :: +RawDataMessageIOGateway(uint32 minChunkSize, uint32 maxChunkSize) : _recvScratchSpace(NULL), _minChunkSize(minChunkSize), _maxChunkSize(maxChunkSize) +{ + // empty +} + +RawDataMessageIOGateway :: +~RawDataMessageIOGateway() +{ + delete [] _recvScratchSpace; +} + +int32 +RawDataMessageIOGateway :: +DoOutputImplementation(uint32 maxBytes) +{ + TCHECKPOINT; + + const Message * msg = _sendMsgRef(); + if (msg == NULL) + { + // try to get the next message from our queue + _sendMsgRef = PopNextOutgoingMessage(); + msg = _sendMsgRef(); + _sendBufLength = _sendBufIndex = _sendBufByteOffset = -1; + } + + if (msg) + { + if ((_sendBufByteOffset < 0)||(_sendBufByteOffset >= _sendBufLength)) + { + // Try to get the next field from our message message + if (msg->FindData(PR_NAME_DATA_CHUNKS, B_ANY_TYPE, ++_sendBufIndex, &_sendBuf, (uint32*)(&_sendBufLength)) == B_NO_ERROR) _sendBufByteOffset = 0; + else + { + _sendMsgRef.Reset(); // no more data available? Go to the next message then. + return DoOutputImplementation(maxBytes); + } + } + + if ((_sendBufByteOffset >= 0)&&(_sendBufByteOffset < _sendBufLength)) + { + uint32 mtuSize = GetDataIO()()->GetPacketMaximumSize(); + if (mtuSize > 0) + { + // UDP mode -- send each data chunk as its own UDP packet + int32 bytesWritten = GetDataIO()()->Write(_sendBuf, muscleMin((uint32)_sendBufLength, mtuSize)); + if (bytesWritten > 0) + { + _sendBufByteOffset = _sendBufLength; // We don't support partial sends for UDP style, so pretend the whole thing was sent + int32 subRet = DoOutputImplementation((maxBytes>(uint32)bytesWritten)?(maxBytes-bytesWritten):0); + return (subRet >= 0) ? subRet+bytesWritten : -1; + } + else if (bytesWritten < 0) return -1; + } + else + { + // TCP mode -- send as much as we can of the current data block + int32 bytesWritten = GetDataIO()()->Write(&((char *)_sendBuf)[_sendBufByteOffset], muscleMin(maxBytes, (uint32) (_sendBufLength-_sendBufByteOffset))); + if (bytesWritten < 0) return -1; + else if (bytesWritten > 0) + { + _sendBufByteOffset += bytesWritten; + int32 subRet = DoOutputImplementation(maxBytes-bytesWritten); + return (subRet >= 0) ? subRet+bytesWritten : -1; + } + } + } + } + return 0; +} + + +int32 +RawDataMessageIOGateway :: +DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + TCHECKPOINT; + + uint32 mtuSize = GetDataIO()()->GetPacketMaximumSize(); + int32 ret = 0; + if (mtuSize > 0) + { + // UDP mode: Each UDP packet is represented as a Message containing one data chunk + while(maxBytes > 0) + { + ByteBufferRef bufRef = GetByteBufferFromPool(mtuSize); + if (bufRef() == NULL) return -1; // out of memory? + + int32 bytesRead = GetDataIO()()->Read(bufRef()->GetBuffer(), mtuSize); + if (bytesRead > 0) + { + (void) bufRef()->SetNumBytes(bytesRead, true); + MessageRef msg = GetMessageFromPool(PR_COMMAND_RAW_DATA); + if ((msg())&&(msg()->AddFlat(PR_NAME_DATA_CHUNKS, bufRef) == B_NO_ERROR)) + { + ret += bytesRead; + maxBytes = (maxBytes>(uint32)bytesRead) ? (maxBytes-bytesRead) : 0; + receiver.CallMessageReceivedFromGateway(msg); // Call receive immediately; that way he can found out the source via GetDataIO()()->GetSourceOfLastReadPacket() if necessary + } + } + else if (bytesRead < 0) return -1; + else break; + } + } + else + { + // TCP mode: read in as a stream + Message * inMsg = _recvMsgRef(); + if (_minChunkSize > 0) + { + // Minimum-chunk-size mode: we read bytes directly into the Message's data field until it is full, then + // forward that message on to the user code and start the next. Advantage of this is: no data-copying necessary! + if (inMsg == NULL) + { + _recvMsgRef = GetMessageFromPool(PR_COMMAND_RAW_DATA); + inMsg = _recvMsgRef(); + if (inMsg) + { + if ((inMsg->AddData(PR_NAME_DATA_CHUNKS, B_RAW_TYPE, NULL, _minChunkSize) == B_NO_ERROR)&& + (inMsg->FindDataPointer(PR_NAME_DATA_CHUNKS, B_RAW_TYPE, &_recvBuf, (uint32*)&_recvBufLength) == B_NO_ERROR)) _recvBufByteOffset = 0; + else + { + _recvMsgRef.Reset(); + return -1; // oops, no mem? + } + } + } + if (inMsg) + { + int32 bytesRead = GetDataIO()()->Read(&((char*)_recvBuf)[_recvBufByteOffset], muscleMin(maxBytes, (uint32)(_recvBufLength-_recvBufByteOffset))); + if (bytesRead < 0) return -1; + else if (bytesRead > 0) + { + ret += bytesRead; + _recvBufByteOffset += bytesRead; + if (_recvBufByteOffset == _recvBufLength) + { + // This buffer is full... forward it on to the user, and start receiving the next one. + receiver.CallMessageReceivedFromGateway(_recvMsgRef); + _recvMsgRef.Reset(); + int32 subRet = IsSuggestedTimeSliceExpired() ? 0 : DoInputImplementation(receiver, maxBytes-bytesRead); + return (subRet >= 0) ? (ret+subRet) : -1; + } + } + } + } + else + { + // Immediate-forward mode... Read data into a temporary buffer, and immediately forward it to the user. + if (_recvScratchSpace == NULL) + { + // demand-allocate a scratch buffer + const uint32 maxScratchSpaceSize = 8192; // we probably won't ever get more than this much at once anyway + _recvScratchSpaceSize = (_maxChunkSize < maxScratchSpaceSize) ? _maxChunkSize : maxScratchSpaceSize; + _recvScratchSpace = newnothrow_array(uint8, _recvScratchSpaceSize); + if (_recvScratchSpace == NULL) + { + WARN_OUT_OF_MEMORY; + return -1; + } + } + + int32 bytesRead = GetDataIO()()->Read(_recvScratchSpace, muscleMin(_recvScratchSpaceSize, maxBytes)); + if (bytesRead < 0) return -1; + else if (bytesRead > 0) + { + ret += bytesRead; + MessageRef ref = GetMessageFromPool(PR_COMMAND_RAW_DATA); + if ((ref())&&(ref()->AddData(PR_NAME_DATA_CHUNKS, B_RAW_TYPE, _recvScratchSpace, bytesRead) == B_NO_ERROR)) receiver.CallMessageReceivedFromGateway(ref); + // note: don't recurse here! It would be bad (tm) on a fast feed since we might never return + } + } + } + return ret; +} + +bool +RawDataMessageIOGateway :: +HasBytesToOutput() const +{ + return ((_sendMsgRef())||(GetOutgoingMessageQueue().HasItems())); +} + +void +RawDataMessageIOGateway :: +Reset() +{ + TCHECKPOINT; + + AbstractMessageIOGateway::Reset(); + _sendMsgRef.Reset(); + _recvMsgRef.Reset(); +} + +MessageRef +RawDataMessageIOGateway :: +PopNextOutgoingMessage() +{ + return GetOutgoingMessageQueue().RemoveHeadWithDefault(); +} + +CountedRawDataMessageIOGateway :: CountedRawDataMessageIOGateway(uint32 minChunkSize, uint32 maxChunkSize) : RawDataMessageIOGateway(minChunkSize, maxChunkSize), _outgoingByteCount(0) +{ + // empty +} + +status_t CountedRawDataMessageIOGateway :: AddOutgoingMessage(const MessageRef & messageRef) +{ + if (RawDataMessageIOGateway::AddOutgoingMessage(messageRef) != B_NO_ERROR) return B_ERROR; + + uint32 msgSize = GetNumRawBytesInMessage(messageRef); + if (GetOutgoingMessageQueue().GetNumItems() > 1) _outgoingByteCount += msgSize; + else _outgoingByteCount = msgSize; // semi-paranoia about meddling via GetOutgoingMessageQueue() access + return B_NO_ERROR; +} + +void CountedRawDataMessageIOGateway :: Reset() +{ + RawDataMessageIOGateway::Reset(); + _outgoingByteCount = 0; +} + +MessageRef CountedRawDataMessageIOGateway :: PopNextOutgoingMessage() +{ + MessageRef ret = RawDataMessageIOGateway::PopNextOutgoingMessage(); + if (GetOutgoingMessageQueue().HasItems()) + { + uint32 retSize = GetNumRawBytesInMessage(ret); + _outgoingByteCount = (retSize<_outgoingByteCount) ? (_outgoingByteCount-retSize) : 0; // paranoia to avoid underflow + } + else _outgoingByteCount = 0; // semi-paranoia about meddling via GetOutgoingMessageQueue() access + + return ret; +} + +uint32 CountedRawDataMessageIOGateway :: GetNumRawBytesInMessage(const MessageRef & messageRef) const +{ + if (messageRef()) + { + uint32 count = 0; + const void * junk; + uint32 temp; + for (int32 i=0; messageRef()->FindData(PR_NAME_DATA_CHUNKS, B_ANY_TYPE, i, &junk, &temp) == B_NO_ERROR; i++) count += temp; + return count; + } + else return 0; +} + +}; // end namespace muscle diff --git a/iogateway/RawDataMessageIOGateway.h b/iogateway/RawDataMessageIOGateway.h new file mode 100644 index 00000000..d72ab197 --- /dev/null +++ b/iogateway/RawDataMessageIOGateway.h @@ -0,0 +1,95 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleRawDataMessageIOGateway_h +#define MuscleRawDataMessageIOGateway_h + +#include "iogateway/AbstractMessageIOGateway.h" + +namespace muscle { + +/** This is the name of the field used to hold data chunks */ +#define PR_NAME_DATA_CHUNKS "rd" + +/** The 'what' code that will be found in incoming Messages. */ +#define PR_COMMAND_RAW_DATA 1919181923 // 'rddc' + +/** + * This gateway is very crude; it can be used to write raw data to a TCP socket, and + * to retrieve data from the socket in chunks of a specified size range. + */ +class RawDataMessageIOGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** Constructor. + * @param minChunkSize Don't return any data in chunks smaller than this. Defaults to zero. + * @param maxChunkSize Don't return any data in chunks larger than this. Defaults to the largest possible uint32 value. + */ + RawDataMessageIOGateway(uint32 minChunkSize=0, uint32 maxChunkSize=MUSCLE_NO_LIMIT); + + /** Destructor */ + virtual ~RawDataMessageIOGateway(); + + virtual bool HasBytesToOutput() const; + virtual void Reset(); + +protected: + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT); + + /** Removes the next MessageRef from the head of our outgoing-Messages + * queue and returns it. Returns a NULL MessageRef if there is no + * outgoing Message in the queue. + * (broken out into a virtual method so its behavior can be modified + * by subclasses, if necessary) + */ + virtual MessageRef PopNextOutgoingMessage(); + +private: + void FlushPendingInput(); + + MessageRef _sendMsgRef; + const void * _sendBuf; + int32 _sendBufLength; // # of bytes in current buffer + int32 _sendBufIndex; // index of the buffer currently being sent + int32 _sendBufByteOffset; // Index of Next byte to send in the current buffer + + MessageRef _recvMsgRef; + void * _recvBuf; + int32 _recvBufLength; + int32 _recvBufByteOffset; + + uint8 * _recvScratchSpace; // demand-allocated + uint32 _recvScratchSpaceSize; + + uint32 _minChunkSize; + uint32 _maxChunkSize; +}; + +/** + * This class is the same as a RawDataMessageIOGateway, except that it is instrumented + * to keep track of the number of bytes of raw data currently in its outgoing-Message + * queue. + */ +class CountedRawDataMessageIOGateway : public RawDataMessageIOGateway +{ +public: + CountedRawDataMessageIOGateway(uint32 minChunkSize=0, uint32 maxChunkSize=MUSCLE_NO_LIMIT); + + virtual status_t AddOutgoingMessage(const MessageRef & messageRef); + + uint32 GetNumOutgoingDataBytes() const {return _outgoingByteCount;} + + virtual void Reset(); + +protected: + virtual MessageRef PopNextOutgoingMessage(); + +private: + uint32 GetNumRawBytesInMessage(const MessageRef & messageRef) const; + + uint32 _outgoingByteCount; +}; + +}; // end namespace muscle + +#endif diff --git a/iogateway/SLIPFramedDataMessageIOGateway.cpp b/iogateway/SLIPFramedDataMessageIOGateway.cpp new file mode 100644 index 00000000..a9519d0a --- /dev/null +++ b/iogateway/SLIPFramedDataMessageIOGateway.cpp @@ -0,0 +1,164 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "iogateway/SLIPFramedDataMessageIOGateway.h" + +namespace muscle { + +SLIPFramedDataMessageIOGateway :: SLIPFramedDataMessageIOGateway() : _lastReceivedCharWasEscape(false) +{ + // empty +} + +SLIPFramedDataMessageIOGateway :: ~SLIPFramedDataMessageIOGateway() +{ + // empty +} + +int32 SLIPFramedDataMessageIOGateway :: DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + int32 ret = RawDataMessageIOGateway::DoInputImplementation(*this, maxBytes); + if (_pendingMessage()) + { + MessageRef msg; + muscleSwap(_pendingMessage, msg); // paranoia wrt re-entrancy + receiver.CallMessageReceivedFromGateway(msg); + } + return ret; +} + +void SLIPFramedDataMessageIOGateway :: Reset() +{ + AbstractMessageIOGateway::Reset(); + _lastReceivedCharWasEscape = false; + _pendingBuffer.Reset(); + _pendingMessage.Reset(); +} + +static const uint8 SLIP_END = 0300; // yes, octal constants +static const uint8 SLIP_ESC = 0333; // straight out of the RFC +static const uint8 SLIP_ESCAPE_END = 0334; +static const uint8 SLIP_ESCAPE_ESC = 0335; + +static ByteBufferRef SLIPEncodeBytes(const uint8 * bytes, uint32 numBytes) +{ + // First, calculate how many bytes the SLIP'd buffer will need to hold + uint32 numSLIPBytes = 2; // 1 for the SLIP_END byte at the beginning, and one for the SLIP_END at the end. + for (uint32 i=0; iGetBuffer(); + *out++ = SLIP_END; + for (uint32 i=0; iRemoveName(PR_NAME_DATA_CHUNKS); // make sure we don't modify the field object in (msg) + + // slipMsg will be like (msg), except that we've slip-encoded each data item + const uint8 * buf; + uint32 numBytes; + for (int32 i=0; msg()->FindData(PR_NAME_DATA_CHUNKS, B_ANY_TYPE, i, (const void **) &buf, &numBytes) == B_NO_ERROR; i++) + { + ByteBufferRef slipData = SLIPEncodeBytes(buf, numBytes); + if ((slipData()==NULL)||(slipMsg()->AddFlat(PR_NAME_DATA_CHUNKS, slipData) != B_NO_ERROR)) return MessageRef(); + } + + return slipMsg; +} + +void SLIPFramedDataMessageIOGateway :: AddPendingByte(uint8 b) +{ + if (_pendingBuffer() == NULL) + { + _pendingBuffer = GetByteBufferFromPool(256); // An arbitrary initial size, since I can't think of any good heuristic + if (_pendingBuffer()) _pendingBuffer()->Clear(false); // but make it look empty + else return; // out of memory? + } + (void) _pendingBuffer()->AppendByte(b); +} + +// This proxy implementation receives raw data from the superclass and SLIP-decodes it, building up a Message full of decoded data to send to our own caller later. +void SLIPFramedDataMessageIOGateway :: MessageReceivedFromGateway(const MessageRef & msg, void * /*userData*/) +{ + const uint8 * buf; + uint32 numBytes; + for (int32 x=0; msg()->FindData(PR_NAME_DATA_CHUNKS, B_ANY_TYPE, x, (const void **) &buf, &numBytes) == B_NO_ERROR; x++) + { + for (uint32 i=0; iGetNumBytes() > 0)) + { + if (_pendingMessage() == NULL) _pendingMessage = GetMessageFromPool(msg()->what); + if (_pendingMessage()) (void) _pendingMessage()->AddFlat(PR_NAME_DATA_CHUNKS, _pendingBuffer); + _pendingBuffer.Reset(); + } + break; + + case SLIP_ESC: + // do nothing + break; + + default: + AddPendingByte(b); + break; + } + _lastReceivedCharWasEscape = (b==SLIP_ESC); + } + } + } +} + +}; // end namespace muscle diff --git a/iogateway/SLIPFramedDataMessageIOGateway.h b/iogateway/SLIPFramedDataMessageIOGateway.h new file mode 100644 index 00000000..4286135a --- /dev/null +++ b/iogateway/SLIPFramedDataMessageIOGateway.h @@ -0,0 +1,52 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSLIPFramedDataMessageIOGateway_h +#define MuscleSLIPFramedDataMessageIOGateway_h + +#include "iogateway/RawDataMessageIOGateway.h" +#include "util/ByteBuffer.h" + +namespace muscle { + +/** + * This gateway is similar to the RawDataMessageIOGateway, except that + * it encodes outgoing data using SLIP data framing (RFC 1055), and also + * it parses incoming data as SLIP-framed data and decodes it before + * passing it back to the calling code. + * + * Note that this gateway assumes that each item in the PR_NAME_DATA_CHUNKS + * field is to be SLIP-encoded into its own SLIP frame, so you may need to + * be a bit careful about how you segment your outgoing data. + */ +class SLIPFramedDataMessageIOGateway : public RawDataMessageIOGateway, private CountedObject +{ +public: + /** Constructor. */ + SLIPFramedDataMessageIOGateway(); + + /** Destructor */ + virtual ~SLIPFramedDataMessageIOGateway(); + + virtual void Reset(); + +protected: + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + +protected: + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT); + + /** Overridden to SLIP-encode the popped Message before returning it. */ + virtual MessageRef PopNextOutgoingMessage(); + +private: + void AddPendingByte(uint8 b); + + // State used to decode incoming SLIP data + ByteBufferRef _pendingBuffer; + MessageRef _pendingMessage; + bool _lastReceivedCharWasEscape; +}; + +}; // end namespace muscle + +#endif diff --git a/iogateway/SSLSocketAdapterGateway.cpp b/iogateway/SSLSocketAdapterGateway.cpp new file mode 100644 index 00000000..35cc80bd --- /dev/null +++ b/iogateway/SSLSocketAdapterGateway.cpp @@ -0,0 +1,95 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/SSLSocketDataIO.h" +#include "iogateway/SSLSocketAdapterGateway.h" + +namespace muscle { + +SSLSocketAdapterGateway :: SSLSocketAdapterGateway(const AbstractMessageIOGatewayRef & slaveGateway) +{ + SetSlaveGateway(slaveGateway); +} + +SSLSocketAdapterGateway :: ~SSLSocketAdapterGateway() +{ + // empty +} + +void SSLSocketAdapterGateway :: SetDataIO(const DataIORef & ref) +{ + AbstractMessageIOGateway::SetDataIO(ref); + if (_slaveGateway()) _slaveGateway()->SetDataIO(ref); +} + +void SSLSocketAdapterGateway :: SetSlaveGateway(const AbstractMessageIOGatewayRef & slaveGateway) +{ + if (_slaveGateway()) _slaveGateway()->SetDataIO(DataIORef()); + _slaveGateway = slaveGateway; + if (_slaveGateway()) _slaveGateway()->SetDataIO(GetDataIO()); +} + +status_t SSLSocketAdapterGateway :: AddOutgoingMessage(const MessageRef & messageRef) +{ + return _slaveGateway()->AddOutgoingMessage(messageRef); +} + +bool SSLSocketAdapterGateway :: IsReadyForInput() const +{ + return ((_sslMessages.HasItems()) || ((GetSSLState() & (SSLSocketDataIO::SSL_STATE_READ_WANTS_READABLE_SOCKET | SSLSocketDataIO::SSL_STATE_WRITE_WANTS_READABLE_SOCKET)) != 0) || ((_slaveGateway())&&(_slaveGateway()->IsReadyForInput()))); +} + +bool SSLSocketAdapterGateway :: HasBytesToOutput() const +{ + return (((GetSSLState() & (SSLSocketDataIO::SSL_STATE_READ_WANTS_WRITEABLE_SOCKET | SSLSocketDataIO::SSL_STATE_WRITE_WANTS_WRITEABLE_SOCKET)) != 0) || ((_slaveGateway())&&(_slaveGateway()->HasBytesToOutput()))); +} + +uint64 SSLSocketAdapterGateway :: GetOutputStallLimit() const +{ + return _slaveGateway() ? _slaveGateway()->GetOutputStallLimit() : MUSCLE_TIME_NEVER; +} + +void SSLSocketAdapterGateway :: Shutdown() +{ + if (_slaveGateway()) _slaveGateway()->Shutdown(); +} + +void SSLSocketAdapterGateway :: Reset() +{ + if (_slaveGateway()) _slaveGateway()->Reset(); +} + +int32 SSLSocketAdapterGateway :: DoOutputImplementation(uint32 maxBytes) +{ + if ((GetSSLState() & SSLSocketDataIO::SSL_STATE_READ_WANTS_WRITEABLE_SOCKET) != 0) + { + if ((_slaveGateway()==NULL)||(_slaveGateway()->DoInput(_sslMessages) < 0)) return -1; + if (_sslMessages.HasItems()) SetSSLForceReadReady(true); // to make sure that our DoInput() method gets called ASAP + } + return _slaveGateway() ? _slaveGateway()->DoOutput(maxBytes) : -1; +} + +int32 SSLSocketAdapterGateway :: DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + if (_sslMessages.HasItems()) + { + SetSSLForceReadReady(false); + MessageRef msg; while(_sslMessages.RemoveHead(msg) == B_NO_ERROR) receiver.CallMessageReceivedFromGateway(msg); + } + + if (((GetSSLState() & SSLSocketDataIO::SSL_STATE_WRITE_WANTS_READABLE_SOCKET) != 0)&&((_slaveGateway()==NULL)||(_slaveGateway()->DoOutput() < 0))) return -1; + return _slaveGateway() ? _slaveGateway()->DoInput(receiver, maxBytes) : -1; +} + +uint32 SSLSocketAdapterGateway :: GetSSLState() const +{ + const SSLSocketDataIO * dio = dynamic_cast(GetDataIO()()); + return dio ? dio->_sslState : 0; +} + +void SSLSocketAdapterGateway :: SetSSLForceReadReady(bool forceReadReady) +{ + SSLSocketDataIO * dio = dynamic_cast(GetDataIO()()); + if (dio) dio->_forceReadReady = forceReadReady; +} + +}; // end namespace muscle diff --git a/iogateway/SSLSocketAdapterGateway.h b/iogateway/SSLSocketAdapterGateway.h new file mode 100644 index 00000000..46e4ba8e --- /dev/null +++ b/iogateway/SSLSocketAdapterGateway.h @@ -0,0 +1,64 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSSLSocketAdapterGateway_h +#define MuscleSSLSocketAdapterGateway_h + +#include "iogateway/AbstractMessageIOGateway.h" + +namespace muscle { + +/** + * This gateway "wraps" a caller-supplied AbstractMessageIOGateway + * and modifies the gateway's behavior so that it can be used correctly. + * with an SSLSocketDataIO. This special logic is necessary because + * non-blocking SSLSocketDataIOs have their own unique requirements for + * when SSL_read() and SSL_write() are called that do not necessarily + * coincide with what a normal gateway wants to do. + * + * So if you are using an SSLSocketDataIO object for non-blocking I/O, + * you should wrap your gateway in one of these so that it can govern + * data flow appropriately. + */ +class SSLSocketAdapterGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** Constructor + * @param slaveGateway Reference to the AbstractMessageIOGateway we want to proxy for. + */ + SSLSocketAdapterGateway(const AbstractMessageIOGatewayRef & slaveGateway); + + /** Destructor */ + virtual ~SSLSocketAdapterGateway(); + + virtual status_t AddOutgoingMessage(const MessageRef & messageRef); + + virtual bool IsReadyForInput() const; + virtual bool HasBytesToOutput() const; + virtual uint64 GetOutputStallLimit() const; + virtual void Shutdown(); + virtual void Reset(); + virtual void SetDataIO(const DataIORef & ref); + + /** Sets our slave gateway to something else. + * @param slaveGateway Reference to the new AbstractMessageIOGateway we want to proxy for. + */ + void SetSlaveGateway(const AbstractMessageIOGatewayRef & slaveGateway); + + /** Returns a reference to our held slave gateway (or a NULL reference if we haven't got one) */ + const AbstractMessageIOGatewayRef & GetSlaveGateway() const {return _slaveGateway;} + +protected: + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT); + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT); + +private: + uint32 GetSSLState() const; + void SetSSLForceReadReady(bool forceReadReady); + + AbstractMessageIOGatewayRef _slaveGateway; + QueueGatewayMessageReceiver _sslMessages; // messages that were generated during a DoOutput() call, oddly enough +}; + +}; // end namespace muscle + +#endif diff --git a/iogateway/SignalMessageIOGateway.h b/iogateway/SignalMessageIOGateway.h new file mode 100644 index 00000000..53ffd4da --- /dev/null +++ b/iogateway/SignalMessageIOGateway.h @@ -0,0 +1,64 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSignalMessageIOGateway_h +#define MuscleSignalMessageIOGateway_h + +#include "iogateway/AbstractMessageIOGateway.h" + +namespace muscle { + +/** + * This gateway is simple almost to the point of being crippled... all it does + * is read data from its socket, and whenever it has read some data, it + * will add a user-specified MessageRef to its incoming Message queue. + * It's useful primarily for thread synchronization purposes. + */ +class SignalMessageIOGateway : public AbstractMessageIOGateway, private CountedObject +{ +public: + /** Constructor. Creates a SignalMessageIOGateway with a NULL signal message reference. + */ + SignalMessageIOGateway() {/* empty */} + + /** Constructor + * @param signalMessage The message to send out when we have read some incoming data. + */ + SignalMessageIOGateway(const MessageRef & signalMessage) : _signalMessage(signalMessage) {/* empty */} + + /** Destructor */ + virtual ~SignalMessageIOGateway() {/* empty */} + + /** Always returns false. */ + virtual bool HasBytesToOutput() const {return false;} + + /** Returns a reference to our current signal message */ + MessageRef GetSignalMessage() const {return _signalMessage;} + + /** Sets our current signal message reference. */ + void SetSignalMessage(const MessageRef & r) {_signalMessage = r;} + +protected: + /** DoOutput is a no-op for this gateway... all messages are simply eaten and dropped. */ + virtual int32 DoOutputImplementation(uint32 maxBytes = MUSCLE_NO_LIMIT) + { + // Just eat and drop ... we don't really support outgoing messages + while(GetOutgoingMessageQueue().RemoveHead() == B_NO_ERROR) {/* keep doing it */} + return maxBytes; + } + + /** Overridden to enqeue a (signalMessage) whenever data is read. */ + virtual int32 DoInputImplementation(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes = MUSCLE_NO_LIMIT) + { + char buf[256]; + int32 bytesRead = GetDataIO()()->Read(buf, muscleMin(maxBytes, (uint32)sizeof(buf))); + if (bytesRead > 0) receiver.CallMessageReceivedFromGateway(_signalMessage); + return bytesRead; + } + +private: + MessageRef _signalMessage; +}; + +}; // end namespace muscle + +#endif diff --git a/java/README.TXT b/java/README.TXT new file mode 100644 index 00000000..75167f3c --- /dev/null +++ b/java/README.TXT @@ -0,0 +1,121 @@ +As of v1.60, some Java classes have been included with MUSCLE to make it +easier to create Java-based MUSCLE clients. These classes require Java 1.4.0 +or higher to compile. The MUSCLE server code is still C++ only, though. + +The included Java classes are: + +package com.meyer.muscle.message: Java equivalent of Message class + FieldNotFoundException - thrown if you try to access a field that isn't in a message + FieldTypeMismatchException - thrown if you try to access a field by the wrong type + Message - used similarly to the C++ Message, or Be's BMessage class + MessageException - base class for all exceptions thrown by Message class + +package com.meyer.muscle.queue + Queue - A double-ended queue, like the C++ Queue or STL dequeue classes + +package com.meyer.muscle.support: Miscellaneous stuff + LEDataInputStream - Adaptor to make an input stream little-endian (courtesy Canadian Moose Products) + LEDataOutputStream - Adaptor to make an output stream little-endian (courtesy Canadian Moose Products) + Flattenable - Interface for flattenable objects (similar to the Flattenable C++ class) + Point - Similar to BPoint (java.awt.Point wasn't used because we need floats, not ints) + Rect - Similar to BRect (java.awt.Rectangle wasn't used for the same reason) + TypeConstants - Defines the B_*_TYPE constants + UnflattenFormatException - Exception that is thrown when an unflatten() call fails + +package com.meyer.muscle.iogateway: Adaptor classes for converting Messages to and from byte streams + AbstractMessageIOGateway - Abstract interface for a message IO gateway + MessageIOGateway - Converts Messages to standard MUSCLE byte streams + MessageIOGatewayFactory - A factory object that knows how to create various MessageIOGateway objects + NativeZLibMessageIOGateway - A gateway that uses Java's built-in zlib library to send/receive compressed Messages + JZLibMessageIOGateway - Same as NAtiveZLibMessageIOGateway, but uses the JCraft ZLib library instead + +package com.meyer.muscle.thread: Some nice threading/message queue support + MessageListener - Interface for a class that wants to receive asynchronous messageReceived() callbacks + MessageQueue - A class that processes a stream of message objects and gives them to MessageListeners + ThreadPool - Holds a set of Threads and reuses them + +package com.meyer.muscle.client: Some higher-level convenience classes for easy client-side networking + MessageTransceiver - A threaded TCP message transceiver (somewhat like the C++ MessageTransceiverThread class) + StorageReflectConstants - Java declarations of the constants used in the StorageReflectSession protocol + DatagramPacketTransceiver - A special class for packetizing/depacketizing MUSCLE Messages over UDP packets + +package com.meyer.muscle.test: Code testing/verification programs + TestClient.java - A command line test client, similar to the C++ testreflectclient program. + UDPClient.java - A command line test client, to test the DatagramPacketTransceiver class. + + +Quick Java Client HOWTO +----------------------- + +Writing a Java MUSCLE client is easy. Here is what you need to do: + +1. Integrate all the com.meyer.muscle classes in the muscle/java subdirectory into your Java project. + +2. Have a class in your application that implements the com.meyer.muscle.thread.MessageListener interface. + Then, when your program wishes to connect to a MUSCLE server, it should use a MessageTransceiver, like this: + + MessageTransceiver mt = new MessageTransceiver(new MessageQueue(this)); // assumes (this) implements MessageListener + mt.connect("beshare.bentonrea.com", 2960, onSuccess, onFailure); // or wherever + + (onSuccess) and (onFailure) can be any objects you like; they will be passed back to you in your + messageReceived() method when the connection is completed, or when it (fails/is broken), respectively. + +3. To send a Message to the server, just create it and send it out using the MessageTransceiver, for example: + + Message msg = new Message(PR_COMMAND_FOOBAR); // construct the message... + msg.setString("SomeStringField", "MyValue"); + msg.setInt("AnIntValue", 35); + float someFloats[] = {1.0f, 2.5f, 4.0f}; + msg.setFloats("SomeFloatValues", someFloats); + mt.sendOutgoingMessage(msg); // and queue it up to be transmitted asynchronously + + Note that the methods in Message are used differently than in the C++ class; you must set + each field's contents with a single method call, instead of adding values one at a time with repeated calls. + Also, the methods are named after Java data types instead of BeOS types: + + Java C++/BeOS + =============================== + boolean bool + byte int8 + short int16 + int int32 + long int64 + float float + double double + java.lang.String String + +4. When a Message is received from the server, your MessageListener's messageReceived() method is called + so you can process it. Note that messageReceived() is called from a background Thread, so you probably want + to declare it synchronized to avoid potential race conditions: + + class MyClass implements MessageListener { + public synchronized void messageReceived(Object msg, int numLeftInQueue) throws Exception + { + if (msg == onSuccess) // as referenced in the mt.connect() call + { + System.out.println("TCP Connection succeeded!"); + } + else if (msg == onFailure) // as referenced in the mt.connect() call + { + System.out.println("TCP Connection failed, or was disconnected"); + } + else if (msg instanceof Message) + { + Message p = (Message) msg; + System.out.println("Received a Message from the server: "); + p.printToStream(System.out); + } + } + + [...] + } + + +5. When you wish to close the TCP connection, call disconnect() on your MessageTransceiver object. + Note that you may still receive calls to your messageReceived() method even after the disconnect() + call returns, due to the asynchronous nature of the message queueing. + +And that's it! All Message semantics are the same as they are in C++ land; see the +"Beginner's Guide to MUSCLE" HTML document, or the comments at the end of StorageReflectConstants.java +for details. There is also a JavaChat client, with source code, on BeBits.com, that allows you +to chat with BeShare users from a Java applet. You can use that as example source too. diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 00000000..42f3d798 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + +

+ + + +
+ + + + + + + + + + +
+ + + +
+
+ +
+
+ + + + + + + + + + + diff --git a/java/com/jcraft/ChangeLog b/java/com/jcraft/ChangeLog new file mode 100644 index 00000000..c828fae3 --- /dev/null +++ b/java/com/jcraft/ChangeLog @@ -0,0 +1,115 @@ +ChangeLog of JZlib +==================================================================== +Last modified: Thu Aug 18 16:16:06 UTC 2005 + + +Changes since version 1.0.6: +- change: memory and performance optimizations in the inflate operation. + Many thanks to Paul Wakefield at platx.org(http://www.platx.org), who + suggested above improvements. +- change: added the nowrap argument to Z{Input,Output}?Stream. + + +Changes since version 1.0.5: +- ZInputStream.read(byte[], int, int) method return sometimes zero + instead of -1 at the end of stream. +- ZOutputStream.end() method should not declare IOException. +- It should be possible to call ZOutputStream.end() method more times + (because you can call close() method and after it end() method in + finally block). +- ZOutputStream.finish() method should not ignore IOException from flush(). +Many thanks to Matej Kraus at seznam.cz , who pointed out above problems. + + +Changes since version 1.0.4: +- a minor bug fix in ZInputStream + + +Changes since version 1.0.3: +- fixed a bug in closing ZOutputStream. + The inflating and deflating processes were not finished successfully. +- added 'finish' and 'end' methods to ZOutputStream.java +- added 'example/test_stream_deflate_inflate.java' + + +Changes since version 1.0.2: +- enabled pure Java implementation of adler32 and + commented out the dependency on J2SE in Adler32.java for J2ME users. + + +Changes since version 1.0.1: +- fixed a bug in deflating some trivial data, for example, + large data chunk filled with zero. +- added 'version' method to JZlib class. + + +Changes since version 1.0.0: +- added ZInputStream and ZOutputStream classes. +- fixed a typo in LICENSE.txt. + + +Changes since version 0.0.9: +- fixed a bug in setting a dictionary in the inflation process. +- The license was changed to a BSD style license. +- Z{Input,Output}Stream classes were deleted. + + +Changes since version 0.0.8: +- fixed a bug in handling a preset dictionary in the inflation process. + + +Changes since version 0.0.7: +- added methods to control the window size (the size of the history + buffer) and to handle the no-wrap option (no zlib header or check), + which is the undocumented functionality of zlib. + + +Changes since version 0.0.6: +- updated InfBlocks.java as zlib did to fix the vulnerability related to + the 'double free'. Of course, JZlib is free from such a vulnerability + like the 'double free', but JZlib had crashed with NullPointerException + by a specially-crafted block of invalid deflated data. + + +Changes since version 0.0.5: +- added 'flush()' method to com.jcraft.jzlib.ZOutputStream. +- fixed to take care when occurring the buffer overflow in + com.jcraft.jzlib.ZOutputStream. +Many thanks to Tim Bendfelt at cs.wisc.edu , who pointed out above problems. + + +Changes since version 0.0.4: +............................ +- fixed a bug in Adler32 class. +- fixed a bug in ZInputStream class +- modified ZInputStream to be extended from InputStream + instead of FileInputStream. +- modified ZOutputStream to be extended from OutputStream + instead of FileOutputStream. +- modified ZStream to be changeable wbits. Give wbits value to + the method 'deflateInit' of ZStream. +Many thanks to Bryan Keller, who reported bugs +and made suggestions. + + +Changes since version 0.0.3: +............................ +- fixed a bug in the compression level 0. +- modified to be compatible with JIKES's bug. +- added Z[Input,Output]Stream written by Lapo Luchini. + + +Changes since version 0.0.2: +............................ +- fixed a critical bug, which had arisen in the deflating operation with + NO_FLUSH and DEFAULT_COMPRESSION mode. + Many thanks to Bryan Keller(keller@neomar.com), who reported this glitch. + + +Changes since version 0.0.1: +............................ +- fixed the bad compression ratio problem, which is reported from + Martin Smith(martin@spamcop.net). The compression ratio was not so good + compared with the compression ration of zlib. Now, this problem has been + fixed and, I hope, that deflated files by JZlib and zlib are completely + same bit by bit. diff --git a/java/com/jcraft/LICENSE.txt b/java/com/jcraft/LICENSE.txt new file mode 100644 index 00000000..cdce5007 --- /dev/null +++ b/java/com/jcraft/LICENSE.txt @@ -0,0 +1,29 @@ +JZlib 0.0.* were released under the GNU LGPL license. Later, we have switched +over to a BSD-style license. + +------------------------------------------------------------------------------ +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/java/com/jcraft/README b/java/com/jcraft/README new file mode 100644 index 00000000..45027db9 --- /dev/null +++ b/java/com/jcraft/README @@ -0,0 +1,124 @@ + + JZlib + + zlib in pure Java(TM) + by ymnk@jcraft.com, JCraft,Inc. + + http://www.jcraft.com/jzlib/ + +Last modified: Fri Feb 14 13:31:26 UTC 2003 + +Description +=========== +JZlib is a re-implementation of zlib in pure Java. +The first and final aim for hacking this stuff is +to add the packet compression support to pure Java SSH systems. + + +Documentation +============= +* README files all over the source tree have info related to the stuff + in the directories. +* ChangeLog: what changed from the previous version? +* LICENSE.txt + + +Directories & Files in the Source Tree +====================================== +* com/ has source trees of JZlib. +* example/ has some samples, which demonstrate the usages. +* misc/ has some stuffs. At present, this directory includes + a patch file for MindTerm v.1.2.1, which adds the packet compression + functionality. + + +Features +======== +* Needless to say, JZlib can inflate data, which is deflated by zlib and + JZlib can generate deflated data, which is acceptable and inflated by zlib. +* JZlib supports all compression level and all flushing mode in zlib. +* JZlib does not support gzip file handling supports. +* The performance has not been estimated yet, but it will be not so bad + in deflating/inflating data stream on the low bandwidth network. +* JZlib is licensed under BSD style license. +* Any invention has not been done in developing JZlib. + So, if zlib is patent free, JZlib is also not covered by any patents. + + +Why JZlib? +========== +Java Platform API provides packages 'java.util.zip.*' for +accessing to zlib, but that support is very limited +if you need to use the essence of zlib. For example, +we needed to full access to zlib to add the packet compression +support to pure Java SSH system, but they are useless for our requirements. +The Internet draft, SSH Transport Layer Protocol, says in the section '4.2 Compression' as follows, + + The following compression methods are currently defined: + none REQUIRED no compression + zlib OPTIONAL GNU ZLIB (LZ77) compression + The "zlib" compression is described in [RFC-1950] and in [RFC-1951]. The + compression context is initialized after each key exchange, and is + passed from one packet to the next with only a partial flush being + performed at the end of each packet. A partial flush means that all data + will be output, but the next packet will continue using compression + tables from the end of the previous packet. + +To implement this functionality, the Z_PARTIAL_FLUSH mode of zlib must be +used, however JDK does not permit us to do so. It seems that this problem has +been well known and some people have already reported to +JavaSoft's BugParade(for example, BugId:4255743), +but any positive response has not been returned from JavaSoft, +so this problem will not be solved forever. +This is our motivation to hack JZlib. + + +A unofficial patch for MindTerm v.1.2.1 +======================================= +A unofficial patch file for MindTerm v.1.2.1 has included in 'misc' directory. +It adds the packet compression support to MindTerm. +Please refer to 'misc/README' file. + + +Copyrights & Disclaimers +======================== +JZlib is copyrighted by ymnk, JCraft,Inc. and is licensed through BSD +style license. Read the LICENSE.txt file for the complete license. +ZInputStream and ZOutputStream classes were contributed by Lapo Luchini. + + +Credits +======= +JZlib has been developed by ymnk@jcraft.com, +but he has just re-implemented zlib in pure Java. +So, all credit should go to authors Jean-loup Gailly and Mark Adler +and contributors of zlib. Here is the copyright notice of zlib version 1.1.3, + + |Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + | + |This software is provided 'as-is', without any express or implied + |warranty. In no event will the authors be held liable for any damages + |arising from the use of this software. + | + |Permission is granted to anyone to use this software for any purpose, + |including commercial applications, and to alter it and redistribute it + |freely, subject to the following restrictions: + | + |1. The origin of this software must not be misrepresented; you must not + | claim that you wrote the original software. If you use this software + | in a product, an acknowledgment in the product documentation would be + | appreciated but is not required. + |2. Altered source versions must be plainly marked as such, and must not be + | misrepresented as being the original software. + |3. This notice may not be removed or altered from any source distribution. + | + |Jean-loup Gailly Mark Adler + |jloup@gzip.org madler@alumni.caltech.edu + + +If you have any comments, suggestions and questions, write us +at jzlib@jcraft.com + + +``SSH is a registered trademark and Secure Shell is a trademark of +SSH Communications Security Corp (www.ssh.com)''. diff --git a/java/com/jcraft/jzlib/Adler32.java b/java/com/jcraft/jzlib/Adler32.java new file mode 100644 index 00000000..d8b6ef86 --- /dev/null +++ b/java/com/jcraft/jzlib/Adler32.java @@ -0,0 +1,94 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Adler32{ + + // largest prime smaller than 65536 + static final private int BASE=65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + static final private int NMAX=5552; + + long adler32(long adler, byte[] buf, int index, int len){ + if(buf == null){ return 1L; } + + long s1=adler&0xffff; + long s2=(adler>>16)&0xffff; + int k; + + while(len > 0) { + k=len=16){ + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + k-=16; + } + if(k!=0){ + do{ + s1+=buf[index++]&0xff; s2+=s1; + } + while(--k!=0); + } + s1%=BASE; + s2%=BASE; + } + return (s2<<16)|s1; + } + + /* + private java.util.zip.Adler32 adler=new java.util.zip.Adler32(); + long adler32(long value, byte[] buf, int index, int len){ + if(value==1) {adler.reset();} + if(buf==null) {adler.reset();} + else{adler.update(buf, index, len);} + return adler.getValue(); + } + */ +} diff --git a/java/com/jcraft/jzlib/Deflate.java b/java/com/jcraft/jzlib/Deflate.java new file mode 100644 index 00000000..2948a143 --- /dev/null +++ b/java/com/jcraft/jzlib/Deflate.java @@ -0,0 +1,1622 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public +final class Deflate{ + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_DEFAULT_COMPRESSION=-1; + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_MEM_LEVEL=8; + + static class Config{ + int good_length; // reduce lazy search above this match length + int max_lazy; // do not perform lazy search above this match length + int nice_length; // quit search above this match length + int max_chain; + int func; + Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static final private int STORED=0; + static final private int FAST=1; + static final private int SLOW=2; + static final private Config[] config_table; + static{ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + static final private String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + static final private int NeedMore=0; + + // block flush performed + static final private int BlockDone=1; + + // finish started, need only more output at next deflate + static final private int FinishStarted=2; + + // finish done, accept no more input or output + static final private int FinishDone=3; + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final private int Z_FILTERED=1; + static final private int Z_HUFFMAN_ONLY=2; + static final private int Z_DEFAULT_STRATEGY=0; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int INIT_STATE=42; + static final private int BUSY_STATE=113; + static final private int FINISH_STATE=666; + + // The deflate compression method + static final private int Z_DEFLATED=8; + + static final private int STORED_BLOCK=0; + static final private int STATIC_TREES=1; + static final private int DYN_TREES=2; + + // The three kinds of block type + static final private int Z_BINARY=0; + static final private int Z_ASCII=1; + static final private int Z_UNKNOWN=2; + + static final private int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final private int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final private int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final private int REPZ_11_138=18; + + static final private int MIN_MATCH=3; + static final private int MAX_MATCH=258; + static final private int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + static final private int MAX_BITS=15; + static final private int D_CODES=30; + static final private int BL_CODES=19; + static final private int LENGTH_CODES=29; + static final private int LITERALS=256; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + static final private int END_BLOCK=256; + + ZStream strm; // pointer back to this zlib stream + int status; // as the name implies + byte[] pending_buf; // output still pending + int pending_buf_size; // size of pending_buf + int pending_out; // next pending byte to output to the stream + int pending; // nb of bytes in the pending buffer + int noheader; // suppress zlib header and adler32 + byte data_type; // UNKNOWN, BINARY or ASCII + byte method; // STORED (for zip only) or DEFLATED + int last_flush; // value of flush param for previous deflate call + + int w_size; // LZ77 window size (32K by default) + int w_bits; // log2(w_size) (8..16) + int w_mask; // w_size - 1 + + byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + short[] head; // Heads of the hash chains or NIL. + + int ins_h; // hash index of string to be inserted + int hash_size; // number of elements in hash table + int hash_bits; // log2(hash_size) + int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + int block_start; + + int match_length; // length of best match + int prev_match; // previous match + int match_available; // set if previous match exists + int strstart; // start of string to insert + int match_start; // start of matching string + int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + int level; // compression level (1..9) + int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + int good_match; + + // Stop searching when current match exceeds this + int nice_match; + + short[] dyn_ltree; // literal and length tree + short[] dyn_dtree; // distance tree + short[] bl_tree; // Huffman tree for bit lengths + + Tree l_desc=new Tree(); // desc for literal tree + Tree d_desc=new Tree(); // desc for distance tree + Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + short[] bl_count=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + int[] heap=new int[2*L_CODES+1]; + + int heap_len; // number of elements in the heap + int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + byte[] depth=new byte[2*L_CODES+1]; + + int l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + int lit_bufsize; + + int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + int d_buf; // index of pendig_buf + + int opt_len; // bit length of current block with optimal trees + int static_len; // bit length of current block with static trees + int matches; // number of string matches in current block + int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + int bi_valid; + + Deflate(){ + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + final void put_byte(byte[] p, int start, int len){ + System.arraycopy(p, start, pending_buf, pending, len); + pending+=len; + } + + final void put_byte(byte c){ + pending_buf[pending++]=c; + } + final void put_short(int w) { + put_byte((byte)(w/*&0xff*/)); + put_byte((byte)(w>>>8)); + } + final void putShortMSB(int b){ + put_byte((byte)(b>>8)); + put_byte((byte)(b/*&0xff*/)); + } + + final void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + void send_bits(int value, int length){ + int len = length; + if (bi_valid > (int)Buf_size - len) { + int val = value; +// bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid)&0xffff); + put_short(bi_buf); + bi_buf = (short)(val >>> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } else { +// bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid)&0xffff); + bi_valid += len; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + boolean _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + pending_buf[l_buf+last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(pending_buf[l_buf+lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n (ascii_freq >>> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + void bi_flush(){ + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + put_byte((byte)bi_buf); + bi_buf>>>=8; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + void bi_windup(){ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte((byte)bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + void copy_block(int buf, // the input data + int len, // its length + boolean header // true if block header must be written + ){ + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + void flush_block_only(boolean eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>>3; + static_lenb=(static_len+3+7)>>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.arraycopy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(m>=w_size ? (short)(m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (m >= w_size ? (short)(m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<= MIN_MATCH){ + ins_h=(((ins_h)<=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<= MIN_MATCH) { + ins_h=(((ins_h)< 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + int deflateInit(ZStream strm, int level, int bits){ + return deflateInit2(strm, level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + int deflateInit(ZStream strm, int level){ + return deflateInit(strm, level, MAX_WBITS); + } + int deflateInit2(ZStream strm, int level, int method, int windowBits, + int memLevel, int strategy){ + int noheader = 0; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + noheader = 1; + windowBits = -windowBits; + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.noheader = noheader; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*4]; + pending_buf_size = lit_bufsize*4; + + d_buf = lit_bufsize/2; + l_buf = (1+2)*lit_bufsize; + + this.level = level; + +//System.out.println("level="+level); + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(strm); + } + + int deflateReset(ZStream strm){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(noheader < 0) { + noheader = 0; // was set to -1 by deflate(..., Z_FINISH); + } + status = (noheader!=0) ? BUSY_STATE : INIT_STATE; + strm.adler=strm._adler.adler32(0, null, 0, 0); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + int deflateParams(ZStream strm, int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + int deflateSetDictionary (ZStream strm, byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler=strm._adler.adler32(strm.adler, dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.arraycopy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + this.strm = strm; // just in case + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + putShortMSB((int)(strm.adler>>>16)); + putShortMSB((int)(strm.adler&0xffff)); + } + strm.adler=strm._adler.adler32(0, null, 0, 0); + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i>>16)); + putShortMSB((int)(strm.adler&0xffff)); + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + noheader = -1; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } +} diff --git a/java/com/jcraft/jzlib/InfBlocks.java b/java/com/jcraft/jzlib/InfBlocks.java new file mode 100644 index 00000000..451640c3 --- /dev/null +++ b/java/com/jcraft/jzlib/InfBlocks.java @@ -0,0 +1,613 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfBlocks{ + static final private int MANY=1440; + + // And'ing with mask[n] masks the lower n bits + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + // Table for deflate from PKZIP's appnote.txt. + static final int[] border = { // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int TYPE=0; // get type bits (3, including end bit) + static final private int LENS=1; // get lengths for stored + static final private int STORED=2;// processing stored block + static final private int TABLE=3; // get table lengths + static final private int BTREE=4; // get bit lengths tree for a dynamic block + static final private int DTREE=5; // get length, distance trees for a dynamic block + static final private int CODES=6; // processing fixed or dynamic block + static final private int DRY=7; // output remaining window bytes + static final private int DONE=8; // finished last block, done + static final private int BAD=9; // ot a data error--stuck here + + int mode; // current inflate_block mode + + int left; // if STORED, bytes left to copy + + int table; // table lengths (14 bits) + int index; // index into blens (or border) + int[] blens; // bit lengths of codes + int[] bb=new int[1]; // bit length tree depth + int[] tb=new int[1]; // bit length decoding tree + + InfCodes codes=new InfCodes(); // if CODES, current state + + int last; // true if this block is the last block + + // mode independent information + int bitk; // bits in bit buffer + int bitb; // bit buffer + int[] hufts; // single malloc for tree space + byte[] window; // sliding window + int end; // one byte after sliding window + int read; // window read pointer + int write; // window write pointer + Object checkfn; // check function + long check; // check on output + + InfTree inftree=new InfTree(); + + InfBlocks(ZStream z, Object checkfn, int w){ + hufts=new int[MANY*3]; + window=new byte[w]; + end=w; + this.checkfn = checkfn; + mode = TYPE; + reset(z, null); + } + + void reset(ZStream z, long[] c){ + if(c!=null) c[0]=check; + if(mode==BTREE || mode==DTREE){ + } + if(mode==CODES){ + codes.free(z); + } + mode=TYPE; + bitk=0; + bitb=0; + read=write=0; + + if(checkfn != null) + z.adler=check=z._adler.adler32(0L, null, 0, 0); + } + + int proc(ZStream z, int r){ + int t; // temporary storage + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + {p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} + {q=write;m=(int)(q>> 1){ + case 0: // stored + {b>>>=(3);k-=(3);} + t = k & 7; // go to byte boundary + + {b>>>=(t);k-=(t);} + mode = LENS; // get length of stored block + break; + case 1: // fixed + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[][] tl=new int[1][]; + int[][] td=new int[1][]; + + InfTree.inflate_trees_fixed(bl, bd, tl, td, z); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z); + } + + {b>>>=(3);k-=(3);} + + mode = CODES; + break; + case 2: // dynamic + + {b>>>=(3);k-=(3);} + + mode = TABLE; + break; + case 3: // illegal + + {b>>>=(3);k-=(3);} + mode = BAD; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + break; + case LENS: + + while(k<(32)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>> 16) & 0xffff) != (b & 0xffff)){ + mode = BAD; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); + break; + case STORED: + if (n == 0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + if(m==0){ + if(q==end&&read!=0){ + q=0; m=(int)(qn) t = n; + if(t>m) t = m; + System.arraycopy(z.next_in, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last!=0 ? DRY : TYPE; + break; + case TABLE: + + while(k<(14)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)< 29 || ((t >> 5) & 0x1f) > 29) + { + mode = BAD; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if(blens==null || blens.length>>=(14);k-=(14);} + + index = 0; + mode = BTREE; + case BTREE: + while (index < 4 + (table >>> 10)){ + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(3);k-=(3);} + } + + while(index < 19){ + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK){ + r = t; + if (r == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + index = 0; + mode = DTREE; + case DTREE: + while (true){ + t = table; + if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ + break; + } + + int i, j, c; + + t = bb[0]; + + while(k<(t)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + blens[index++] = c; + } + else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while(k<(t+i)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + + j += (b & inflate_mask[i]); + + b>>>=(i);k-=(i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)){ + blens=null; + mode = BAD; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + c = c == 16 ? blens[i-1] : 0; + do{ + blens[i++] = c; + } + while (--j!=0); + index = i; + } + } + + tb[0]=-1; + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[] tl=new int[1]; + int[] td=new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, bl, bd, tl, td, hufts, z); + + if (t != Z_OK){ + if (t == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + r = t; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0], z); + } + mode = CODES; + case CODES: + bitb=b; bitk=k; + z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + + if ((r = codes.proc(this, z, r)) != Z_STREAM_END){ + return inflate_flush(z, r); + } + r = Z_OK; + codes.free(z); + + p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; + q=write;m=(int)(q z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy as far as end of window + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == end){ + // wrap pointers + q = 0; + if (write == end) + write = 0; + + // compute bytes to copy + n = write - q; + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + read = q; + + // done + return r; + } +} diff --git a/java/com/jcraft/jzlib/InfCodes.java b/java/com/jcraft/jzlib/InfCodes.java new file mode 100644 index 00000000..8889d07a --- /dev/null +++ b/java/com/jcraft/jzlib/InfCodes.java @@ -0,0 +1,604 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfCodes{ + + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + static final private int START=0; // x: set up for LEN + static final private int LEN=1; // i: get length/literal/eob next + static final private int LENEXT=2; // i: getting length extra (have base) + static final private int DIST=3; // i: get distance next + static final private int DISTEXT=4;// i: getting distance extra + static final private int COPY=5; // o: copying bytes in window, waiting for space + static final private int LIT=6; // o: got literal, waiting for output space + static final private int WASH=7; // o: got eob, possibly still output waiting + static final private int END=8; // x: got eob and all data flushed + static final private int BADCODE=9;// x: got error + + int mode; // current inflate_codes mode + + // mode dependent information + int len; + + int[] tree; // pointer into tree + int tree_index=0; + int need; // bits needed + + int lit; + + // if EXT or COPY, where and how much + int get; // bits to get for extra + int dist; // distance back to copy from + + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + int[] ltree; // literal/length/eob tree + int ltree_index; // literal/length/eob tree + int[] dtree; // distance tree + int dtree_index; // distance tree + + InfCodes(){ + } + void init(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index, ZStream z){ + mode=START; + lbits=(byte)bl; + dbits=(byte)bd; + ltree=tl; + ltree_index=tl_index; + dtree = td; + dtree_index=td_index; + tree=null; + } + + int proc(InfBlocks s, ZStream z, int r){ + int j; // temporary storage + int tindex; // temporary pointer + int e; // extra bits or operation + int b=0; // bit buffer + int k=0; // bits in bit buffer + int p=0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q= 258 && n >= 10){ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + r = inflate_fast(lbits, dbits, + ltree, ltree_index, + dtree, dtree_index, + s, z); + + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q>>=(tree[tindex+1]); + k-=(tree[tindex+1]); + + e=tree[tindex]; + + if(e == 0){ // literal + lit = tree[tindex+2]; + mode = LIT; + break; + } + if((e & 16)!=0 ){ // length + get = e & 15; + len = tree[tindex+2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3+tree[tindex+2]; + break; + } + if ((e & 32)!=0){ // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + need = dbits; + tree = dtree; + tree_index=dtree_index; + mode = DIST; + case DIST: // i: get distance next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=tree[tindex+1]; + k-=tree[tindex+1]; + + e = (tree[tindex]); + if((e & 16)!=0){ // distance + get = e & 15; + dist = tree[tindex+2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3 + tree[tindex+2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case DISTEXT: // i: getting distance extra + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + mode = COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while(f < 0){ // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len!=0){ + + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q 7){ // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++] = (byte)tp[tp_index_t_3+2]; + m--; + continue; + } + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + e &= 15; + c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); + + b>>=e; k-=e; + + // decode distance base of block to copy + while(k<(15)){ // max bits for distance code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + // get extra bits to add to distance base + e &= 15; + while(k<(e)){ // get extra bits (up to 13) + n--; + b|=(z.next_in[p++]&0xff)<>=(e); k-=(e); + + // do the copy + m -= c; + if (q >= d){ // offset before dest + // just copy + r=q-d; + if(q-r>0 && 2>(q-r)){ + s.window[q++]=s.window[r++]; // minimum count is three, + s.window[q++]=s.window[r++]; // so unroll loop a little + c-=2; + } + else{ + System.arraycopy(s.window, r, s.window, q, 2); + q+=2; r+=2; c-=2; + } + } + else{ // else offset after destination + r=q-d; + do{ + r+=s.end; // force pointer in window + }while(r<0); // covers invalid distances + e=s.end-r; + if(c>e){ // if source crosses, + c-=e; // wrapped copy + if(q-r>0 && e>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--e!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, e); + q+=e; r+=e; e=0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if(q-r>0 && c>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--c!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, c); + q+=c; r+=c; c=0; + } + break; + } + else if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + e=tp[tp_index_t_3]; + } + else{ + z.msg = "invalid distance code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + break; + } + + if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + if((e=tp[tp_index_t_3])==0){ + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++]=(byte)tp[tp_index_t_3+2]; + m--; + break; + } + } + else if((e&32)!=0){ + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_STREAM_END; + } + else{ + z.msg="invalid literal/length code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + } + while(m>=258 && n>= 10); + + // not enough input or output--restore pointers and return + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_OK; + } +} diff --git a/java/com/jcraft/jzlib/InfTree.java b/java/com/jcraft/jzlib/InfTree.java new file mode 100644 index 00000000..cbca4366 --- /dev/null +++ b/java/com/jcraft/jzlib/InfTree.java @@ -0,0 +1,520 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfTree{ + + static final private int MANY=1440; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final int fixed_bl = 9; + static final int fixed_bd = 5; + + static final int[] fixed_tl = { + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,192, + 80,7,10, 0,8,96, 0,8,32, 0,9,160, + 0,8,0, 0,8,128, 0,8,64, 0,9,224, + 80,7,6, 0,8,88, 0,8,24, 0,9,144, + 83,7,59, 0,8,120, 0,8,56, 0,9,208, + 81,7,17, 0,8,104, 0,8,40, 0,9,176, + 0,8,8, 0,8,136, 0,8,72, 0,9,240, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,200, + 81,7,13, 0,8,100, 0,8,36, 0,9,168, + 0,8,4, 0,8,132, 0,8,68, 0,9,232, + 80,7,8, 0,8,92, 0,8,28, 0,9,152, + 84,7,83, 0,8,124, 0,8,60, 0,9,216, + 82,7,23, 0,8,108, 0,8,44, 0,9,184, + 0,8,12, 0,8,140, 0,8,76, 0,9,248, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,196, + 81,7,11, 0,8,98, 0,8,34, 0,9,164, + 0,8,2, 0,8,130, 0,8,66, 0,9,228, + 80,7,7, 0,8,90, 0,8,26, 0,9,148, + 84,7,67, 0,8,122, 0,8,58, 0,9,212, + 82,7,19, 0,8,106, 0,8,42, 0,9,180, + 0,8,10, 0,8,138, 0,8,74, 0,9,244, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,204, + 81,7,15, 0,8,102, 0,8,38, 0,9,172, + 0,8,6, 0,8,134, 0,8,70, 0,9,236, + 80,7,9, 0,8,94, 0,8,30, 0,9,156, + 84,7,99, 0,8,126, 0,8,62, 0,9,220, + 82,7,27, 0,8,110, 0,8,46, 0,9,188, + 0,8,14, 0,8,142, 0,8,78, 0,9,252, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,194, + 80,7,10, 0,8,97, 0,8,33, 0,9,162, + 0,8,1, 0,8,129, 0,8,65, 0,9,226, + 80,7,6, 0,8,89, 0,8,25, 0,9,146, + 83,7,59, 0,8,121, 0,8,57, 0,9,210, + 81,7,17, 0,8,105, 0,8,41, 0,9,178, + 0,8,9, 0,8,137, 0,8,73, 0,9,242, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,202, + 81,7,13, 0,8,101, 0,8,37, 0,9,170, + 0,8,5, 0,8,133, 0,8,69, 0,9,234, + 80,7,8, 0,8,93, 0,8,29, 0,9,154, + 84,7,83, 0,8,125, 0,8,61, 0,9,218, + 82,7,23, 0,8,109, 0,8,45, 0,9,186, + 0,8,13, 0,8,141, 0,8,77, 0,9,250, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,198, + 81,7,11, 0,8,99, 0,8,35, 0,9,166, + 0,8,3, 0,8,131, 0,8,67, 0,9,230, + 80,7,7, 0,8,91, 0,8,27, 0,9,150, + 84,7,67, 0,8,123, 0,8,59, 0,9,214, + 82,7,19, 0,8,107, 0,8,43, 0,9,182, + 0,8,11, 0,8,139, 0,8,75, 0,9,246, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,206, + 81,7,15, 0,8,103, 0,8,39, 0,9,174, + 0,8,7, 0,8,135, 0,8,71, 0,9,238, + 80,7,9, 0,8,95, 0,8,31, 0,9,158, + 84,7,99, 0,8,127, 0,8,63, 0,9,222, + 82,7,27, 0,8,111, 0,8,47, 0,9,190, + 0,8,15, 0,8,143, 0,8,79, 0,9,254, + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,193, + + 80,7,10, 0,8,96, 0,8,32, 0,9,161, + 0,8,0, 0,8,128, 0,8,64, 0,9,225, + 80,7,6, 0,8,88, 0,8,24, 0,9,145, + 83,7,59, 0,8,120, 0,8,56, 0,9,209, + 81,7,17, 0,8,104, 0,8,40, 0,9,177, + 0,8,8, 0,8,136, 0,8,72, 0,9,241, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,201, + 81,7,13, 0,8,100, 0,8,36, 0,9,169, + 0,8,4, 0,8,132, 0,8,68, 0,9,233, + 80,7,8, 0,8,92, 0,8,28, 0,9,153, + 84,7,83, 0,8,124, 0,8,60, 0,9,217, + 82,7,23, 0,8,108, 0,8,44, 0,9,185, + 0,8,12, 0,8,140, 0,8,76, 0,9,249, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,197, + 81,7,11, 0,8,98, 0,8,34, 0,9,165, + 0,8,2, 0,8,130, 0,8,66, 0,9,229, + 80,7,7, 0,8,90, 0,8,26, 0,9,149, + 84,7,67, 0,8,122, 0,8,58, 0,9,213, + 82,7,19, 0,8,106, 0,8,42, 0,9,181, + 0,8,10, 0,8,138, 0,8,74, 0,9,245, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,205, + 81,7,15, 0,8,102, 0,8,38, 0,9,173, + 0,8,6, 0,8,134, 0,8,70, 0,9,237, + 80,7,9, 0,8,94, 0,8,30, 0,9,157, + 84,7,99, 0,8,126, 0,8,62, 0,9,221, + 82,7,27, 0,8,110, 0,8,46, 0,9,189, + 0,8,14, 0,8,142, 0,8,78, 0,9,253, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,195, + 80,7,10, 0,8,97, 0,8,33, 0,9,163, + 0,8,1, 0,8,129, 0,8,65, 0,9,227, + 80,7,6, 0,8,89, 0,8,25, 0,9,147, + 83,7,59, 0,8,121, 0,8,57, 0,9,211, + 81,7,17, 0,8,105, 0,8,41, 0,9,179, + 0,8,9, 0,8,137, 0,8,73, 0,9,243, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,203, + 81,7,13, 0,8,101, 0,8,37, 0,9,171, + 0,8,5, 0,8,133, 0,8,69, 0,9,235, + 80,7,8, 0,8,93, 0,8,29, 0,9,155, + 84,7,83, 0,8,125, 0,8,61, 0,9,219, + 82,7,23, 0,8,109, 0,8,45, 0,9,187, + 0,8,13, 0,8,141, 0,8,77, 0,9,251, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,199, + 81,7,11, 0,8,99, 0,8,35, 0,9,167, + 0,8,3, 0,8,131, 0,8,67, 0,9,231, + 80,7,7, 0,8,91, 0,8,27, 0,9,151, + 84,7,67, 0,8,123, 0,8,59, 0,9,215, + 82,7,19, 0,8,107, 0,8,43, 0,9,183, + 0,8,11, 0,8,139, 0,8,75, 0,9,247, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,207, + 81,7,15, 0,8,103, 0,8,39, 0,9,175, + 0,8,7, 0,8,135, 0,8,71, 0,9,239, + 80,7,9, 0,8,95, 0,8,31, 0,9,159, + 84,7,99, 0,8,127, 0,8,63, 0,9,223, + 82,7,27, 0,8,111, 0,8,47, 0,9,191, + 0,8,15, 0,8,143, 0,8,79, 0,9,255 + }; + static final int[] fixed_td = { + 80,5,1, 87,5,257, 83,5,17, 91,5,4097, + 81,5,5, 89,5,1025, 85,5,65, 93,5,16385, + 80,5,3, 88,5,513, 84,5,33, 92,5,8193, + 82,5,9, 90,5,2049, 86,5,129, 192,5,24577, + 80,5,2, 87,5,385, 83,5,25, 91,5,6145, + 81,5,7, 89,5,1537, 85,5,97, 93,5,24577, + 80,5,4, 88,5,769, 84,5,49, 92,5,12289, + 82,5,13, 90,5,3073, 86,5,193, 192,5,24577 + }; + + // Tables for deflate from PKZIP's appnote.txt. + static final int[] cplens = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + }; + + // see note #13 above about 258 + static final int[] cplext = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + }; + + static final int[] cpdist = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + static final int[] cpdext = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + static final int BMAX=15; // maximum bit length of any code + + int[] hn = null; // hufts used in space + int[] v = null; // work area for huft_build + int[] c = null; // bit length count table + int[] r = null; // table entry for structure assignment + int[] u = null; // table stack + int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX) + int bindex, + int n, // number of codes (assumed <= 288) + int s, // number of simple-valued codes (0..s-1) + int[] d, // list of base values for non-simple codes + int[] e, // list of extra bits for non-simple codes + int[] t, // result: starting table + int[] m, // maximum lookup bits, returns actual + int[] hp,// space for trees + int[] hn,// hufts used in space + int[] v // working area: values in order of bit length + ){ + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do { + c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX + }while(i!=0); + + if(c[0] == n){ // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if(c[j]!=0) break; + k = j; // minimum code length + if(l < j){ + l = j; + } + for (i = BMAX; i!=0; i--){ + if(c[i]!=0) break; + } + g = i; // maximum code length + if(l > i){ + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1){ + if ((y -= c[j]) < 0){ + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0){ + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i!=0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do { + if ((j = b[bindex+p]) != 0){ + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++){ + a = c[k]; + while (a--!=0){ + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l){ + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if((f=1<<(j=k-w))>a+1){ // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if(j < z){ + while (++j < z){ // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY){ // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /*hp+*/ hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if(h!=0){ + x[h]=i; // save pattern for backing up + r[0]=(byte)j; // bits in this table + r[1]=(byte)l; // bits to dump before this table + j=i>>>(w - l); + r[2] = (int)(q - u[h-1] - j); // offset to this table + System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table + } + else{ + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (byte)(k - w); + if (p >= n){ + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s){ + r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else{ + r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists + r[2]=d[v[p++] - s]; + } + + // fill code-like entries with r + f=1<<(k-w); + for (j=i>>>w;j>>= 1){ + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]){ + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + int inflate_trees_bits(int[] c, // 19 code lengths + int[] bb, // bits tree desired/actual depth + int[] tb, // bits tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + initWorkArea(19); + hn[0]=0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed dynamic bit lengths tree"; + } + else if(result == Z_BUF_ERROR || bb[0] == 0){ + z.msg = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + int inflate_trees_dynamic(int nl, // number of literal/length codes + int nd, // number of distance codes + int[] c, // that many (total) code lengths + int[] bl, // literal desired/actual bit depth + int[] bd, // distance desired/actual bit depth + int[] tl, // literal/length tree result + int[] td, // distance tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + + // build literal/length tree + initWorkArea(288); + hn[0]=0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0){ + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR){ + z.msg = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)){ + if (result == Z_DATA_ERROR){ + z.msg = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR){ + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth + int[] bd, //distance desired/actual bit depth + int[][] tl,//literal/length tree result + int[][] td,//distance tree result + ZStream z //for memory allocation + ){ + bl[0]=fixed_bl; + bd[0]=fixed_bd; + tl[0]=fixed_tl; + td[0]=fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize){ + if(hn==null){ + hn=new int[1]; + v=new int[vsize]; + c=new int[BMAX+1]; + r=new int[3]; + u=new int[BMAX]; + x=new int[BMAX+1]; + } + if(v.lengthstate); + return Z_OK; + } + + int inflateInit(ZStream z, int w){ + z.msg = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + nowrap = 0; + if(w < 0){ + w = - w; + nowrap = 1; + } + + // set window size + if(w<8 ||w>15){ + inflateEnd(z); + return Z_STREAM_ERROR; + } + wbits=w; + + z.istate.blocks=new InfBlocks(z, + z.istate.nowrap!=0 ? null : this, + 1<>4)+8>z.istate.wbits){ + z.istate.mode = BAD; + z.msg="invalid window size"; + z.istate.marker = 5; // can't try inflateSync + break; + } + z.istate.mode=FLAG; + case FLAG: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + b = (z.next_in[z.next_in_index++])&0xff; + + if((((z.istate.method << 8)+b) % 31)!=0){ + z.istate.mode = BAD; + z.msg = "incorrect header check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + if((b&PRESET_DICT)==0){ + z.istate.mode = BLOCKS; + break; + } + z.istate.mode = DICT4; + case DICT4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=DICT3; + case DICT3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode=DICT2; + case DICT2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode=DICT1; + case DICT1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need += (z.next_in[z.next_in_index++]&0xffL); + z.adler = z.istate.need; + z.istate.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z.istate.mode = BAD; + z.msg = "need dictionary"; + z.istate.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + + r = z.istate.blocks.proc(z, r); + if(r == Z_DATA_ERROR){ + z.istate.mode = BAD; + z.istate.marker = 0; // can try inflateSync + break; + } + if(r == Z_OK){ + r = f; + } + if(r != Z_STREAM_END){ + return r; + } + r = f; + z.istate.blocks.reset(z, z.istate.was); + if(z.istate.nowrap!=0){ + z.istate.mode=DONE; + break; + } + z.istate.mode=CHECK4; + case CHECK4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=CHECK3; + case CHECK3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode = CHECK2; + case CHECK2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode = CHECK1; + case CHECK1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=(z.next_in[z.next_in_index++]&0xffL); + + if(((int)(z.istate.was[0])) != ((int)(z.istate.need))){ + z.istate.mode = BAD; + z.msg = "incorrect data check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + z.istate.mode = DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } + } + } + + + int inflateSetDictionary(ZStream z, byte[] dictionary, int dictLength){ + int index=0; + int length = dictLength; + if(z==null || z.istate == null|| z.istate.mode != DICT0) + return Z_STREAM_ERROR; + + if(z._adler.adler32(1L, dictionary, 0, dictLength)!=z.adler){ + return Z_DATA_ERROR; + } + + z.adler = z._adler.adler32(0, null, 0, 0); + + if(length >= (1<>>7)]); + } + + short[] dyn_tree; // the dynamic tree + int max_code; // largest code with non zero frequency + StaticTree stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + void gen_bitlen(Deflate s){ + short[] tree = dyn_tree; + short[] stree = stat_desc.static_tree; + int[] extra = stat_desc.extra_bits; + int base = stat_desc.extra_base; + int max_length = stat_desc.max_length; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap + + for(h=s.heap_max+1; h max_length){ bits = max_length; overflow++; } + tree[n*2+1] = (short)bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n*2]; + s.opt_len += f * (bits + xbits); + if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); + } + if (overflow == 0) return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) { + n = s.bl_count[bits]; + while (n != 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m*2+1] != bits) { + s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2]; + tree[m*2+1] = (short)bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + void build_tree(Deflate s){ + short[] tree=dyn_tree; + short[] stree=stat_desc.static_tree; + int elems=stat_desc.elems; + int n, m; // iterate over heap elements + int max_code=-1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for(n=0; n=1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node=elems; // next internal node of the tree + do{ + // n = node of least frequency + n=s.heap[1]; + s.heap[1]=s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m=s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node*2] = (short)(tree[n*2] + tree[m*2]); + s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1); + tree[n*2+1] = tree[m*2+1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while(s.heap_len>=2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + static void gen_codes(short[] tree, // the tree to decorate + int max_code, // largest code with non zero frequency + short[] bl_count // number of codes at each bit length + ){ + short[] next_code=new short[MAX_BITS+1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>>=1; + res<<=1; + } + while(--len>0); + return res>>>1; + } +} + diff --git a/java/com/jcraft/jzlib/ZInputStream.java b/java/com/jcraft/jzlib/ZInputStream.java new file mode 100644 index 00000000..e00c8663 --- /dev/null +++ b/java/com/jcraft/jzlib/ZInputStream.java @@ -0,0 +1,151 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2001 Lapo Luchini. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS +OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ZInputStream extends FilterInputStream { + + protected ZStream z=new ZStream(); + protected int bufsize=512; + protected int flush=JZlib.Z_NO_FLUSH; + protected byte[] buf=new byte[bufsize], + buf1=new byte[1]; + protected boolean compress; + + protected InputStream in=null; + + public ZInputStream(InputStream in) { + this(in, false); + } + public ZInputStream(InputStream in, boolean nowrap) { + super(in); + this.in=in; + z.inflateInit(nowrap); + compress=false; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + public ZInputStream(InputStream in, int level) { + super(in); + this.in=in; + z.deflateInit(level); + compress=true; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + /*public int available() throws IOException { + return inf.finished() ? 0 : 1; + }*/ + + public int read() throws IOException { + if(read(buf1, 0, 1)==-1) + return(-1); + return(buf1[0]&0xFF); + } + + private boolean nomoreinput=false; + + public int read(byte[] b, int off, int len) throws IOException { + if(len==0) + return(0); + int err; + z.next_out=b; + z.next_out_index=off; + z.avail_out=len; + do { + if((z.avail_in==0)&&(!nomoreinput)) { // if buffer is empty and more input is avaiable, refill it + z.next_in_index=0; + z.avail_in=in.read(buf, 0, bufsize);//(bufsize0 || z.avail_out==0); + } + + public int getFlushMode() { + return(flush); + } + + public void setFlushMode(int flush) { + this.flush=flush; + } + + public void finish() throws IOException { + int err; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=bufsize; + if(compress){ err=z.deflate(JZlib.Z_FINISH); } + else{ err=z.inflate(JZlib.Z_FINISH); } + if(err!=JZlib.Z_STREAM_END && err != JZlib.Z_OK) + throw new ZStreamException((compress?"de":"in")+"flating: "+z.msg); + if(bufsize-z.avail_out>0){ + out.write(buf, 0, bufsize-z.avail_out); + } + } + while(z.avail_in>0 || z.avail_out==0); + flush(); + } + public void end() { + if(z==null) + return; + if(compress){ z.deflateEnd(); } + else{ z.inflateEnd(); } + z.free(); + z=null; + } + public void close() throws IOException { + try{ + try{finish();} + catch (IOException ignored) {} + } + finally{ + end(); + out.close(); + out=null; + } + } + + /** + * Returns the total number of bytes input so far. + */ + public long getTotalIn() { + return z.total_in; + } + + /** + * Returns the total number of bytes output so far. + */ + public long getTotalOut() { + return z.total_out; + } + + public void flush() throws IOException { + out.flush(); + } + +} diff --git a/java/com/jcraft/jzlib/ZStream.java b/java/com/jcraft/jzlib/ZStream.java new file mode 100644 index 00000000..334475e9 --- /dev/null +++ b/java/com/jcraft/jzlib/ZStream.java @@ -0,0 +1,211 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + public byte[] next_in; // next input byte + public int next_in_index; + public int avail_in; // number of bytes available at next_in + public long total_in; // total nb of input bytes read so far + + public byte[] next_out; // next output byte should be put there + public int next_out_index; + public int avail_out; // remaining free space at next_out + public long total_out; // total nb of bytes output so far + + public String msg; + + Deflate dstate; + Inflate istate; + + int data_type; // best guess about the data type: ascii or binary + + public long adler; + Adler32 _adler=new Adler32(); + + public int inflateInit(){ + return inflateInit(DEF_WBITS); + } + public int inflateInit(boolean nowrap){ + return inflateInit(DEF_WBITS, nowrap); + } + public int inflateInit(int w){ + return inflateInit(w, false); + } + + public int inflateInit(int w, boolean nowrap){ + istate=new Inflate(); + return istate.inflateInit(this, nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + return istate.inflate(this, f); + } + public int inflateEnd(){ + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(this); + istate = null; + return ret; + } + public int inflateSync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(this); + } + public int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(this, dictionary, dictLength); + } + + public int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + public int deflateInit(int level, boolean nowrap){ + return deflateInit(level, MAX_WBITS, nowrap); + } + public int deflateInit(int level, int bits){ + return deflateInit(level, bits, false); + } + public int deflateInit(int level, int bits, boolean nowrap){ + dstate=new Deflate(); + return dstate.deflateInit(this, level, nowrap?-bits:bits); + } + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + return dstate.deflate(this, flush); + } + public int deflateEnd(){ + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + return ret; + } + public int deflateParams(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(this, level, strategy); + } + public int deflateSetDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(this, dictionary, dictLength); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + void flush_pending(){ + int len=dstate.pending; + + if(len>avail_out) len=avail_out; + if(len==0) return; + + if(dstate.pending_buf.length<=dstate.pending_out || + next_out.length<=next_out_index || + dstate.pending_buf.length<(dstate.pending_out+len) || + next_out.length<(next_out_index+len)){ + System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+ + ", "+next_out.length+", "+next_out_index+", "+len); + System.out.println("avail_out="+avail_out); + } + + System.arraycopy(dstate.pending_buf, dstate.pending_out, + next_out, next_out_index, len); + + next_out_index+=len; + dstate.pending_out+=len; + total_out+=len; + avail_out-=len; + dstate.pending-=len; + if(dstate.pending==0){ + dstate.pending_out=0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + int read_buf(byte[] buf, int start, int size) { + int len=avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + avail_in-=len; + + if(dstate.noheader==0) { + adler=_adler.adler32(adler, next_in, next_in_index, len); + } + System.arraycopy(next_in, next_in_index, buf, start, len); + next_in_index += len; + total_in += len; + return len; + } + + public void free(){ + next_in=null; + next_out=null; + msg=null; + _adler=null; + } +} diff --git a/java/com/jcraft/jzlib/ZStreamException.java b/java/com/jcraft/jzlib/ZStreamException.java new file mode 100644 index 00000000..9f5cb0af --- /dev/null +++ b/java/com/jcraft/jzlib/ZStreamException.java @@ -0,0 +1,46 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class ZStreamException extends java.io.IOException { + public ZStreamException() { + super(); + } + public ZStreamException(String s) { + super(s); + } + + private static final long serialVersionUID = 0; +} diff --git a/java/com/meyer/muscle/client/DatagramPacketTransceiver.java b/java/com/meyer/muscle/client/DatagramPacketTransceiver.java new file mode 100644 index 00000000..17fc2cbe --- /dev/null +++ b/java/com/meyer/muscle/client/DatagramPacketTransceiver.java @@ -0,0 +1,382 @@ +package com.meyer.muscle.client; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.MulticastSocket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ByteChannel; +import java.util.HashMap; + +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.NotEnoughDataException; +import com.meyer.muscle.support.UnflattenFormatException; +import com.meyer.muscle.thread.MessageQueue; +import com.meyer.muscle.thread.MessageListener; +import com.meyer.muscle.iogateway.AbstractMessageIOGateway; +import com.meyer.muscle.iogateway.MessageIOGateway; + + + +/** + *

DatagramPacketTransceiver - A class for creating Java UDP clients.

+ * + *

This class is compatible with the C++ muscled PacketTunnelIOGateway. + * See the class UDPClient in the com.meyer.muscle.test package for example of how + * to use this class.

+ * + *

This implementation does not yet pack multiple Message objects into a + * single DatagramPacket. It will decode multiple messages packed into + * DatagramPackets, making it compatible with the C++ class. This implementation + * does support self-exclusion, miscellaneous data delivery (ByteBuffers are + * passed to listeners instead of Message objects) max output sizes (mtu), and + * reassembly of message fragments delivered out of order. + * + * @author Bryan.Varner + */ +public class DatagramPacketTransceiver extends Thread implements MessageListener { + public static final int DEFAULT_MAGIC = 1114989680; // 'Budp' + public static final int FRAGMENT_HEADER_SIZE = 6; + public static final int FRAGMENT_HEADER_LENGTH = (FRAGMENT_HEADER_SIZE * 4); // 4 bytes per 32bit int. + + // Header values. + // multiply by 4 = byte offsets into the received DatagramPacket buffer + public static final int HEADER_MAGIC = 0; + public static final int HEADER_SEXID = 1; + public static final int HEADER_MESSAGEID = 2; + public static final int HEADER_OFFSET = 3; + public static final int HEADER_CHUNK_SIZE = 4; + public static final int HEADER_TOTAL_SIZE = 5; + + AbstractMessageIOGateway slaveGateway; + MessageQueue listeners; + + int mtu; + int magic; + + boolean allowMisc; + int maxIncomingMessageSize; + int sexId; + + int outputPacketSize; + int sendMessageIDCounter; + + HashMap receiveStates; + + protected boolean halt; + + protected DatagramSocket socket; + + /** + * Creates a PacketTunnelIOGateway that defers to slave, with a MTU, and + * magic number for the header. It will receive packets on the socket, + * decode them using the given gateway, and broadcast the results to the + * MessageQueue. Messages sent with this Transceiver will have a mtu of + * maxTransferUnit, and a magic number identified by magic. + * + * @param socket The DatagramSocket to send / receive on. + * @param listeners The MessageQueue to post received data to. + * @param slave An AbstractMessageIOGateway to use when encoding / decoding + * @param maxTransferUnit The max size of a datagram packet. + * For Ethernet, this should be 1500 + * @param magic The "magic number" identifying the packets being sent. + */ + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners, AbstractMessageIOGateway slave, int maxTransferUnit, int magic) { + super("DatagramTransceiver"); + this.slaveGateway = slave; + this.mtu = Math.max(maxTransferUnit, FRAGMENT_HEADER_LENGTH + 1); + this.magic = magic; + + this.outputPacketSize = 0; + this.sendMessageIDCounter = 0; + + this.maxIncomingMessageSize = Integer.MAX_VALUE; + this.allowMisc = false; + this.sexId = 0; + + this.socket = socket; + this.listeners = listeners; + + this.receiveStates = new HashMap(); + this.halt = false; + } + + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners, int maxTransferUnit, int magic) { + this(socket, listeners, null, maxTransferUnit, magic); + } + + + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners, AbstractMessageIOGateway slave, int maxTransferUnit) { + this(socket, listeners, slave, maxTransferUnit, DEFAULT_MAGIC); + } + + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners, int maxTransferUnit) { + this(socket, listeners, null, maxTransferUnit, DEFAULT_MAGIC); + } + + + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners, AbstractMessageIOGateway slave) { + this(socket, listeners, slave, 1500, DEFAULT_MAGIC); + } + + public DatagramPacketTransceiver(DatagramSocket socket, MessageQueue listeners) { + this(socket, listeners, null); + } + + + public DatagramPacketTransceiver(DatagramSocket socket) { + this(socket, null); + } + + + public void setListenQueue(MessageQueue queue) { + this.listeners = queue; + } + + + + public boolean hasSlaveGateway() { + return slaveGateway != null; + } + + public AbstractMessageIOGateway getSlaveGateway() { + return slaveGateway; + } + + + public int getMaxIncomingMessageSize() { + return this.maxIncomingMessageSize; + } + + public void setMaxIncomingMessageSize(int size) { + this.maxIncomingMessageSize = size; + } + + public boolean getAllowMiscIncomingData() { + return this.allowMisc; + } + + public void setAllowMiscIncomingData(boolean b) { + this.allowMisc = b; + } + + public int getSourceExclusionId() { + return sexId; + } + + public void setSourceExclusionId(int sexId) { + this.sexId = sexId; + } + + + public void shutdown() { + halt = true; + if (this.isAlive()) { + this.interrupt(); + } + } + + /** + * Returns weather or not this has been shut down. + */ + public boolean isShutdown() { + return halt; + } + + protected void preRun() { + } + + protected void postRun() { + } + + public void run() { + preRun(); + + int maxIncomingMessageSize = Integer.MAX_VALUE; + if (slaveGateway != null) { + slaveGateway.getMaximumIncomingMessageSize(); + } + + byte[] buf = new byte[mtu]; + DatagramPacket recv = new DatagramPacket(buf, buf.length); + try { + socket.setSoTimeout(1000); + } catch (SocketException se) { + se.printStackTrace(); + } + + while (!halt) { + try { + socket.receive(recv); + + ByteBuffer buffer = ByteBuffer.wrap(buf); + buffer.order(ByteOrder.LITTLE_ENDIAN); + int packetMagic = buffer.getInt(); + + if (allowMisc && (buffer.capacity() < FRAGMENT_HEADER_SIZE || packetMagic != magic)) { + // Pass along the ByteBuffer. + buffer.rewind(); + listeners.postMessage(buffer); + } else { + boolean firstTime = true; + while (buffer.remaining() >= FRAGMENT_HEADER_LENGTH) { + int[] header = new int[FRAGMENT_HEADER_SIZE]; + + /* The first time through, we've already read the + * magic number. so assign it, and mark the + * boolean so that we'll read the magic next time. + */ + if (firstTime) { + header[HEADER_MAGIC] = packetMagic; // already read. + firstTime = false; + } else { + header[HEADER_MAGIC] = buffer.getInt(); + } + /* Read the headers, starting with SEXID. */ + for (int i = HEADER_SEXID; i < header.length; i++) { + header[i] = buffer.getInt(); + } + + /* Headers read. Check magic, sexid, positions & remaining space */ + if ((header[HEADER_MAGIC] == magic) && + ((header[HEADER_SEXID] == 0) || (header[HEADER_SEXID] != sexId)) && + (((buffer.capacity() - buffer.position()) >= header[HEADER_CHUNK_SIZE]) && (header[HEADER_TOTAL_SIZE] <= maxIncomingMessageSize))) + { + ReceiveState rs = receiveStates.get(recv.getSocketAddress()); + if (rs == null) { + if (header[HEADER_OFFSET] == 0) { + rs = new ReceiveState(header[HEADER_MESSAGEID]); + receiveStates.put(recv.getSocketAddress(), rs); + rs.buffer = ByteBuffer.allocate(header[HEADER_TOTAL_SIZE]); + rs.buffer.order(ByteOrder.LITTLE_ENDIAN); + } + } + + if ((header[HEADER_OFFSET] == 0) || (header[HEADER_MESSAGEID] != rs.messageId)) { + // New Message... start receiving it (but only if we are starting at the beginning). + rs.messageId = header[HEADER_MESSAGEID]; + rs.buffer.clear(); + if (rs.buffer.capacity() != header[HEADER_TOTAL_SIZE]) { + // Allocate new buffer. + rs.buffer = ByteBuffer.allocate(header[HEADER_TOTAL_SIZE]); + rs.buffer.order(ByteOrder.LITTLE_ENDIAN); + } + } + + int rsCapacity = rs.buffer.capacity(); + if ((header[HEADER_MESSAGEID] == rs.messageId) && + (header[HEADER_TOTAL_SIZE] == rsCapacity) && + (header[HEADER_OFFSET] == rs.buffer.position()) && + (header[HEADER_OFFSET] + header[HEADER_CHUNK_SIZE] <= rsCapacity)) + { + rs.buffer.put(buffer.array(), buffer.position(), header[HEADER_CHUNK_SIZE]); + if (rs.buffer.remaining() == 0 && header[HEADER_TOTAL_SIZE] > 0) { + rs.buffer.clear(); // this doesn't actually clear the backing buffer data! + try { + Message m = null; + if (slaveGateway != null) { + m = slaveGateway.unflattenMessage(rs.buffer); + } else { + m = new Message(); + m.unflatten(rs.buffer, rs.buffer.remaining()); + } + + if (listeners != null) { + listeners.postMessage(m); + } + } catch (NotEnoughDataException nede) { + System.err.println("Missing " + nede.getNumMissingBytes() + " bytes."); + } + rs.buffer.clear(); + } + } else if (header[HEADER_OFFSET] != rs.buffer.position()) { + System.err.println("specified offset: " + header[HEADER_OFFSET] + " is not current buffer position: " + rs.buffer.position()); + } else { + rs.buffer.clear(); + } + buffer.position(buffer.position() + header[HEADER_CHUNK_SIZE]); + } else { + break; + } + } + } + } catch (SocketTimeoutException ste) { + Thread.currentThread().yield(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + postRun(); + } + + public synchronized void messageReceived(Object message, int numLeftInQueue) throws Exception { + // TODO: Batch messages (in a queue) and potentially write more than one message per DatagramPacket. + if (message instanceof Message) { + ByteBuffer fullOutBuffer = null; + if (slaveGateway != null) { + fullOutBuffer = slaveGateway.flattenMessage((Message)message); + fullOutBuffer.order(ByteOrder.LITTLE_ENDIAN); + } else { + fullOutBuffer = ByteBuffer.allocate(((Message)message).flattenedSize()); + fullOutBuffer.order(ByteOrder.LITTLE_ENDIAN); + ((Message)message).flatten(fullOutBuffer); + fullOutBuffer.rewind(); + } + + // Break the fullOutBuffer up into chunks of headers + data <= mtu. + while (fullOutBuffer.remaining() > 0) { + // Either the full remaining buffer + header, or MTU + byte[] sendBuf = new byte[Math.min((fullOutBuffer.remaining() + FRAGMENT_HEADER_LENGTH), mtu)]; + ByteBuffer sendBuffer = ByteBuffer.wrap(sendBuf); + sendBuffer.order(ByteOrder.LITTLE_ENDIAN); + + // Write the first four header fields + sendBuffer.putInt(magic); + sendBuffer.putInt(sexId); + sendBuffer.putInt(sendMessageIDCounter); + sendBuffer.putInt(fullOutBuffer.position()); // current offset of this message. + + // Process the chunk we need to send. + byte[] chunkBuf = new byte[sendBuf.length - FRAGMENT_HEADER_LENGTH]; + fullOutBuffer.get(chunkBuf); + + // Write the next two headers. + sendBuffer.putInt(chunkBuf.length); + sendBuffer.putInt(fullOutBuffer.capacity()); + + sendBuffer.put(chunkBuf); + + // Create the DatagramPacket, send the buffer. + DatagramPacket packet = new DatagramPacket(sendBuf, sendBuf.length); + packet = prepPacket(packet); + socket.send(packet); + } + } + } + + protected DatagramPacket prepPacket(DatagramPacket packet) { + return packet; + } + + + // -- AbstractMessageIOGateway methods -- + private final class ReceiveState { + int messageId; + ByteBuffer buffer; + + ReceiveState() { + this(0); + } + + ReceiveState(int messageId) { + messageId = messageId; + buffer = null; + } + } +} diff --git a/java/com/meyer/muscle/client/MessageTransceiver.java b/java/com/meyer/muscle/client/MessageTransceiver.java new file mode 100644 index 00000000..90fb1fbd --- /dev/null +++ b/java/com/meyer/muscle/client/MessageTransceiver.java @@ -0,0 +1,474 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.client; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ByteChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import com.meyer.muscle.iogateway.AbstractMessageIOGateway; +import com.meyer.muscle.iogateway.MessageIOGatewayFactory; +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.NotEnoughDataException; +import com.meyer.muscle.support.UnflattenFormatException; +import com.meyer.muscle.thread.MessageListener; +import com.meyer.muscle.thread.MessageQueue; + +/** This class is a handy utility class that acts as + * an interface to a TCP socket to send and receive + * Messages over. + * @author Bryan Varner + */ +public class MessageTransceiver implements MessageListener +{ + /** Creates a MessageTransceiver that will send replies to the given queue, + * using the provided outgoing compression level. + * @param repliesTo where to send status & reply messages + * @param compressionLevel the outgoing compression level to use + * @param maximumIncomingMessageSize Maximum size of incoming messages, in bytes. Set to Integer.MAX_VALUE to enforce no limit. + */ + public MessageTransceiver(MessageQueue repliesTo, int compressionLevel, int maximumIncomingMessageSize) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(compressionLevel, maximumIncomingMessageSize); + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Creates a MessageTransceiver that will send replies to the given queue. + * @param repliesTo where to send status & reply messages + */ + public MessageTransceiver(MessageQueue repliesTo) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(); // default protocol, default compression + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Start up a transceiver that automatically uses the specified socket. + * @param repliesTo where to send status & reply messages + * @param s pre-bound channel to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + * @param compressionLevel the outgoing compression level to use. + * @param maximumIncomingMessageSize Maximum size of incoming messages, in bytes. Set to Integer.MAX_VALUE to enforce no limit. + */ + public MessageTransceiver(MessageQueue repliesTo, ByteChannel s, Object successTag, Object failTag, int compressionLevel, int maximumIncomingMessageSize) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(compressionLevel, maximumIncomingMessageSize); + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Start up a transceiver that automatically uses the specified socket. + * @param repliesTo where to send status & reply messages + * @param s pre-bound channel to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + */ + public MessageTransceiver(MessageQueue repliesTo, ByteChannel s, Object successTag, Object failTag) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(); + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Creates a MessageTransceiver that will send replies to the given queue, and + * use the given IO gateway for I/O purposes. + * @param repliesTo where to send status & reply messages + * @param ioGateway the AbstractMessageIOGateway to use to flatten and unflatten Messages. + */ + public MessageTransceiver(MessageQueue repliesTo, AbstractMessageIOGateway ioGateway) + { + _repliesTo = repliesTo; + _gateway = ioGateway; + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Start up a transceiver that automatically connects to the specified socket. + * @param repliesTo where to send status & reply messages + * @param ioGateway the AbstractMessageIOGateway to use to flatten and unflatten Messages. + * @param s pre-bound channel to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + */ + public MessageTransceiver(MessageQueue repliesTo, AbstractMessageIOGateway ioGateway, ByteChannel s, Object successTag, Object failTag) + { + _repliesTo = repliesTo; + _gateway = ioGateway; + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Tells the internal thread to connect to the given host and port + * Connection is done asynchronously, and an OperationCompleteMessage + * will be sent to the repliesTo queue when it has been done. + * @param hostName IP address or host name of computer to connect to + * @param port Port number to connect to (2960 is the port for a MUSCLE server) + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + */ + public void connect(String hostName, int port, Object successTag, Object failTag) + { + _sendQueue.postMessage(new ConnectMessage(hostName, port, successTag, failTag)); + } + + /** Tells the internal thread to listen on the given port. + * Connection is done asynchronoulsy. + * With this you will receive multiple copies of successTag. The first signals that we have bound to the port. + * If continually listening, this is all you will receive. However, if you are doing a one-shot listen(), the + * second successTag will signify that the connection has been established. + * @param s A constructed ServerSocket that has already properly bound to it's port. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that a new connection has been established. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection has been broken. (must connect first, we're listening) + * @param continuallyListen If true, the listen thread will continue to accept sockets until the MessageTranciever dies. If false, only a single connection is accepted. + */ + public void listen(ServerSocketChannel s, Object successTag, Object failTag, boolean continuallyListen) + { + _continuallyListen = continuallyListen; + _sendQueue.postMessage(new ConnectMessage(s, successTag, failTag)); + } + + /** Forces the transceiver to stop listening immediately if it is in a mode to continually listen. + * Of course, this is asynchronous. + */ + public void stopListening() + { + _continuallyListen = true; + if (_currentConnectThread instanceof ListenThread) _currentConnectThread.interrupt(); + } + + /** Tells the internal thread to disconnect. This will be done asynchronously. + * When the disconnection is done, the failTag specified previously in the connect() + * call will be sent to your reply queue. + */ + public void disconnect() + { + _sendQueue.postMessage(new ConnectMessage(null, 0, null, null)); + } + + /** Tells the internal thread to send a Message out over the TCP socket. + * This will be done asynchronously. + */ + public void sendOutgoingMessage(Message msg) + { + _sendQueue.postMessage(msg); + } + + /** + * Set a tag that will be sent whenever our queue of outgoing Messages + * becomes empty. (Default is NULL, meaning that no notifications will be sent) + * @param tag The notification tag to send, or NULL to disable. + */ + public void setNotifyOutputQueueDrainedTag(Object tag) + { + _outputQueueDrainedTag = tag; + } + + /** Any received Messages will be sent out to the TCP socket */ + public void messageReceived(Object msg, int numLeft) + { + if (msg instanceof Message) sendOutgoingMessage((Message)msg); + } + + /** Internal control class */ + private final class ConnectMessage + { + public ConnectMessage(String hostName, int port, Object successTag, Object failTag) + { + _hostName = hostName; + _port = port; + _successTag = successTag; + _failTag = failTag; + _sock = null; + } + + public ConnectMessage(ServerSocketChannel s, Object successTag, Object failTag) + { + _hostName = null; + _port = 0; + _successTag = successTag; + _failTag = failTag; + _sock = s; + } + + public String _hostName; + public int _port; + public Object _successTag; + public Object _failTag; + public ServerSocketChannel _sock; + } + + /** Logic for the connecting-and-sending thread */ + private final class MessageTransmitter implements MessageListener + { + public MessageTransmitter() + { + // empty + } + + public MessageTransmitter(ByteChannel s, Object successTag, Object failTag) + { + _failTag = failTag; + _currentConnectThread = null; // no longer in the connect period + if (s != null) + { + _channel = s; + new MessageReceiver(_channel, _connectionID); + sendReply(successTag); // ok, done! + } + else sendReply(_failTag); + } + + public void messageReceived(Object message, int numLeft) + { + if (message instanceof Message) + { + if (_channel != null) + { + try { + _gateway.flattenMessage(_channel, (Message) message); + } + catch(IOException ex) { + disconnect(_connectionID); + } + } + } + else if (message instanceof ConnectMessage) + { + disconnect(_connectionID); // make sure our previous connection is gone + + ConnectMessage cmsg = (ConnectMessage) message; + if (cmsg._hostName != null) + { + // Do the actual connecting in Yet Another Thread, so that we can remain + // responsive to the user's requests during the (possibly lengthy) connect period. + _currentConnectThread = new ConnectThread(cmsg); // connect thread is started in ctor + } + else if (cmsg._sock != null) + { + // If a socket was passed in, we know we need to listen! + _currentConnectThread = new ListenThread(cmsg); + } + } + else if (message instanceof ConnectThread) + { + // When our ConnectThread has finished, he sends himself back to us with the connect results. + ConnectThread ct = (ConnectThread) message; + SocketChannel s = ct._socketChannel; + ConnectMessage cmsg = ct._cmsg; + if (ct == _currentConnectThread) + { + _currentConnectThread = null; // no longer in the connect period + if (s != null) + { + _channel = s; + _failTag = cmsg._failTag; + new MessageReceiver(_channel, _connectionID); + if (_channel.isOpen()) sendReply(cmsg._successTag); // ok, done! + } + else sendReply(cmsg._failTag); + } + else + { + // oops, no use for the socket, so just close it and forget about it + if (s != null) try {s.close();} catch(Exception ex) {/* don't care */} + sendReply(cmsg._failTag); + } + } + else if (message instanceof MessageReceiver) disconnect(((MessageReceiver)message).connectionID()); + + if (numLeft == 0) + { + if (_outputQueueDrainedTag != null) sendReply(_outputQueueDrainedTag); + } + } + + /** Close the connection and notify the user (only if the connection ID is correct) */ + private void disconnect(int id) + { + if ((_channel != null)&&(_connectionID == id)) + { + try { + _channel.close(); + } + catch(IOException ex) { + // ignore + } + sendReply(_failTag); + _channel = null; + _failTag = null; + _currentConnectThread = null; + _connectionID++; + } + } + + /** Send a message back to the user */ + private void sendReply(Object replyObj) + { + if ((_repliesTo != null)&&(replyObj != null)) _repliesTo.postMessage(replyObj); + } + + /** Logic for our receive-incoming-data thread */ + private class MessageReceiver implements MessageListener + { + public MessageReceiver(ByteChannel in, int cid) + { + _in = in; + _connectionID = cid; + (new MessageQueue(this)).postMessage(null); // start the background reader thread + } + + public void messageReceived(Object msg, int numLeft) + { + int maxIncomingMessageSize = _gateway.getMaximumIncomingMessageSize(); + + ByteBuffer buffer = ByteBuffer.allocate(128*1024); // default receive-buffer-size is 128KB, per my discussion with Lior + buffer.order(ByteOrder.LITTLE_ENDIAN); + int numBytesRead; + try { + while(true) { + numBytesRead = _in.read(buffer); + if (numBytesRead < 0) { + // The peer has disconnected, so go away. + _sendQueue.postMessage(this); + break; + } + buffer.limit(buffer.position()); + buffer.rewind(); + try { + while (buffer.remaining() > 0) { + buffer.mark(); + sendReply(_gateway.unflattenMessage(buffer)); + } + buffer.compact(); + } + catch (NotEnoughDataException ned) + { + int eodPosition = buffer.position(); + buffer.reset(); + + // Check if there is a need to enlarge the buffer + if (buffer.capacity() < (eodPosition + ned.getNumMissingBytes() - buffer.position())) { + // The data will exceed the buffer, even after compacting. + int desiredSize = buffer.capacity() + ned.getNumMissingBytes(); + int doubleSize = buffer.capacity()*2; + if (desiredSize < doubleSize) desiredSize = (doubleSize < maxIncomingMessageSize) ? doubleSize : maxIncomingMessageSize; + if (desiredSize <= maxIncomingMessageSize) + { + ByteBuffer tmp = ByteBuffer.allocate(desiredSize); + tmp.order(ByteOrder.LITTLE_ENDIAN); + tmp.put(buffer); + buffer = tmp; + } + else { + // Not enough memory for the message that is expected, and not allowed to allocate enough memory. + throw new UnflattenFormatException("Incoming message too large: "+(buffer.capacity() + ned.getNumMissingBytes())+"/"+maxIncomingMessageSize); + } + } + else { + int newPosition = eodPosition - buffer.position(); + buffer.compact(); + buffer.position(newPosition); + } + } + } + } + catch(Exception ex) { + // oops, socket broke... notify out master that we're leaving (by posting ourself as a message, teehee) + _sendQueue.postMessage(this); + } + } + + public int connectionID() {return _connectionID;} + + private int _connectionID; + private ByteChannel _in; + } + + private Object _failTag = null; + private ByteChannel _channel = null; + } + + // A little class for doing TCP connects in a separate thread (so the MessageTransceiver won't block during the connect) */ + private class ConnectThread implements MessageListener + { + private MessageQueue myQueue = null; + + public ConnectThread(ConnectMessage cmsg) + { + _cmsg = cmsg; + myQueue = new MessageQueue(this); + myQueue.postMessage(null); // just to kick off the thread + } + + public void messageReceived(Object obj, int numLeft) + { + try { + _socketChannel = SocketChannel.open(); + _socketChannel.socket().setReceiveBufferSize(128*1024); + _socketChannel.socket().setSendBufferSize(128*1024); + _socketChannel.connect(new InetSocketAddress(_cmsg._hostName, _cmsg._port)); + } + catch(IOException ex) { + // do nothing + } + _sendQueue.postMessage(this); + } + + public Runnable getThread(){ + return myQueue; + } + + public void interrupt(){ + myQueue.interrupt(); + } + + public SocketChannel _socketChannel = null; + public ConnectMessage _cmsg; + } + + // A little class for listening for TCP connections in a separate thread. -- Author: Bryan Varner + // We override the listener method of ConnectThread and block for any incoming connections to said port. + private class ListenThread extends ConnectThread + { + public ListenThread(ConnectMessage cmsg) + { + super(cmsg); + } + + public void messageReceived(Object obj, int numLeft) + { + ServerSocketChannel servSock = _cmsg._sock; + + try { + while(true) { + // At this point we know we want at least one connection, we'll test for continuance at the end. + _socketChannel = servSock.accept(); + + if (_continuallyListen == false) + { + // If we aren't supposed to keep listening, post this object, killing this thread. + _sendQueue.postMessage(this); + } + else + { + // If we are supposed to keep goin, send the new socket back to the object that started the transceiver, and let them deal with it. + if (_repliesTo != null) _repliesTo.postMessage(_socketChannel); + } + } + } catch (IOException ex){ + // You're getting the preverbial screw. Hope you're enjoying it. + } + } + } + + private AbstractMessageIOGateway _gateway; + private MessageQueue _repliesTo; + private MessageQueue _sendQueue; + private int _connectionID = 0; + private ConnectThread _currentConnectThread = null; // non-null if we are connecting or listening (in a slave thread) + private boolean _continuallyListen = false; + private Object _outputQueueDrainedTag = null; +} diff --git a/java/com/meyer/muscle/client/StorageReflectConstants.java b/java/com/meyer/muscle/client/StorageReflectConstants.java new file mode 100644 index 00000000..fec9f987 --- /dev/null +++ b/java/com/meyer/muscle/client/StorageReflectConstants.java @@ -0,0 +1,487 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.client; + +/** Constants that are understood by the StorageReflectSession class of the standard MUSCLE server + * See documentation or comments at the end of this file for what they all mean + */ +public interface StorageReflectConstants +{ + /** Unused dummy value, marks the beginning of the reserved command array */ + public static final int BEGIN_PR_COMMANDS = 558916400; + + /** Adds/replaces the given fields in the parameters table */ + public static final int PR_COMMAND_SETPARAMETERS = 558916401; + + /** Returns the current parameter set to the client */ + public static final int PR_COMMAND_GETPARAMETERS = 558916402; + + /** deletes the parameters specified in PR_NAME_KEYS */ + public static final int PR_COMMAND_REMOVEPARAMETERS = 558916403; + + /** Adds/replaces the given message in the data table */ + public static final int PR_COMMAND_SETDATA = 558916404; + + /** Retrieves the given message(s) in the data table */ + public static final int PR_COMMAND_GETDATA = 558916405; + + /** Removes the gives message(s) from the data table */ + public static final int PR_COMMAND_REMOVEDATA = 558916406; + + /** Removes data from outgoing result messages */ + public static final int PR_COMMAND_JETTISONRESULTS = 558916407; + + /** Insert nodes underneath a node, as an ordered list */ + public static final int PR_COMMAND_INSERTORDEREDDATA = 558916408; + + /** Echo this message back to the sending client */ + public static final int PR_COMMAND_PING = 558916409; + + /** Kick matching clients off the server (Requires privilege) */ + public static final int PR_COMMAND_KICK = 558916410; + + /** Add ban patterns to the server's ban list (Requires privilege) */ + public static final int PR_COMMAND_ADDBANS = 558916411; + + /** Remove ban patterns from the server's ban list (Requires privilege) */ + public static final int PR_COMMAND_REMOVEBANS = 558916412; + + /** Submessages under PR_NAME_KEYS are executed in order, as if they came separately */ + public static final int PR_COMMAND_BATCH = 558916413; + + /** Server will ignore this message */ + public static final int PR_COMMAND_NOOP = 558916414; + + /** Move one or more intries in a node index to a different spot in the index */ + public static final int PR_COMMAND_REORDERDATA = 558916415; + + /** Add require patterns to the server's require list (Requires ban privilege) */ + public static final int PR_COMMAND_ADDREQUIRES = 558916416; + + /** Remove require patterns from the server's require list (Requires ban privilege) */ + public static final int PR_COMMAND_REMOVEREQUIRES = 558916417; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED11 = 558916418; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED12 = 558916419; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED13 = 558916420; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED14 = 558916421; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED15 = 558916422; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED16 = 558916423; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED17 = 558916424; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED18 = 558916425; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED19 = 558916426; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED20 = 558916427; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED21 = 558916428; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED22 = 558916429; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED23 = 558916430; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED24 = 558916431; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED25 = 558916432; + + /** Dummy value to indicate the end of the reserved command range */ + public static final int END_PR_COMMANDS = 558916433; + + /** Marks beginning of range of 'what' codes that may be generated by the StorageReflectSession and sent back to the client */ + public static final int BEGIN_PR_RESULTS = 558920240; + + /** Sent to client in response to PR_COMMAND_GETPARAMETERS */ + public static final int PR_RESULT_PARAMETERS = 558920241; + + /** Sent to client in response to PR_COMMAND_GETDATA, or subscriptions */ + public static final int PR_RESULT_DATAITEMS = 558920242; + + /** Sent to client to tell him that we don't know how to process his request message */ + public static final int PR_RESULT_ERRORUNIMPLEMENTED = 558920243; + + /** Notification that an entry has been inserted into an ordered index */ + public static final int PR_RESULT_INDEXUPDATED = 558920244; + + /** Response from a PR_COMMAND_PING message */ + public static final int PR_RESULT_PONG = 558920245; + + /** Your client isn't allowed to do something it tried to do */ + public static final int PR_RESULT_ERRORACCESSDENIED = 558920246; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED4 = 558920247; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED5 = 558920248; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED6 = 558920249; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED7 = 558920250; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED8 = 558920251; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED9 = 558920252; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED10 = 558920253; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED11 = 558920254; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED12 = 558920255; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED13 = 558920256; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED14 = 558920257; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED15 = 558920258; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED16 = 558920259; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED17 = 558920260; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED18 = 558920261; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED19 = 558920262; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED20 = 558920263; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED21 = 558920264; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED22 = 558920265; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED23 = 558920266; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED24 = 558920267; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED25 = 558920268; + + /** Reserved for future expansion */ + public static final int END_PR_RESULTS = 558920269; + + /** Privilege bit, indicates that the client is allowed to kick other clients off the MUSCLE server */ + public static final int PR_PRIVILEGE_KICK = 0; + + /** Privilege bit, indicates that the client is allowed to ban other clients from the MUSCLE server */ + public static final int PR_PRIVILEGE_ADDBANS = 1; + + /** Privilege bit, indicates that the client is allowed to unban other clients from the MUSCLE server */ + public static final int PR_PRIVILEGE_REMOVEBANS = 2; + + /** Number of defined privilege bits */ + public static final int PR_NUM_PRIVILEGES = 3; + + /** Op-code that indicates that an entry was inserted at the given slot index, with the given ID */ + public static final char INDEX_OP_ENTRYINSERTED = 'i'; + + /** Op-code that indicates that an entry was removed from the given slot index, had the given ID */ + public static final char INDEX_OP_ENTRYREMOVED = 'r'; + + /** Op-code that indicates that the index was cleared */ + public static final char INDEX_OP_CLEARED = 'c'; + + /** Field name for key-strings (often node paths or regex expressions) */ + public static final String PR_NAME_KEYS = "!SnKy"; + + /** Field name to contains node path strings of removed data items */ + public static final String PR_NAME_REMOVED_DATAITEMS = "!SnRd"; + + /** Field name (any type): If present in a PR_COMMAND_SETPARAMETERS message, disables inital-value-send from new subscriptions */ + public static final String PR_NAME_SUBSCRIBE_QUIETLY = "!SnQs"; + + /** Field name (any type): If present in a PR_COMMAND_SETDATA message, subscribers won't be notified about the change. */ + public static final String PR_NAME_SET_QUIETLY = "!SnQ2"; + + /** Field name (any type): If present in a PR_COMMAND_REMOVEDATA message, subscribers won't be notified about the change. */ + public static final String PR_NAME_REMOVE_QUIETLY = "!SnQ3"; + + /** Field name (any type): If set as parameter, include ourself in wildcard matches */ + public static final String PR_NAME_REFLECT_TO_SELF = "!Self"; + + /** Field name (any type): If set as a parameter, disable all subscription update messaging. */ + public static final String PR_NAME_DISABLE_SUBSCRIPTIONS = "!Dsub"; + + /** Field name of int parameter; sets max # of items per PR_RESULT_DATAITEMS message */ + public static final String PR_NAME_MAX_UPDATE_MESSAGE_ITEMS = "!MxUp"; + + /** Field name of String returned in parameter set; contains this session's /host/sessionID path */ + public static final String PR_NAME_SESSION_ROOT = "!Root"; + + /** Field name for Message: In PR_RESULT_ERROR_* messages, holds the client's message that failed to execute. */ + public static final String PR_NAME_REJECTED_MESSAGE = "!Rjct"; + + /** Field name of int32 bitchord of client's PR_PRIVILEGE_* bits. */ + public static final String PR_NAME_PRIVILEGE_BITS = "!Priv"; + + /** Field name of int64 indicating how many more bytes are available for MUSCLE server to use */ + public static final String PR_NAME_SERVER_MEM_AVAILABLE = "!Mav"; + + /** Field name of int64 indicating how many bytes the MUSCLE server currently has allocated */ + public static final String PR_NAME_SERVER_MEM_USED = "!Mus"; + + /** Field name of int64 indicating how the maximum number of bytes the MUSCLE server may have allocated at once. */ + public static final String PR_NAME_SERVER_MEM_MAX = "!Mmx"; + + /** Field name of String indicating version of MUSCLE that the server was compiled from */ + public static final String PR_NAME_SERVER_VERSION = "!Msv"; + + /** Field name of int64 indicating how many microseconds have elapsed since the server was started. */ + public static final String PR_NAME_SERVER_UPTIME = "!Mup"; + + /** Field name of int32 indicating how many database nodes may be uploaded by this client (total). */ + public static final String PR_NAME_MAX_NODES_PER_SESSION = "!Mns"; + + /** Field name of a string that the server will replace with the session ID string of your + * session in any outgoing client-to-client messages. */ + public static final String PR_NAME_SESSION = "session"; + + /** this field name's submessage is the payload of the current node + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODEDATA = "data"; + + /** this field name's submessage represents the children of the current node (recursive) + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODECHILDREN = "kids"; + + /** this field name's submessage represents the index of the current node + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODEINDEX = "index"; + + /** + * Field name of a message that causes the server to reply with compressed messages. + * This will only work with the Java API if the JZLib library ( http://www.jcraft.com/jzlib/ ) + * is on the classpath. + */ + public static final String PR_NAME_REPLY_ENCODING = "!Enc"; +} + +// This is a specialization of AbstractReflectSession that adds several +// useful capabilities to the Reflect Server. Abilities include: +// - Messages can specify (via wildcard path matching) which other +// clients they should be reflected to. If the message doesn't specify +// a path, then the session's default path can be used. +// - Clients can upload and store data on the server (in the server's RAM only). +// This data will remain accessible to all sessions until the client disconnects. +// - Clients can "subscribe" to server state information and be automatically +// notified when the information has changed. +// +// CLIENT-TO-SERVER MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_COMMAND_SETPARAMETERS: +// All fields of the message are placed into the session's parameter set. +// Any fields in the previous parameters set with matching names are replaced. +// Currently parsed parameter names are as follows: +// +// "SUBSCRIBE:" : Any parameter name that begins with the prefix SUBSCRIBE: +// is taken to represent a request to monitor the contents of +// all nodes whose pathnames match the path that follows. So, +// for example, the parameter name +// +// SUBSCRIBE:/*/*/Joe +// +// Indicates a request to watch all nodes with paths that match +// the regular expression /*/*/Joe. The value +// these parameters are unimportant, and are not looked at. +// Thus, these parameters may be of any type. Once a SUBSCRIBE +// parameter has been added, any data nodes that match the specified +// path will be returned immediately to the client in a PR_RESULT_DATAITEMS +// message. Furthermore, any time these nodes are modified or deleted, +// or any time a new node is added that matches the path, another +// PR_RESULT_DATAITEMS message will be sent to notify the client of +// the change. +// +// PR_NAME_KEYS : If set, any non-"special" messages without a +// PR_NAME_KEYS field will be reflected to clients +// who match at least one of the set of key-paths +// listed here. (Should be one or more string values) +// +// PR_NAME_REFLECT_TO_SELF : If set, wildcard matches can match the current session. +// Otherwise, wildcard matches with the current session will +// be suppressed (on the grounds that your client already knows +// the value of anything that it uploaded, and doesn't need to +// be reminded of it). This field may be of any type, only +// its existence/non-existence is relevant. +// +// +// if 'what' is PR_COMMAND_GETPARAMETERS: +// Causes a PR_RESULT_PARAMETERS message to be returned to the client. The returned message +// contains the entire current parameter set. +// +// if 'what' is PR_COMMAND_REMOVEPARAMETERS: +// The session looks for PR_NAME_KEYS string entrys. For each string found +// under this entry, any matching field in the parameters message are deleted. +// Wildcards are permitted in these strings. (So e.g. "*" would remove ALL parameters) +// +// if 'what' is PR_COMMAND_SETDATA: +// Scans the message for all fields of type message. Each message field +// should contain only one message. The field's name is parsed as a local +// key-path of the data item (e.g. "myData", or "imageInfo/colors/red"). +// Each contained message will be stored in the local session's data tree under +// that key path. (Note: fields that start with a '/' are not allowed, and +// will be ignored!) +// +// if 'what' is PR_COMMAND_REMOVEDATA: +// Removes all data nodes that match the path(s) in the PR_NAME_KEYS string field. +// Paths should be specified relative to this session's root node (i.e. they should +// not start with a slash) +// +// if 'what' is PR_COMMAND_GETDATA: +// The session looks for one or more strings in the PR_NAME_KEYS field. Each +// string represents a key-path indicating which information the client is +// interested in retrieving. If there is no leading slash, "/*/*/" will be +// implicitely prepended. Here are some valid example key-paths: +// /*/*/color (gets "color" from all hostnames, all session IDs) +// /joe.blow.com/*/shape (gets "shape" from all sessions connected from joe.blow.com) +// /joe.blow.com/19435935093/sound (gets "sound" from a single session) +// /*/*/vehicleTypes/* (gets all vehicle types from all clients) +// j* (equivalent to "/*/*/j*") +// shape/* (equivalent to "/*/*/shape/*") +// The union of all the sets of matching data nodes specified by these paths will be +// added to a single PR_RESULT_DATAITEMS message which is then passed back to the client. +// Each matching message is added with its full path as a field name. +// +// if 'what' is PR_COMMAND_INSERTORDEREDDATA: +// The session looks for one or more messages in the PR_NAME_KEYS field. Each +// string represents a wildpath, rooted at this session's node (read: no leading +// slash should be present) that specifies zero or more data nodes to insert ordered/ +// indexed children under. Each node in the union of these node sets will have new +// ordered/indexed child nodes created underneath it. The names of these new child +// nodes will be chosen algorithmically by the server. There will be one child node +// created for each sub-message in this message. Sub-messages may be added under any +// field name; if the field name happens to be the name of a currently indexed child, +// the new message node will be be inserted *before* the specified child in the index. +// Otherwise, it will be appended to the end of the index. Clients who have subscribed +// to the specified nodes will see the updates to the index; clients who have subscribed +// to the children will get updates of the actual data as well. +// +// if 'what' is PR_COMMAND_PING: +// The session will change the message's 'what' code to PR_RESULT_PONG, and send +// it right back to the client. In this way, the client can find out (a) that +// the server is still alive, (b) how long it takes the server to respond, and +// (c) that any previously sent operations have finished. +// +// if 'what' is PR_COMMAND_KICK: +// The server will look for one or more strings in the PR_NAME_KEYS field. It will +// do a search of the database for any nodes matching one or more of the node paths +// specified by these strings, and will kick any session with matching nodes off +// of the server. Of course, this will only be done if the client who sent the +// PR_COMMAND_KICK field has PR_PRIVILEGE_KICK access. +// +// if 'what' is PR_COMMAND_ADDBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be added to the server's "banned IP list", and +// subsequent connection attempts from IP addresses matching any of these ban strings +// will be denied. Of course, this will only be done if the client who sent the +// PR_COMMAND_ADDBANS field has PR_PRIVILEGE_ADDBANS access. +// +// if 'what' is PR_COMMAND_REMOVEBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be used a pattern-matching strings against the +// current set of "ban" patterns. Any "ban" patterns that are matched by any +// of the PR_NAME_KEYS strings will be removed from the "banned IP patterns" set, +// so that IP addresses who matched those patterns will be able to connect to +// the server again. Of course, this will only be done if the client who sent the +// PR_COMMAND_REMOVEBANS field has PR_PRIVILEGE_REMOVEBANS access. +// +// if 'what' is PR_COMMAND_RESERVED_*: +// The server will change the 'what' code of your message to PR_RESULT_UNIMPLEMENTED, +// and send it back to your client. +// +// if 'what' is PR_RESULT_*: +// The message will be silently dropped. You are not allowed to send PR_RESULT_(*) +// messages to the server, and should be very ashamed of yourself for even thinking +// about it. +// +// All other 'what' codes +// Messages with other 'what' codes are simply reflected to other clients verbatim. +// If a PR_NAME_KEYS string field is found in the message, then it will be parsed as a +// set of key-paths, and only other clients who can match at least one of these key-paths +// will receive the message. If no PR_NAME_KEYS field is found, then the parameter +// named PR_NAME_KEYS will be used as a default value. If that parameter isn't +// found, then the message will be reflected to all clients (except this one). +// +// SERVER-TO-CLIENT MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_RESULT_PARAMETERS: +// The message contains the complete parameter set that is currently associated with +// this client on the server. Parameters may have any field name, and be of any type. +// Certain parameter names are recognized by the StorageReflectSession as having special +// meaning; see the documentation on PR_COMMAND_SETPARAMETERS for information about these. +// +// if 'what' is PR_RESULT_DATAITEMS: +// The message contains information about data that is stored on the server. All stored +// data is stored in the form of Messages. Thus, all data in this message will be +// in the form of a message field, with the field's name being the fully qualified path +// of the node it represents (e.g. "/my.computer.com/5/MyNodeName") and the value being +// the stored data itself. Occasionally it is necessary to inform the client that a data +// node has been deleted; this is done by adding the deceased node's path name as a string +// to the PR_NAME_REMOVED_DATAITEM field. If multiple nodes were removed, there may be +// more than one string present in the PR_NAME_REMOVED_DATAITEM field. +// +// if 'what' is PR_RESULT_INDEXUPDATED: +// The message contains information about index entries that were added (via PR_COMMAND_INSERTORDERREDDATA) +// to a node that the client is subscribed to. Each entry's field name is the fully qualified +// path of a subscribed node, and the value(s) are strings of this format: "%c%lu:%s", %c is +// a single character that is one of the INDEX_OP_* values, the %lu is an index the item was added to +// or removed from, and %s is the key string for the child in question. +// Note that there may be more than one value per field! +// +// if 'what' is PR_RESULT_ERRORUNIMPLEMENTED: +// Your message is being returned to you because it tried to use functionality that +// hasn't been implemented on the server side. This usually happens if you are trying +// to use a new MUSCLE feature with an old MUSCLE server. +// +// if 'what' is PR_RESULT_PONG: +// Your PR_COMMAND_PING message has been returned to you as a PR_RESULT_PONG message. +// +// if 'what' is PR_RESULT_ERRORACCESSDENIED: +// You tried to do something that you don't have permission to do (such as kick, ban, +// or unban another user). +// +// if 'what' is anything else: +// This message was reflected to your client by a neighboring client session. The content +// of the message is not specified by the StorageReflectSession; it just passes any message +// on verbatim. + diff --git a/java/com/meyer/muscle/iogateway/AbstractMessageIOGateway.java b/java/com/meyer/muscle/iogateway/AbstractMessageIOGateway.java new file mode 100644 index 00000000..fb2b93df --- /dev/null +++ b/java/com/meyer/muscle/iogateway/AbstractMessageIOGateway.java @@ -0,0 +1,80 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ + +package com.meyer.muscle.iogateway; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.NotEnoughDataException; +import com.meyer.muscle.support.UnflattenFormatException; + +/** Interface for an object that knows how to translate bytes into Messages, and vice versa. */ +public interface AbstractMessageIOGateway +{ + public final static int MUSCLE_MESSAGE_DEFAULT_ENCODING = 1164862256; // 'Enc0' ... our default (plain-vanilla) encoding + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_1 = 1164862257; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_2 = 1164862258; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_3 = 1164862259; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_4 = 1164862260; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_5 = 1164862261; + /** This is the recommended CPU vs space-savings setting for zlib */ + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_6 = 1164862262; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_7 = 1164862263; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_8 = 1164862264; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_9 = 1164862265; + + /** Reads from the input stream until a Message can be assembled and returned. + * @param in The input stream to read from. + * @throws IOException if there is an error reading from the stream. + * @throws UnflattenFormatException if there is an error parsing the data in the stream. + * @return The next assembled Message. + */ + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException; + + /** As above, but unflattens the Message based on the contents of the supplied ByteBuffer instead. + * @param in The ByteBuffer to read from. + * @throws IOException if there is an error reading from the byte buffer. + * @throws UnflattenFormatException if there is an error parsing the data in the byte buffer. + * @throws NotEnoughDataException if the byte buffer doesn't contain enough data to unflatten a message. + * @return The next assembled Message. + */ + public Message unflattenMessage(ByteBuffer in) throws IOException, UnflattenFormatException, NotEnoughDataException; + + /** Converts the given Message into bytes and sends it out the stream. + * @param out the data stream to send the converted bytes to. + * @param msg the Message to convert. + * @throws IOException if there is an error writing to the stream. + */ + public void flattenMessage(DataOutput out, Message msg) throws IOException; + + /** As above, but flattens the Message to supplied ByteChannel instead. + * @param out the ByteChannel send the converted bytes to. + * @param msg the Message to convert. + * @throws IOException if there is an error writing to the ByteChannel. + */ + public void flattenMessage(ByteChannel out, Message msg) throws IOException; + + /** Converts the given Message into a ByteBuffer and returns the filled buffer + * to the caller. The Buffer's limit will be set to the end of the message, and + * the buffer's position will be set to the start of the message. + * @param msg the Message to flatten. + * @return A filled ByteBuffer + * @throws IOException if there is an error writing to the ByteChannel. + */ + public ByteBuffer flattenMessage(Message msg) throws IOException; + + /** Should be implemented to return the largest allowable incoming message size, in bytes. + * If there is no limit, this method should be implemented to return Integer.MAX_VALUE. + */ + public int getMaximumIncomingMessageSize(); + + /** + * Change the currently used outgoing encoding. + * @param newEncoding + */ + public void setOutgoingEncoding(int newEncoding); +} diff --git a/java/com/meyer/muscle/iogateway/JZLibMessageIOGateway.java b/java/com/meyer/muscle/iogateway/JZLibMessageIOGateway.java new file mode 100644 index 00000000..53938e71 --- /dev/null +++ b/java/com/meyer/muscle/iogateway/JZLibMessageIOGateway.java @@ -0,0 +1,325 @@ +package com.meyer.muscle.iogateway; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.LEDataInputStream; +import com.meyer.muscle.support.LEDataOutputStream; +import com.meyer.muscle.support.NotEnoughDataException; +import com.meyer.muscle.support.UnflattenFormatException; + +public class JZLibMessageIOGateway extends MessageIOGateway +{ + protected ZStream _deflateStream; + protected ZStream _inflateStream; + + protected ByteArrayOutputStream _outputByteBuffer; + protected LEDataOutputStream _leOutputStream; + private ByteBuffer _msgBuf; + private ByteBuffer _outgoing; + private ByteBuffer _uncompressedDataBuffer; + + public JZLibMessageIOGateway() + { + super(); + } + + public JZLibMessageIOGateway(int encoding) + { + super(encoding); + } + + public void setOutgoingEncoding(int newEncoding) + { + super.setOutgoingEncoding(newEncoding); + _deflateStream = null; + } + + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException + { + int numBytes = in.readInt(); + if (numBytes > getMaximumIncomingMessageSize()) throw new UnflattenFormatException("Incoming message was too large! (" + numBytes + " bytes, " + getMaximumIncomingMessageSize() + " allowed!)"); + + int encoding = in.readInt(); + if (encoding == MUSCLE_MESSAGE_DEFAULT_ENCODING) + { + Message pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + int independentValue = in.readInt(); + int flatSize = in.readInt(); + boolean needToInit = false; + boolean independent = (independentValue == ZLIB_CODEC_HEADER_INDEPENDENT); + if (_inflateStream == null) + { + _inflateStream = new ZStream(); + needToInit = true; + } + if (independent) needToInit = true; + byte[] currChunk = new byte[numBytes - 8]; + byte[] uncompressedData = new byte[flatSize]; + in.readFully(currChunk); // Get the data + + // Uncompress the data + _inflateStream.next_in = currChunk; + _inflateStream.next_in_index = 0; + _inflateStream.next_out = uncompressedData; + _inflateStream.next_out_index = 0; + if (needToInit) _inflateStream.inflateInit(); + + _inflateStream.avail_in = currChunk.length; + _inflateStream.avail_out = uncompressedData.length; + int err = JZlib.Z_DATA_ERROR; + while((_inflateStream.next_out_index 0) + { + err = _deflateStream.deflate(JZlib.Z_SYNC_FLUSH); + if ((err == JZlib.Z_STREAM_ERROR)||(err == JZlib.Z_DATA_ERROR)||(err == JZlib.Z_NEED_DICT)||(err == JZlib.Z_BUF_ERROR)) throw new IOException("Problem compressing the outgoing message."); + } + + out.writeInt(_deflateStream.next_out_index + 8); + out.writeInt(oge); + out.writeInt(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + out.writeInt(flatSize); + out.write(compressed, 0, _deflateStream.next_out_index); + _outputByteBuffer.reset(); + } + + public Message unflattenMessage(ByteBuffer in) throws IOException, UnflattenFormatException , NotEnoughDataException + { + if (in.remaining() < 8) throw new NotEnoughDataException(8-in.remaining()); + + int numBytes = in.getInt(); + if (numBytes > getMaximumIncomingMessageSize()) throw new UnflattenFormatException("Incoming message was too large! (" + numBytes + " bytes, " + getMaximumIncomingMessageSize() + " allowed!)"); + + int encoding = in.getInt(); + if (in.remaining() < numBytes) + { + in.position(in.limit()); + throw new NotEnoughDataException(numBytes-in.remaining()); + } + + if (encoding == MUSCLE_MESSAGE_DEFAULT_ENCODING) + { + Message pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + int independentValue = in.getInt(); + int flatSize = in.getInt(); + boolean needToInit = false; + boolean independent = (independentValue == ZLIB_CODEC_HEADER_INDEPENDENT); + if (_inflateStream == null) + { + _inflateStream = new ZStream(); + needToInit = true; + } + if (independent) needToInit = true; + if (_uncompressedDataBuffer == null || _uncompressedDataBuffer.capacity() < flatSize) + { + _uncompressedDataBuffer = ByteBuffer.allocate(flatSize); + _uncompressedDataBuffer.order(ByteOrder.LITTLE_ENDIAN); + } + _uncompressedDataBuffer.rewind(); + _uncompressedDataBuffer.limit(flatSize); + + // Uncompress the data, use the byte buffer's backing array to do the work. + _inflateStream.next_in = in.array(); + _inflateStream.next_in_index = in.position(); + _inflateStream.next_out = _uncompressedDataBuffer.array(); + _inflateStream.next_out_index = 0; + if (needToInit) _inflateStream.inflateInit(); + + _inflateStream.avail_in = (numBytes - 8); + _inflateStream.avail_out = flatSize; + int err = JZlib.Z_DATA_ERROR; + + do { + err = _inflateStream.inflate(JZlib.Z_SYNC_FLUSH); + } while(err == JZlib.Z_OK && (_inflateStream.next_out_index 0) + { + if (numBytesWritten == 0) + { + selector.select(); + + // Slight shortcut here - there's only one key registered in this selector object. + if (selector.selectedKeys().isEmpty()) continue; + else selector.selectedKeys().clear(); + } + numBytesWritten = out.write(_outgoing); + } + selector.close(); + } else out.write(_outgoing); + } else out.write(_outgoing); + } + + private void prepareBuffers(Message msg, int flatSize) throws IOException + { + int oge = getOutgoingEncoding(); + + // Else, we need to compress the message. + boolean independent = false; + if (_deflateStream == null) + { + _deflateStream = new ZStream(); + _deflateStream.deflateInit(oge - MUSCLE_MESSAGE_DEFAULT_ENCODING, 15); // suppress the zlibheaders + + independent = true; + } + if ((_msgBuf == null)||(_msgBuf.capacity() < flatSize)) + { + _msgBuf = ByteBuffer.allocate(flatSize); + _msgBuf.order(ByteOrder.LITTLE_ENDIAN); + } + _msgBuf.rewind(); + _msgBuf.limit(flatSize); + msg.flatten(_msgBuf); + + if ((_outgoing == null)||(_outgoing.capacity() < (flatSize + 16))) + { + _outgoing = ByteBuffer.allocate(flatSize + 16); + _outgoing.order(ByteOrder.LITTLE_ENDIAN); + } + + _deflateStream.next_in = _msgBuf.array(); + _deflateStream.next_in_index = 0; + _deflateStream.next_out = _outgoing.array(); + _deflateStream.next_out_index = 16; + _deflateStream.avail_in = flatSize; + _deflateStream.avail_out = flatSize; + + int err = JZlib.Z_DATA_ERROR; + while(_deflateStream.avail_in > 0) + { + err = _deflateStream.deflate(JZlib.Z_SYNC_FLUSH); + if ((err == JZlib.Z_STREAM_ERROR)||(err == JZlib.Z_DATA_ERROR)||(err == JZlib.Z_NEED_DICT)||(err == JZlib.Z_BUF_ERROR)) throw new IOException( "Problem compressing the outgoing message."); + } + + _outgoing.rewind(); + _outgoing.limit(_deflateStream.next_out_index); + + _outgoing.putInt(_deflateStream.next_out_index - 8); + _outgoing.putInt(oge); + _outgoing.putInt(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + _outgoing.putInt(flatSize); + _outgoing.rewind(); + } +} diff --git a/java/com/meyer/muscle/iogateway/MessageIOGateway.java b/java/com/meyer/muscle/iogateway/MessageIOGateway.java new file mode 100644 index 00000000..6d1f8d55 --- /dev/null +++ b/java/com/meyer/muscle/iogateway/MessageIOGateway.java @@ -0,0 +1,174 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.iogateway; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; + +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.NotEnoughDataException; +import com.meyer.muscle.support.UnflattenFormatException; + +/** A gateway that converts to and from the 'standard' MUSCLE flattened message byte stream. */ +public class MessageIOGateway implements AbstractMessageIOGateway +{ + private int _outgoingEncoding; + private ByteBuffer _outgoing; + + public MessageIOGateway() + { + _outgoingEncoding = MUSCLE_MESSAGE_DEFAULT_ENCODING; + } + + public MessageIOGateway(int encoding) + { + setOutgoingEncoding(encoding); + } + + public void setOutgoingEncoding(int newEncoding) + { + if ((newEncoding < MUSCLE_MESSAGE_DEFAULT_ENCODING) || (newEncoding > MUSCLE_MESSAGE_ENCODING_ZLIB_9)) throw new UnsupportedOperationException("The argument is not a supported encoding"); + _outgoingEncoding = newEncoding; + } + + /** Returns this gateway's current MUSCLE_MESSAGE_ENCODING_* value */ + public final int getOutgoingEncoding() + { + return _outgoingEncoding; + } + + /** Set the largest allowable size for incoming Message objects. Default value is Integer.MAX_VALUE. */ + public void setMaximumIncomingMessageSize(int maxSize) {_maximumIncomingMessageSize = maxSize;} + + /** Returns the current maximum-incoming-message-size. Default value is Integer.MAX_VALUE. */ + public int getMaximumIncomingMessageSize() {return _maximumIncomingMessageSize;} + + public Message unflattenMessage(ByteBuffer in) throws IOException, UnflattenFormatException, NotEnoughDataException + { + if (in.remaining() < 8) throw new NotEnoughDataException(8-in.remaining()); + + int numBytes = in.getInt(); + if (numBytes > getMaximumIncomingMessageSize()) throw new UnflattenFormatException("Incoming message was too large! (" + numBytes + " bytes, " + getMaximumIncomingMessageSize() + " allowed!)"); + + int encoding = in.getInt(); + if (encoding != MUSCLE_MESSAGE_DEFAULT_ENCODING) throw new IOException(); + if (in.remaining() < numBytes) + { + in.position(in.limit()); + throw new NotEnoughDataException(numBytes-in.remaining()); + } + + Message pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + + /** Reads from the input stream until a Message can be assembled and returned. + * @param in The input stream to read from. + * @throws IOException if there is an error reading from the stream. + * @throws UnflattenFormatException if there is an error parsing the data in the stream. + * @return The next assembled Message. + */ + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException + { + int numBytes = in.readInt(); + if (numBytes > getMaximumIncomingMessageSize()) throw new UnflattenFormatException("Incoming message was too large! (" + numBytes + " bytes, " + getMaximumIncomingMessageSize() + " allowed!)"); + + int encoding = in.readInt(); + if (encoding != MUSCLE_MESSAGE_DEFAULT_ENCODING) throw new IOException(); + Message pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + + /** Converts the given Message into bytes and sends it out the stream. + * @param out the data stream to send the converted bytes to. + * @param msg the Message to convert. + * @throws IOException if there is an error writing to the stream. + */ + public void flattenMessage(DataOutput out, Message msg) throws IOException + { + out.writeInt(msg.flattenedSize()); + out.writeInt(MUSCLE_MESSAGE_DEFAULT_ENCODING); + msg.flatten(out); + } + + /** Converts the given Message into bytes and sends it out the ByteChannel. + * This method will block if necessary, even if the ByteChannel is + * non-blocking. If you want to do 100% proper non-blocking I/O, + * you'll need to use the version of flattenMessage() that returns + * a ByteBuffer, and then handle the byte transfers yourself. + * @param out the ByteChannel to send the converted bytes to. + * @param msg the Message to convert. + * @throws IOException if there is an error writing to the stream. + */ + public void flattenMessage(ByteChannel out, Message msg) throws IOException + { + int flattenedSize = msg.flattenedSize(); + if ((_outgoing == null)||(_outgoing.capacity() < 8+flattenedSize)) + { + _outgoing = ByteBuffer.allocate(8+flattenedSize); + _outgoing.order(ByteOrder.LITTLE_ENDIAN); + } + _outgoing.rewind(); + _outgoing.limit(8+flattenedSize); + + _outgoing.putInt(flattenedSize); + _outgoing.putInt(MUSCLE_MESSAGE_DEFAULT_ENCODING); + msg.flatten(_outgoing); + _outgoing.rewind(); + + if (out instanceof SelectableChannel) + { + SelectableChannel sc = (SelectableChannel) out; + if (!sc.isBlocking()) + { + int numBytesWritten = 0; + + Selector selector = Selector.open(); + sc.register(selector, SelectionKey.OP_WRITE); + while(_outgoing.remaining() > 0) + { + if (numBytesWritten == 0) + { + selector.select(); + + // Slight shortcut here - there's only one key registered in this selector object. + if (selector.selectedKeys().isEmpty()) continue; + else selector.selectedKeys().clear(); + } + numBytesWritten = out.write(_outgoing); + } + selector.close(); + } + else out.write(_outgoing); + } + else out.write(_outgoing); + } + + public ByteBuffer flattenMessage(Message msg) throws IOException + { + ByteBuffer buffer; + int flattenedSize = msg.flattenedSize(); + buffer = ByteBuffer.allocate(flattenedSize+8); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.rewind(); + buffer.limit(8+flattenedSize); + + buffer.putInt(flattenedSize); + buffer.putInt(MUSCLE_MESSAGE_DEFAULT_ENCODING); + msg.flatten(buffer); + buffer.rewind(); + return buffer; + } + + protected final static int ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219; // 'zlic' + protected final static int ZLIB_CODEC_HEADER_DEPENDENT = 2053925218; // 'zlib' + private int _maximumIncomingMessageSize = Integer.MAX_VALUE; +} diff --git a/java/com/meyer/muscle/iogateway/MessageIOGatewayFactory.java b/java/com/meyer/muscle/iogateway/MessageIOGatewayFactory.java new file mode 100644 index 00000000..bf58eead --- /dev/null +++ b/java/com/meyer/muscle/iogateway/MessageIOGatewayFactory.java @@ -0,0 +1,44 @@ +package com.meyer.muscle.iogateway; + +public class MessageIOGatewayFactory +{ + // This class is a factory and shouldn't be created anywhere + private MessageIOGatewayFactory() + { + // empty + } + + public static AbstractMessageIOGateway getMessageIOGateway() + { + return MessageIOGatewayFactory.getMessageIOGateway(AbstractMessageIOGateway.MUSCLE_MESSAGE_DEFAULT_ENCODING); + } + + public static AbstractMessageIOGateway getMessageIOGateway(int encoding) + { + return getMessageIOGateway(encoding, Integer.MAX_VALUE); + } + + public static AbstractMessageIOGateway getMessageIOGateway(int encoding, int maxIncomingMessageSize) + { + MessageIOGateway ret = null; + + if (encoding == AbstractMessageIOGateway.MUSCLE_MESSAGE_DEFAULT_ENCODING) ret = new MessageIOGateway(); + else + { + // else, try to get the best zlib implementation currently available + try { + if (Class.forName("com.jcraft.jzlib.JZlib") != null) + { + // Found the JZlib library on the classpath, now return a reference to JZLibMessageIOGateway + Class gatewayClass = Class.forName("com.meyer.muscle.iogateway.JZLibMessageIOGateway"); + ret = (MessageIOGateway) gatewayClass.getConstructor(new Class[] {Integer.TYPE}).newInstance(new Object[] {new Integer(encoding)}); + } + } catch (Exception e) { + // If any problems occur, we will fall back to the native class! + } + if (ret == null) ret = new NativeZLibMessageIOGateway(encoding); + } + if (ret != null) ret.setMaximumIncomingMessageSize(maxIncomingMessageSize); + return ret; + } +} diff --git a/java/com/meyer/muscle/iogateway/NativeZLibMessageIOGateway.java b/java/com/meyer/muscle/iogateway/NativeZLibMessageIOGateway.java new file mode 100644 index 00000000..64616293 --- /dev/null +++ b/java/com/meyer/muscle/iogateway/NativeZLibMessageIOGateway.java @@ -0,0 +1,154 @@ +package com.meyer.muscle.iogateway; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ByteChannel; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; + +import com.meyer.muscle.message.Message; +import com.meyer.muscle.support.LEDataOutputStream; + +/** + * Support outgoing compression only, using the java.util.zip classes from the JDK. + * This doesn't work very well because of a few bugs in the JDK. + * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html + * http://developer.java.sun.com/developer/bugParade/bugs/4206909.html + * + */ +class NativeZLibMessageIOGateway extends MessageIOGateway +{ + protected Inflater _inflater; + protected Deflater _deflater; + protected ByteArrayOutputStream _baos; + protected DataOutput _dos; + + public NativeZLibMessageIOGateway() + { + super(); + } + + public NativeZLibMessageIOGateway(int encoding) + { + super(encoding); + } + + public void setOutgoingEncoding(int newEncoding) + { + super.setOutgoingEncoding(newEncoding); + _deflater = null; + } + + public void flattenMessage(ByteChannel out, Message msg) throws IOException + { + int oge = getOutgoingEncoding(); + + if (oge <= MUSCLE_MESSAGE_DEFAULT_ENCODING) + { + super.flattenMessage(out, msg); + return; + } + int flatSize = msg.flattenedSize(); + if (flatSize < 32) + { + super.flattenMessage(out, msg); + return; + } + + boolean independent = false; + if (_deflater == null) + { + _deflater = new Deflater(oge - MUSCLE_MESSAGE_DEFAULT_ENCODING, false); + _baos = new ByteArrayOutputStream(flatSize); + _dos = new DataOutputStream(new DeflaterStreamResetHack(_baos, _deflater, oge - MUSCLE_MESSAGE_DEFAULT_ENCODING)); + independent = true; + } + + ByteBuffer data = ByteBuffer.allocate(flatSize); + data.order(ByteOrder.LITTLE_ENDIAN); + + msg.flatten(data); + data.rewind(); + + _dos.write(data.array()); + ((OutputStream) _dos).flush(); + + data = ByteBuffer.allocate(16 + _baos.size()); + data.order(ByteOrder.LITTLE_ENDIAN); + + data.putInt(_baos.size() + 8); + data.putInt(oge); + data.putInt(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + data.putInt(flatSize); + data.put(_baos.toByteArray()); + out.write(data); + _baos.reset(); + } + + public void flattenMessage(DataOutput out, Message msg) throws IOException + { + int oge = getOutgoingEncoding(); + int flatSize = msg.flattenedSize(); + if ((oge <= MUSCLE_MESSAGE_DEFAULT_ENCODING) || (flatSize < 32)) + { + super.flattenMessage(out, msg); + return; + } + + boolean independent = false; + if (_deflater == null) + { + _deflater = new Deflater(oge - MUSCLE_MESSAGE_DEFAULT_ENCODING, false); + _baos = new ByteArrayOutputStream(flatSize); + _dos = new LEDataOutputStream(new DeflaterStreamResetHack(_baos, _deflater, oge - MUSCLE_MESSAGE_DEFAULT_ENCODING)); + independent = true; + } + + msg.flatten(_dos); + ((OutputStream)_dos).flush(); + out.writeInt(_baos.size() + 8); + out.writeInt(oge); + out.writeInt(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + out.writeInt(flatSize); + out.write(_baos.toByteArray()); + _baos.reset(); + } + + static class DeflaterStreamResetHack extends DeflaterOutputStream + { + private int _level; + + public DeflaterStreamResetHack(OutputStream out, Deflater def, int level) + { + super(out, def, 1024); + this._level = level; + } + + private static final byte [] EMPTYBYTEARRAY = new byte [0]; + + /** + * Insure all remaining data will be output. + */ + public void flush() throws IOException + { + /** + * Now this is tricky: We force the Deflater to flush + * its data by switching compression level. + * As yet, a perplexingly simple workaround for + * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html + */ + def.setInput(EMPTYBYTEARRAY, 0, 0); + def.setLevel(Deflater.NO_COMPRESSION); + deflate(); + def.setLevel(_level); + deflate(); + out.flush(); + } + } +} diff --git a/java/com/meyer/muscle/message/FieldNotFoundException.java b/java/com/meyer/muscle/message/FieldNotFoundException.java new file mode 100644 index 00000000..c53dd4bb --- /dev/null +++ b/java/com/meyer/muscle/message/FieldNotFoundException.java @@ -0,0 +1,21 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ + +package com.meyer.muscle.message; + +/** Exception that is thrown if you try to access a Message + * field that isn't present in the Message. + */ +public class FieldNotFoundException extends MessageException +{ + private static final long serialVersionUID = -1387490200161526582L; + + public FieldNotFoundException(String s) + { + super(s); + } + + public FieldNotFoundException() + { + super("Message entry not found"); + } +} diff --git a/java/com/meyer/muscle/message/FieldTypeMismatchException.java b/java/com/meyer/muscle/message/FieldTypeMismatchException.java new file mode 100644 index 00000000..12154e54 --- /dev/null +++ b/java/com/meyer/muscle/message/FieldTypeMismatchException.java @@ -0,0 +1,20 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.message; + +/** Exception that is thrown if you try to access a field in a Message + * by the wrong type (eg calling getInt() on a string field or somesuch) + */ +public class FieldTypeMismatchException extends MessageException +{ + private static final long serialVersionUID = -8562539748755552587L; + + public FieldTypeMismatchException(String s) + { + super(s); + } + + public FieldTypeMismatchException() + { + super("Message entry type mismatch"); + } +} diff --git a/java/com/meyer/muscle/message/Message.java b/java/com/meyer/muscle/message/Message.java new file mode 100644 index 00000000..8d6bbe49 --- /dev/null +++ b/java/com/meyer/muscle/message/Message.java @@ -0,0 +1,2177 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.meyer.muscle.support.Flattenable; +import com.meyer.muscle.support.LEDataInputStream; +import com.meyer.muscle.support.LEDataOutputStream; +import com.meyer.muscle.support.Point; +import com.meyer.muscle.support.Rect; +import com.meyer.muscle.support.UnflattenFormatException; + +/** + * This class is sort of similar to Be's BMessage class. When flattened, + * the resulting byte stream is compatible with the flattened + * buffers of MUSCLE's C++ Message class. + * It only acts as a serializable data container; it does not + * include any threading capabilities. + */ +public final class Message implements Flattenable +{ + /** Oldest serialization protocol version parsable by this code's unflatten() methods */ + public static final int OLDEST_SUPPORTED_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /** Newest serialization protocol version parsable by this code's unflatten() methods, + * as well as the version of the protocol produce by this code's flatten() methods. */ + public static final int CURRENT_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /** 32 bit 'what' code, for quick identification of message types. Set this however you like. */ + public int what = 0; + + /** Default Constructor. */ + public Message() + { + if (_empty == null) _empty = new HashMap(); // IE5 is lame + } + + /** Constructor. + * @param w The 'what' member variable will be set to the value you specify here. + */ + public Message(int w) + { + this(); + what = w; + } + + /** Copy Constructor. + * @param copyMe The Message to make us a (wholly independant) copy of. + */ + public Message(Message copyMe) + { + this(); + setEqualTo(copyMe); + } + + /** Returns an independent copy of this Message */ + public Flattenable cloneFlat() + { + Message clone = new Message(); + clone.setEqualTo(this); + return clone; + } + + /** Sets this Message equal to (c) + * @param c What to make ourself a clone of. Should be a Message + * @throws ClassCastException if (c) isn't a Message. + */ + public void setEqualTo(Flattenable c) throws ClassCastException + { + clear(); + Message copyMe = (Message)c; + what = copyMe.what; + Iterator fields = copyMe.fieldNames(); + while(fields.hasNext()) + { + try { + copyMe.copyField((String)fields.next(), this); + } + catch(FieldNotFoundException ex) { + ex.printStackTrace(); // should never happen + } + } + } + + /** Returns an Iterator of Strings that are the + * field names present in this Message + */ + public Iterator fieldNames() + { + return (_fieldTable != null) ? _fieldTable.keySet().iterator() : _empty.keySet().iterator(); + } + + /** Returns the given 'what' constant as a human readable 4-byte string, e.g. "BOOL", "BYTE", etc. + * @param w Any 32-bit value you would like to have turned into a String. + */ + public static String whatString(int w) + { + byte [] temp = new byte[4]; + for (int i=0; i<4; i++) + { + byte b = (byte)((w >> ((3-i)*8)) & 0xFF); + if ((b<' ')||(b>'~')) b = '?'; + temp[i] = b; + } + return new String(temp); + } + + /** Returns the number of field names of the given type that are present in the Message. + * @param type The type of field to count, or B_ANY_TYPE to count all field types. + * @return The number of matching fields, or zero if there are no fields of the appropriate type. + */ + public int countFields(int type) + { + if (_fieldTable == null) return 0; + if (type == B_ANY_TYPE) return _fieldTable.size(); + + int count = 0; + Iterator e = _fieldTable.values().iterator(); + while(e.hasNext()) + { + MessageField field = (MessageField) e.next(); + if (field.typeCode() == type) count++; + } + return count; + } + + /** Returns the total number of fields in this Message. */ + public int countFields() {return countFields(B_ANY_TYPE);} + + /** Returns true iff there are no fields in this Message. */ + public boolean isEmpty() {return (countFields() == 0);} + + /** Returns a string that is a summary of the contents of this Message. Good for debugging. */ + public String toString() + { + StringBuffer ret = new StringBuffer(); + ret.append("Message: what='").append(whatString(what)).append("' (").append(what).append("), countFields=") + .append(countFields()).append(", flattenedSize=").append(flattenedSize()).append('\n'); + Iterator iterator = fieldNames(); + while(iterator.hasNext()) + { + String fieldName = (String) iterator.next(); + ret.append(" ").append(fieldName).append(": ").append(_fieldTable.get(fieldName)).append('\n'); + } + return ret.toString(); + } + + /** Renames a field. + * @param old_entry Field name to rename from. + * @param new_entry Field name to rename to. If a field with this name already exists, it will be replaced. + * @throws FieldNotFoundException if a field named (old_entry) can't be found. + */ + public void renameField(String old_entry, String new_entry) throws FieldNotFoundException + { + MessageField field = getField(old_entry); + _fieldTable.remove(old_entry); + _fieldTable.put(new_entry, field); + } + + /** Returns false (Messages can be of various sizes, depending on how many fields they have, etc.) */ + public boolean isFixedSize() {return false;} + + /** Returns B_MESSAGE_TYPE */ + public int typeCode() {return B_MESSAGE_TYPE;} + + /** Returns The number of bytes it would take to flatten this Message into a byte buffer. */ + public int flattenedSize() + { + int sum = 4 + 4 + 4; // 4 bytes for the protocol revision #, 4 bytes for the number-of-entries field, 4 bytes for what code + if (_fieldTable != null) + { + Iterator e = fieldNames(); + while(e.hasNext()) + { + String fieldName = (String) e.next(); + MessageField field = (MessageField) _fieldTable.get(fieldName); + + // 4 bytes for the name length, name data, 4 bytes for entry type code, 4 bytes for entry data length, entry data + sum += 4 + (fieldName.length()+1) + 4 + 4 + field.flattenedSize(); + } + } + return sum; + } + + /** Returns true iff (code) is B_MESSAGE_TYPE */ + public boolean allowsTypeCode(int code) {return (code == B_MESSAGE_TYPE);} + + /** + * Converts this Message into a flattened stream of bytes that can be saved to disk + * or sent over a network, and later converted back into an identical Message object. + * @param out The stream to output bytes to. (Should generally be an LEOutputDataStream object) + * @throws IOException if there is a problem outputting the bytes. + */ + public void flatten(DataOutput out) throws IOException + { + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + out.writeInt(CURRENT_PROTOCOL_VERSION); + out.writeInt(what); + out.writeInt(countFields()); + Iterator e = fieldNames(); + while(e.hasNext()) + { + String name = (String) e.next(); + MessageField field = (MessageField) _fieldTable.get(name); + out.writeInt(name.length()+1); + out.writeBytes(name); + out.writeByte(0); // terminating NUL byte + out.writeInt(field.typeCode()); + out.writeInt(field.flattenedSize()); + field.flatten(out); + } + } + + public void flatten(ByteBuffer out) throws IOException + { + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + out.putInt(CURRENT_PROTOCOL_VERSION); + out.putInt(what); + out.putInt(countFields()); + Iterator e = fieldNames(); + while(e.hasNext()) + { + String name = (String) e.next(); + MessageField field = (MessageField) _fieldTable.get(name); + byte[] nameBytes = name.getBytes(); + out.putInt(nameBytes.length+1); + out.put(nameBytes); + out.put((byte)0); // terminating NUL byte + out.putInt(field.typeCode()); + + int fieldValuePosition = out.position(); + out.position(fieldValuePosition+4); + field.flatten(out); + out.putInt(fieldValuePosition, out.position()-fieldValuePosition-4); + } + } + + public void unflatten(ByteBuffer in, int numBytes) throws UnflattenFormatException, IOException + { + // Now do the actual work on the byte buffer. + clear(); + int protocolVersion = in.getInt(); + if ((protocolVersion > CURRENT_PROTOCOL_VERSION)||(protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) throw new UnflattenFormatException("Version mismatch error"); + what = in.getInt(); + int numEntries = in.getInt(); + byte [] stringBuf = null; + if (numEntries > 0) ensureFieldTableAllocated(); + for (int i=0; i5)?fieldNameLength:5)*2]; + in.get(stringBuf, 0, fieldNameLength); + String fieldName = new String(stringBuf, 0, fieldNameLength-1); + MessageField field = getCreateOrReplaceField(fieldName, in.getInt()); + field.unflatten(in, in.getInt()); + _fieldTable.put(fieldName, field); + } + } + + /** + * Convert bytes from the given stream back into a Message. Any previous contents of + * this Message will be erased, and replaced with the data specified in the byte buffer. + * @param in The stream to get bytes from. (Should generally be an LEInputDataStream object) + * @param numBytes The number of bytes this object takes up in the stream, or negative if the number is not known. + * @throws UnflattenFormatException if the data in (buf) wasn't well-formed. + * @throws IOException if there was an error reading from the input stream. + */ + public void unflatten(DataInput in, int numBytes) throws UnflattenFormatException, IOException + { + clear(); + int protocolVersion = in.readInt(); + if ((protocolVersion > CURRENT_PROTOCOL_VERSION)||(protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) throw new UnflattenFormatException("Version mismatch error"); + what = in.readInt(); + int numEntries = in.readInt(); + byte [] stringBuf = null; + if (numEntries > 0) ensureFieldTableAllocated(); + for (int i=0; i5)?fieldNameLength:5)*2]; + in.readFully(stringBuf, 0, fieldNameLength); + MessageField field = getCreateOrReplaceField(new String(stringBuf, 0, fieldNameLength-1), in.readInt()); + field.unflatten(in, in.readInt()); + _fieldTable.put(new String(stringBuf, 0, fieldNameLength-1), field); + } + } + + /** Sets the given field name to contain a single boolean value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setBoolean(String name, boolean val) + { + MessageField field = getCreateOrReplaceField(name, B_BOOL_TYPE); + boolean [] array = (boolean[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new boolean[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given boolean values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of boolean values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setBooleans(String name, boolean [] vals) {setObjects(name, B_BOOL_TYPE, vals, vals.length);} + + /** Returns the first boolean value in the given field. + * @param name Name of the field to look for a boolean value in. + * @return The first boolean value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_BOOL_TYPE field. + */ + public boolean getBoolean(String name) throws MessageException {return getBooleans(name)[0];} + + /** Returns the first boolean value in the given field, or the default specified. + * @param name Name of the field to look for a boolean value in + * @param def Default value if the field dosen't exist or is the wrong type. + */ + public boolean getBoolean(String name, boolean def) { + try { + return getBoolean(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of boolean values. + * @param name Name of the field to look for boolean values in. + * @return The array of boolean values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_BOOL_TYPE field. + */ + public boolean[] getBooleans(String name) throws MessageException {return (boolean[]) getData(name, B_BOOL_TYPE);} + + /** Sets the given field name to contain a single byte value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setByte(String name, byte val) + { + MessageField field = getCreateOrReplaceField(name, B_INT8_TYPE); + byte [] array = (byte[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new byte[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given byte values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of byte values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setBytes(String name, byte [] vals) {setObjects(name, B_INT8_TYPE, vals, vals.length);} + + /** Returns the first byte value in the given field. + * @param name Name of the field to look for a byte value in. + * @return The first byte value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT8_TYPE field. + */ + public byte getByte(String name) throws MessageException {return getBytes(name)[0];} + + /** Returns the first byte value in the given field. + * @param name Name of the field to look for a byte value in. + * @param def Default value to return if the field dosen't exist or is the wrong type. + * @return The first byte value in the field. + */ + public byte getByte(String name, byte def) { + try { + return getByte(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of byte values. + * @param name Name of the field to look for byte values in. + * @return The array of byte values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT8_TYPE field. + */ + public byte[] getBytes(String name) throws MessageException {return (byte[]) getData(name, B_INT8_TYPE);} + + /** Sets the given field name to contain a single short value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setShort(String name, short val) + { + MessageField field = getCreateOrReplaceField(name, B_INT16_TYPE); + short [] array = (short[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new short[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given short values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of short values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setShorts(String name, short [] vals) {setObjects(name, B_INT16_TYPE, vals, vals.length);} + + /** Returns the first short value in the given field. + * @param name Name of the field to look for a short value in. + * @return The first short value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT16_TYPE field. + */ + public short getShort(String name) throws MessageException {return getShorts(name)[0];} + + /** Returns the first short value in the given field. + * @param name Name of the field to look for a short value in. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return The first short value in the field. + */ + public short getShort(String name, short def) { + try { + return getShort(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of short values. + * @param name Name of the field to look for short values in. + * @return The array of short values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT16_TYPE field. + */ + public short[] getShorts(String name) throws MessageException {return (short[]) getData(name, B_INT16_TYPE);} + + /** Sets the given field name to contain a single int value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setInt(String name, int val) + { + MessageField field = getCreateOrReplaceField(name, B_INT32_TYPE); + int [] array = (int[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new int[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given int values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of int values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setInts(String name, int [] vals) {setObjects(name, B_INT32_TYPE, vals, vals.length);} + + /** Returns the first int value in the given field. + * @param name Name of the field to look for a int value in. + * @return The first int value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT32_TYPE field. + */ + public int getInt(String name) throws MessageException {return getInts(name)[0];} + + /** Returns the first int value in the given field. + * @param name Name of the field to look for a int value in. + * @param def The value to return if the field dosen't exist, or if the field is the incorrect type. + * @return The first int value in the field. + */ + public int getInt(String name, int def) { + try { + return getInt(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of int values. + * @param name Name of the field to look for int values in. + * @return The array of int values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT32_TYPE field. + */ + public int[] getInts(String name) throws MessageException {return (int[]) getData(name, B_INT32_TYPE);} + + /** Returns the contents of the given field as an array of int values. + * @param name Name of the field to look for int values in. + * @param defs The Default array to return in the event that one does not exist, or an error occurs. + * @return The array of int values associated with (name). Note that this array is still part of this Message. + */ + public int[] getInts(String name, int[] defs) { + try { + return getInts(name); + } catch (MessageException me) { + return defs; + } + } + + /** Sets the given field name to contain a single long value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setLong(String name, long val) + { + MessageField field = getCreateOrReplaceField(name, B_INT64_TYPE); + long [] array = (long[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new long[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given long values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of long values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setLongs(String name, long [] vals) {setObjects(name, B_INT64_TYPE, vals, vals.length);} + + /** Returns the first long value in the given field. + * @param name Name of the field to look for a long value in. + * @return The first long value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT64_TYPE field. + */ + public long getLong(String name) throws MessageException {return getLongs(name)[0];} + + /** Returns the first long value in the given field. + * @param name Name of the field to look for a long value in. + * @param def The Default value to return if the field dosen't exist, or if it's the wrong type. + * @return The first long value in the field. + */ + public long getLong(String name, long def) { + try { + return getLong(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of long values. + * @param name Name of the field to look for long values in. + * @return The array of long values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT64_TYPE field. + */ + public long[] getLongs(String name) throws MessageException {return (long[]) getData(name, B_INT64_TYPE);} + + /** Sets the given field name to contain a single float value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setFloat(String name, float val) + { + MessageField field = getCreateOrReplaceField(name, B_FLOAT_TYPE); + float [] array = (float[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new float[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given float values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of float values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setFloats(String name, float [] vals) {setObjects(name, B_FLOAT_TYPE, vals, vals.length);} + + /** Returns the first float value in the given field. + * @param name Name of the field to look for a float value in. + * @return The first float value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_FLOAT_TYPE field. + */ + public float getFloat(String name) throws MessageException {return getFloats(name)[0];} + + /** Returns the first float value in the given field. + * @param name Name of the field to look for a float value in. + * @param def the Default value to return if the field dosen't exist, or is the wrong type. + * @return The first float value in the field. + */ + public float getFloat(String name, float def) { + try { + return getFloat(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of float values. + * @param name Name of the field to look for float values in. + * @return The array of float values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_FLOAT_TYPE field. + */ + public float[] getFloats(String name) throws MessageException {return (float[]) getData(name, B_FLOAT_TYPE);} + + /** Sets the given field name to contain a single double value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setDouble(String name, double val) + { + MessageField field = getCreateOrReplaceField(name, B_DOUBLE_TYPE); + double [] array = (double[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new double[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given double values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of double values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setDoubles(String name, double [] vals) {setObjects(name, B_DOUBLE_TYPE, vals, vals.length);} + + /** Returns the first double value in the given field. + * @param name Name of the field to look for a double value in. + * @return The first double value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_DOUBLE_TYPE field. + */ + public double getDouble(String name) throws MessageException {return getDoubles(name)[0];} + + /** Returns the first double value in the given field. + * @param name Name of the field to look for a double value in. + * @param def The Default value to return if the field dosen't exist. + * @return The first double value in the field. + */ + public double getDouble(String name, double def) { + try { + return getDouble(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of double values. + * @param name Name of the field to look for double values in. + * @return The array of double values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_DOUBLE_TYPE field. + */ + public double[] getDoubles(String name) throws MessageException {return (double[]) getData(name, B_DOUBLE_TYPE);} + + /** Sets the given field name to contain a single String value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setString(String name, String val) + { + MessageField field = getCreateOrReplaceField(name, B_STRING_TYPE); + String [] array = (String[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new String[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given String values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of String values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setStrings(String name, String [] vals) {setObjects(name, B_STRING_TYPE, vals, vals.length);} + + /** + * @param name The name of the field to access. + * @return the value(s) associated with the requested field name + * @throws MessageFieldNotFoundException If the given field name isn't present in the message + * @throws MessageFieldTypeMismatchException If the given field exists, but is the wrong type of data. + */ + public String getString(String name) throws MessageException {return getStrings(name)[0];} + + /** + * @param name The name of the field to access. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return the value(s) associated with the requested field name + */ + public String getString(String name, String def) { + try { + return getString(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of String values. + * @param name Name of the field to look for String values in. + * @return The array of String values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_STRING_TYPE field. + */ + public String[] getStrings(String name) throws MessageException {return (String[]) getData(name, B_STRING_TYPE);} + + /** Returns the contents of the given field as an array of String values. + * @param name Name of the field to look for String values in. + * @param def the Default values to return. + * @return The array of String values associated with (name). Note that this array is still part of this Message. + */ + public String[] getStrings(String name, String[] def) { + try { + return (String[]) getData(name, B_STRING_TYPE); + } catch (MessageException me) { + return def; + } + } + + /** Sets the given field name to contain a single Message value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in mesage object becomes part of this Message. + */ + public void setMessage(String name, Message val) + { + MessageField field = getCreateOrReplaceField(name, B_MESSAGE_TYPE); + Message [] array = (Message[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Message[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Message values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of Message objects to assign to the field. Note that the neither the array nor the + * Message objects are copied; rather both the array and the Messages become part of this Message. + */ + public void setMessages(String name, Message [] vals) {setObjects(name, B_MESSAGE_TYPE, vals, vals.length);} + + /** Returns the first Message value in the given field. + * @param name Name of the field to look for a Message value in. + * @return The first Message value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_MESSAGE_TYPE field. + */ + public Message getMessage(String name) throws MessageException {return getMessages(name)[0];} + + /** Returns the contents of the given field as an array of Message values. + * @param name Name of the field to look for Message values in. + * @return The array of Message values associated with (name). Note that the array and the Messages + * it holds are still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_MESSAGE_TYPE field. + */ + public Message[] getMessages(String name) throws MessageException {return (Message[]) getData(name, B_MESSAGE_TYPE);} + + /** Sets the given field name to contain a single Point value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in object becomes part of this Message. + */ + public void setPoint(String name, Point val) + { + MessageField field = getCreateOrReplaceField(name, B_POINT_TYPE); + Point [] array = (Point[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Point[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Point values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of Point objects to assign to the field. Note that neither the array nor the + * Point objects are copied; rather both the array and the Points become part of this Message. + */ + public void setPoints(String name, Point [] vals) {setObjects(name, B_POINT_TYPE, vals, vals.length);} + + /** Returns the first Point value in the given field. + * @param name Name of the field to look for a Point value in. + * @return The first Point value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_POINT_TYPE field. + */ + public Point getPoint(String name) throws MessageException {return getPoints(name)[0];} + + /** Returns the first Point value in the given field. + * @param name Name of the field to look for a Point value in. + * @param def The Default value to return if the field dosen't exist or is the wrong type. + * @return The first Point value in the field. + */ + public Point getPoint(String name, Point def) { + try { + return getPoint(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of Point values. + * @param name Name of the field to look for Point values in. + * @return The array of Point values associated with (name). Note that the array and the + * objects it holds are still part of the Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_POINT_TYPE field. + */ + public Point[] getPoints(String name) throws MessageException {return (Point[]) getData(name, B_POINT_TYPE);} + + /** Sets the given field name to contain a single Rect value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in object becomes part of this Message. + */ + public void setRect(String name, Rect val) + { + MessageField field = getCreateOrReplaceField(name, B_RECT_TYPE); + Rect [] array = (Rect[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Rect[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Rect values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of Rect objects to assign to the field. Note that neither the array nor the + * Rect objects are copied; rather both the array and the Rects become part of this Message. + */ + public void setRects(String name, Rect [] vals) {setObjects(name, B_RECT_TYPE, vals, vals.length);} + + /** Returns the first Rect value in the given field. Note that the returned object is still part of this Message. + * @param name Name of the field to look for a Rect value in. + * @return The first Rect value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_RECT_TYPE field. + */ + public Rect getRect(String name) throws MessageException {return getRects(name)[0];} + + /** Returns the first Rect value in the given field. Note that the returned object is still part of this Message. + * @param name Name of the field to look for a Rect value in. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return The first Rect value in the field. + */ + public Rect getRect(String name, Rect def) { + try { + return getRect(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of Rect values. + * @param name Name of the field to look for Rect values in. + * @return The array of Rect values associated with (name). Note that the array and the Rects that + * it holds are still part of the Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_RECT_TYPE field. + */ + public Rect[] getRects(String name) throws MessageException {return (Rect[]) getData(name, B_RECT_TYPE);} + + /** Sets the given field name to contain the flattened bytes of the single given Flattenable object. + * Any previous field contents are replaced. The type code of the field is determined by calling val.typeCode(). + * @param name Name of the field to set + * @param val The object whose bytes are to be flattened out and put into this field. + * (val) will be flattened and the resulting bytes kept. (val) does not become part of the Message object. + */ + public void setFlat(String name, Flattenable val) + { + int type = val.typeCode(); + MessageField field = getCreateOrReplaceField(name, type); + Object payload = field.getData(); + switch(type) + { + // For these types, we have explicit support for holding the objects in memory, so we'll just clone them + case B_MESSAGE_TYPE: + { + Message array[] = ((payload != null)&&(((Message[])payload).length == 1)) ? ((Message[])payload) : new Message[1]; + array[1] = (Message) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_POINT_TYPE: + { + Point array[] = ((payload != null)&&(((Point[])payload).length == 1)) ? ((Point[])payload) : new Point[1]; + array[1] = (Point) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_RECT_TYPE: + { + Rect array[] = ((payload != null)&&(((Rect[])payload).length == 1)) ? ((Rect[])payload) : new Rect[1]; + array[1] = (Rect) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + // For everything else, we have to store the objects as byte buffers + default: + { + byte array[][] = ((payload != null)&&(((byte[][])payload).length == 1)) ? ((byte[][])payload) : new byte[1][]; + array[0] = flattenToArray(val, array[0]); + field.setPayload(array, 1); + } + break; + } + } + + /** Sets the given field name to contain the given Flattenable values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param vals Array of Flattenable objects to assign to the field. The objects are all flattened and + * the flattened data is put into the Message; the objects themselves do not become part of the message. + * Note that if the objects are Messages, Points, or Rects, they will be cloned rather than flattened. + */ + public void setFlats(String name, Flattenable [] vals) + { + int type = vals[0].typeCode(); + int len = vals.length; + MessageField field = getCreateOrReplaceField(name, type); + switch(type) + { + // For these types, we have explicit support for holding the objects in memory, so we'll just clone them + case B_MESSAGE_TYPE: + { + Message array[] = new Message[len]; + for (int i=0; i 0) _numItems = numBytes / flattenedItemSize; + + switch(_type) + { + case B_BOOL_TYPE: + { + boolean [] array = new boolean[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = (in.get() > 0); + _payload = array; + } + break; + + case B_INT8_TYPE: + { + byte [] array = new byte[_numItems]; + in.get(array, 0, _numItems); // wow, easy + _payload = array; + } + break; + + case B_INT16_TYPE: + { + short [] array = new short[_numItems]; + in.asShortBuffer().get(array); + in.position(in.position()+array.length*flattenedItemSize); + _payload = array; + } + break; + + case B_FLOAT_TYPE: + { + float [] array = new float[_numItems]; + in.asFloatBuffer().get(array); + in.position(in.position()+array.length*flattenedItemSize); + _payload = array; + } + break; + + case B_INT32_TYPE: + { + int [] array = new int[_numItems]; + in.asIntBuffer().get(array); + in.position(in.position()+array.length*flattenedItemSize); + _payload = array; + } + break; + + case B_INT64_TYPE: + { + long [] array = new long[_numItems]; + in.asLongBuffer().get(array); + in.position(in.position()+array.length*flattenedItemSize); + _payload = array; + } + break; + + case B_DOUBLE_TYPE: + { + double [] array = new double[_numItems]; + in.asDoubleBuffer().get(array); + in.position(in.position()+array.length*flattenedItemSize); + _payload = array; + } + break; + + case B_POINT_TYPE: + { + Point [] array = new Point[_numItems]; + for (int i=0; i<_numItems; i++) + { + Point p = array[i] = new Point(); + p.unflatten(in, p.flattenedSize()); + } + _payload = array; + } + break; + + case B_RECT_TYPE: + { + Rect [] array = new Rect[_numItems]; + for (int i=0; i<_numItems; i++) + { + Rect r = array[i] = new Rect(); + r.unflatten(in, r.flattenedSize()); + } + _payload = array; + } + break; + + case B_MESSAGE_TYPE: + { + List temp = new ArrayList(); + while(numBytes > 0) + { + Message subMessage = new Message(); + int subMessageSize = in.getInt(); + subMessage.unflatten(in, subMessageSize); + temp.add(subMessage); + numBytes -= (subMessageSize + 4); // 4 for the size int + } + _numItems = temp.size(); + Message[] array = (Message[]) temp.toArray(new Message[_numItems]); + _payload = array; + } + break; + + case B_STRING_TYPE: + { + _numItems = in.getInt(); + String array[] = new String[_numItems]; + byte [] temp = null; // lazy-allocated + for (int i=0; i<_numItems; i++) + { + int nextStringLen = in.getInt(); + if ((temp == null)||(temp.length < nextStringLen)) temp = new byte[nextStringLen]; + in.get(temp, 0, nextStringLen); + try { + array[i] = new String(temp, 0, nextStringLen-1, "UTF8"); // don't include the NUL byte + } catch (UnsupportedEncodingException uee) { + array[i] = new String(temp, 0, nextStringLen-1); // don't include the NUL byte + } + } + _payload = array; + } + break; + + default: + { + _numItems = in.getInt(); + byte [][] array = new byte[_numItems][]; + for (int i=0; i<_numItems; i++) + { + array[i] = new byte[in.getInt()]; + in.get(array[i]); + } + _payload = array; + } + break; + } + } + + /** Restores our state from the given stream. + * Assumes that our _type is already set correctly. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException + { + // For fixed-size types, calculating the number of items to read is easy... + int flattenedItemSize = flattenedItemSize(); + if (flattenedItemSize > 0) _numItems = numBytes / flattenedItemSize; + + switch(_type) + { + case B_BOOL_TYPE: + { + boolean [] array = new boolean[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = (in.readByte() > 0) ? true : false; + _payload = array; + } + break; + + case B_INT8_TYPE: + { + byte [] array = new byte[_numItems]; + in.readFully(array); // wow, easy + _payload = array; + } + break; + + case B_INT16_TYPE: + { + short [] array = new short[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readShort(); + _payload = array; + } + break; + + case B_FLOAT_TYPE: + { + float [] array = new float[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readFloat(); + _payload = array; + } + break; + + case B_INT32_TYPE: + { + int [] array = new int[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readInt(); + _payload = array; + } + break; + + case B_INT64_TYPE: + { + long [] array = new long[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readLong(); + _payload = array; + } + break; + + case B_DOUBLE_TYPE: + { + double [] array = new double[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readDouble(); + _payload = array; + } + break; + + case B_POINT_TYPE: + { + Point [] array = new Point[_numItems]; + for (int i=0; i<_numItems; i++) + { + Point p = array[i] = new Point(); + p.unflatten(in, p.flattenedSize()); + } + _payload = array; + } + break; + + case B_RECT_TYPE: + { + Rect [] array = new Rect[_numItems]; + for (int i=0; i<_numItems; i++) + { + Rect r = array[i] = new Rect(); + r.unflatten(in, r.flattenedSize()); + } + _payload = array; + } + break; + + case B_MESSAGE_TYPE: + { + List temp = new ArrayList(); + while(numBytes > 0) + { + Message subMessage = new Message(); + int subMessageSize = in.readInt(); + subMessage.unflatten(in, subMessageSize); + temp.add(subMessage); + numBytes -= (subMessageSize + 4); // 4 for the size int + } + _numItems = temp.size(); + Message array[] = new Message[_numItems]; + for (int j=0; j<_numItems; j++) array[j] = (Message) temp.get(j); + _payload = array; + } + break; + + case B_STRING_TYPE: + { + _numItems = in.readInt(); + String array[] = new String[_numItems]; + byte [] temp = null; // lazy-allocated + for (int i=0; i<_numItems; i++) + { + int nextStringLen = in.readInt(); + if ((temp == null)||(temp.length < nextStringLen)) temp = new byte[nextStringLen]; + in.readFully(temp, 0, nextStringLen); // includes nul terminator + try { + array[i] = new String(temp, 0, nextStringLen-1, "UTF8"); // don't include the NUL byte + } catch (UnsupportedEncodingException uee) { + array[i] = new String(temp, 0, nextStringLen-1); // don't include the NUL byte + } + } + _payload = array; + } + break; + + default: + { + _numItems = in.readInt(); + byte [][] array = new byte[_numItems][]; + for (int i=0; i<_numItems; i++) + { + array[i] = new byte[in.readInt()]; + in.readFully(array[i]); + } + _payload = array; + } + break; + } + } + + /** Prints some debug info about our state to (out) */ + public String toString() + { + StringBuffer ret = new StringBuffer(" Type='"); + ret.append(whatString(_type)).append("', ").append(_numItems).append(" items: "); + int pitems = (_numItems < 10) ? _numItems : 10; + switch(_type) + { + case B_BOOL_TYPE: + { + boolean [] array = (boolean[]) _payload; + for (int i=0; i 0) + { + int numToCopy = (len < numLeft) ? len : numLeft; + System.arraycopy(_buf, _bytesRead, b, offset, numToCopy); + _bytesRead += numToCopy; + return numToCopy; + } + else return -1; + } + public synchronized void reset() throws IOException {_bytesRead = _mark;} + public long skip(long n) throws IOException + { + if (_buf != null) + { + int numLeft = _buf.length - _bytesRead; + if (numLeft >= n) + { + _bytesRead += n; + return n; + } + else + { + _bytesRead += numLeft; + return numLeft; + } + } + else return 0; + } + + public void setInputBuffer(byte [] buf) + { + _buf = buf; + _bytesRead = 0; + _mark = 0; + } + + private byte [] _buf = null; + private int _mark = 0; + private int _bytesRead = 0; + } + + /** Used for more efficient flattening of Flattenables to byte arrays */ + private class PreAllocatedByteArrayOutputStream extends OutputStream + { + public void close() throws IOException {_buf = null;} + public void write(byte[] b, int off, int len) throws IOException + { + if (_buf != null) + { + int numLeft = _buf.length - _bytesWritten; + if (numLeft >= len) + { + System.arraycopy(b, off, _buf, _bytesWritten, len); + _bytesWritten += len; + return; + } + } + throw new IOException("no space left in buffer "); + } + + public void write(int b) throws IOException + { + if ((_buf != null)&&(_bytesWritten < _buf.length)) _buf[_bytesWritten++] = (byte) b; + else throw new IOException("no space left in buffer"); + } + + public void setOutputBuffer(byte [] buf) + { + _buf = buf; + _bytesWritten = 0; + } + + private byte _buf[] = null; + private int _bytesWritten = 0; + } + + // These are all allocated the first time they are needed, and then kept in case they are needed again + private PreAllocatedByteArrayInputStream _pais = null; + private PreAllocatedByteArrayOutputStream _paos = null; + private LEDataInputStream _lepais = null; + private LEDataOutputStream _lepaos = null; + + private Map _fieldTable = null; // our name -> MessageField table + private static Map _empty; // = new Hashtable(0); // had to change this to be done on-demand in the ctor, as IE was throwing fits :^( +} + diff --git a/java/com/meyer/muscle/message/MessageException.java b/java/com/meyer/muscle/message/MessageException.java new file mode 100644 index 00000000..5fcb9446 --- /dev/null +++ b/java/com/meyer/muscle/message/MessageException.java @@ -0,0 +1,18 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.message; + +/** Base class for the various Exceptions that the Message class may throw */ +public class MessageException extends Exception +{ + private static final long serialVersionUID = -7107595031653595454L; + + public MessageException(String s) + { + super(s); + } + + public MessageException() + { + super("Error accessing a Message field"); + } +} diff --git a/java/com/meyer/muscle/queue/Queue.java b/java/com/meyer/muscle/queue/Queue.java new file mode 100644 index 00000000..8504dae5 --- /dev/null +++ b/java/com/meyer/muscle/queue/Queue.java @@ -0,0 +1,242 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.queue; + +/* This file is Copyright 2000 Level Control Systems. See the included LICENSE.txt file for details. */ + +/** This class implements a templated double-ended queue data structure. + * Adding or removing items from the head or tail of a Queue is (on average) + * an O(1) operation. + */ +public final class Queue +{ + /** Constructor. + */ + public Queue() + { + // empty + } + + /** Copy constructor. */ + public Queue(Queue copyMe) + { + setEqualTo(copyMe); + } + + /** Make (this) a shallow copy of the passed-in queue. + * @param setTo What to set this queue equal to. + */ + public void setEqualTo(Queue setTo) + { + removeAllElements(); + int len = setTo.size(); + ensureSize(len); + for (int i=0; i 0) + { + ret = _queue[_headIndex]; + _queue[_headIndex] = null; // allow garbage collection + _headIndex = nextIndex(_headIndex); + _elementCount--; + } + return ret; + } + + /** Removes and returns the element at the tail of the Queue. + * @return The removed Object, or null if the Queue was empty. + */ + public Object removeLastElement() + { + Object ret = null; + if (_elementCount > 0) + { + ret = _queue[_tailIndex]; + _queue[_tailIndex] = null; // allow garbage collection + _tailIndex = prevIndex(_tailIndex); + _elementCount--; + } + return ret; + } + + + /** removes the element at the (index)'th position in the queue. + * @param idx Which element to remove--can range from zero + * (head of the queue) to CountElements()-1 (tail of the queue). + * @return The removed Object, or null if the Queue was empty. + * Note that this method is somewhat inefficient for indices that + * aren't at the head or tail of the queue (i.e. O(n) time) + * @throws IndexOutOfBoundsException if (idx) isn't a valid index. + */ + public Object removeElementAt(int idx) + { + if (idx == 0) return removeFirstElement(); + + int index = internalizeIndex(idx); + Object ret = _queue[index]; + while(index != _tailIndex) + { + int next = nextIndex(index); + _queue[index] = _queue[next]; + index = next; + } + _queue[_tailIndex] = null; // allow garbage collection + _tailIndex = prevIndex(_tailIndex); + _elementCount--; + return ret; + } + + /** Copies the (index)'th element into (returnElement). + * @param index Which element to get--can range from zero + * (head of the queue) to (CountElements()-1) (tail of the queue). + * @return The Object at the given index. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public Object elementAt(int index) {return _queue[internalizeIndex(index)];} + + /** Replaces the (index)'th element in the queue with (newElement). + * @param index Which element to replace--can range from zero + * (head of the queue) to (CountElements()-1) (tail of the queue). + * @param newElement The element to place into the queue at the (index)'th position. + * @return The Object that was previously in the (index)'th position. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public Object setElementAt(int index, Object newElement) + { + int iidx = internalizeIndex(index); + Object ret = _queue[iidx]; + _queue[iidx] = newElement; + return ret; + } + + /** Inserts (element) into the (nth) slot in the array. InsertElementAt(0) + * is the same as addHead(element), InsertElementAt(CountElements()) is the same + * as addTail(element). Other positions will involve an O(n) shifting of contents. + * @param index The position at which to insert the new element. + * @param newElement The element to insert into the queue. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public void insertElementAt(int index, Object newElement) + { + if (index == 0) prependElement(newElement); + else + { + // Harder case: inserting into the middle of the array + appendElement(null); // just to allocate the extra slot + int lo = (int) index; + for (int i=_elementCount-1; i>lo; i--) _queue[internalizeIndex(i)] = _queue[internalizeIndex(i-1)]; + _queue[internalizeIndex(index)] = newElement; + } + } + + /** Removes all elements from the queue. */ + public void removeAllElements() + { + if (_elementCount > 0) + { + // clear valid array elements to allow immediate garbage collection + if (_tailIndex >= _headIndex) java.util.Arrays.fill(_queue, _headIndex, _tailIndex+1, null); + else + { + java.util.Arrays.fill(_queue, 0, _headIndex+1, null); + java.util.Arrays.fill(_queue, _tailIndex, _queue.length, null); + } + + _elementCount = 0; + _headIndex = -1; + _tailIndex = -1; + } + } + + /** Returns the number of elements in the queue. (This number does not include pre-allocated space) */ + public int size() {return _elementCount;} + + /** Returns true iff their are no elements in the queue. */ + public boolean isEmpty() {return (_elementCount == 0);} + + /** Returns the head element in the queue. You must not call this when the queue is empty! */ + public Object firstElement() {return elementAt(0);} + + /** Returns the tail element in the queue. You must not call this when the queue is empty! */ + public Object lastElement() {return elementAt(_elementCount-1);} + + /** Makes sure there is enough space preallocated to hold at least + * (numElements) elements. You only need to call this if + * you wish to minimize the number of array reallocations done. + * @param numElements the minimum amount of elements to pre-allocate space for in the Queue. + */ + public void ensureSize(int numElements) + { + if ((_queue == null)||(_queue.length < numElements)) + { + Object newQueue[] = new Object[(numElements < 5) ? 5 : numElements*2]; + if (_elementCount > 0) + { + for (int i=0; i<_elementCount; i++) newQueue[i] = elementAt(i); + _headIndex = 0; + _tailIndex = _elementCount-1; + } + _queue = newQueue; + } + } + + /** Returns the last index of the given (element), or -1 if (element) is + * not found in the list. O(n) search time. + * @param element The element to look for. + * @return The index of (element), or -1 if no such element is present. + */ + public int indexOf(Object element) + { + if (_queue != null) for (int i=size()-1; i>=0; i--) if (elementAt(i) == element) return i; + return -1; + } + + private int nextIndex(int idx) {return (idx >= getArraySize()-1) ? 0 : idx+1;} + private int prevIndex(int idx) {return (idx <= 0) ? getArraySize()-1 : idx-1;} + + // Translates a user-index into an index into the _queue array. + private int internalizeIndex(int idx) + { + if ((idx < 0)||(idx >= _elementCount)) throw new IndexOutOfBoundsException("bad index " + idx + " (queuelen=" + _elementCount + ")"); + return (_headIndex + idx) % getArraySize(); + } + + private int getArraySize() {return (_queue != null) ? _queue.length : 0;} + + private Object _queue[] = null; // demand-allocated object array + private int _elementCount = 0; // number of valid elements in the array + private int _headIndex = -1; // index of the first filled slot, or -1 + private int _tailIndex = -1; // index of the last filled slot, or -1 +} + + diff --git a/java/com/meyer/muscle/support/Flattenable.java b/java/com/meyer/muscle/support/Flattenable.java new file mode 100644 index 00000000..23736974 --- /dev/null +++ b/java/com/meyer/muscle/support/Flattenable.java @@ -0,0 +1,51 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.support; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** Interface for objects that can be flattened and unflattened from Be-style byte streams */ +public interface Flattenable extends TypeConstants +{ + /** Should return true iff every object of this type has a flattened size that is known at compile time. */ + public boolean isFixedSize(); + + /** Should return the type code identifying this type of object. */ + public int typeCode(); + + /** Should return the number of bytes needed to store this object in its current state. */ + public int flattenedSize(); + + /** Should return a clone of this object */ + public Flattenable cloneFlat(); + + /** Should set this object's state equal to that of (setFromMe), or throw an UnflattenFormatException if it can't be done. + * @param setFromMe The object we want to be like. + * @throws ClassCastException if (setFromMe) is the wrong type. + */ + public void setEqualTo(Flattenable setFromMe) throws ClassCastException; + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException; + + /** + * Should return true iff a buffer with type_code (code) can be used to reconstruct + * this object's state. + * @param code A type code ant, e.g. B_RAW_TYPE or B_STRING_TYPE, or something custom. + * @return True iff this object can Unflatten from a buffer of the given type, false otherwise. + */ + public boolean allowsTypeCode(int code); + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @param numBytes The number of bytes the object takes up in the stream, or negative if this is unknown. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException; +} diff --git a/java/com/meyer/muscle/support/LEDataInputStream.java b/java/com/meyer/muscle/support/LEDataInputStream.java new file mode 100644 index 00000000..e012c209 --- /dev/null +++ b/java/com/meyer/muscle/support/LEDataInputStream.java @@ -0,0 +1,163 @@ +/* + * LEDataInputStream.java + * + * Copyright (c) 1998 + * Roedy Green + * Canadian Mind Products + * 5317 Barker Avenue + * Burnaby, BC Canada V5H 2N6 + * tel: (604) 435-3052 + * mailto:roedy@mindprod.com + * http://mindprod.com + * + * + * Version 1.0 1998 January 6 + * 1.1 1998 January 7 - officially implements DataInput + * 1.2 1998 January 9 - add LERandomAccessFile + * 1.3 1998 August 27 - fix bug, readFully instead of read. + * 1.4 1998 November 10 - add address and phone. + * 1.5 1999 October 8 - use cmp.LEDataStream package name. + * meyer 2001 Jan 12 - jaf@meyersound.com sucks it into com.meyer.muscle.support package + */ + +package com.meyer.muscle.support; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Very similar to DataInputStream except it reads little-endian instead of + * big-endian binary data. + * We can't extend DataInputStream directly since it has only final methods. + * This forces us implement LEDataInputStream with a DataInputStream object, + * and use wrapper methods. + */ +public class LEDataInputStream extends InputStream implements DataInput { + private static final String EmbeddedCopyright = "Copyright 1998 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + /** + * constructor + */ + public LEDataInputStream(InputStream in) { + this.in = in; + this.d = new DataInputStream(in); + w = new byte[8]; + } + + // L I T T L E E N D I A N R E A D E R S + // Little endian methods for multi-byte numeric types. + // Big-endian do fine for single-byte types and strings. + /** + * like DataInputStream.readShort except little endian. + */ + public final short readShort() throws IOException + { + d.readFully(w, 0, 2); + return (short)( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readUnsignedShort except little endian. + * Note, returns int even though it reads a short. + */ + public final int readUnsignedShort() throws IOException + { + d.readFully(w, 0, 2); + return ( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readChar except little endian. + */ + public final char readChar() throws IOException + { + d.readFully(w, 0, 2); + return (char) ( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readInt except little endian. + */ + public final int readInt() throws IOException + { + d.readFully(w, 0, 4); + return + (w[3]) << 24 | + (w[2]&0xff) << 16 | + (w[1]&0xff) << 8 | + (w[0]&0xff); + } + + /** + * like DataInputStream.readLong except little endian. + */ + public final long readLong() throws IOException + { + d.readFully(w, 0, 8); + return + (long)(w[7]) << 56 | /* long cast needed or shift done modulo 32 */ + (long)(w[6]&0xff) << 48 | + (long)(w[5]&0xff) << 40 | + (long)(w[4]&0xff) << 32 | + (long)(w[3]&0xff) << 24 | + (long)(w[2]&0xff) << 16 | + (long)(w[1]&0xff) << 8 | + (long)(w[0]&0xff); + } + + /** + * like DataInputStream.readFloat except little endian. + */ + public final float readFloat() throws IOException {return Float.intBitsToFloat(readInt());} + + /** + * like DataInputStream.readDouble except little endian. + */ + public final double readDouble() throws IOException {return Double.longBitsToDouble(readLong());} + + // p u r e l y w r a p p e r m e t h o d s + // We can't simply inherit since dataInputStream is final. + + /* Watch out, may return fewer bytes than requested. */ + public final int read(byte b[], int off, int len) throws IOException + { + // For efficiency, we avoid one layer of wrapper + return in.read(b, off, len); + } + + public final void readFully(byte b[]) throws IOException {d.readFully(b, 0, b.length);} + + public final void readFully(byte b[], int off, int len) throws IOException {d.readFully(b, off, len);} + + public final int skipBytes(int n) throws IOException {return d.skipBytes(n);} + + /* only reads one byte */ + public final boolean readBoolean() throws IOException {return d.readBoolean();} + + public final byte readByte() throws IOException {return d.readByte();} + + public int read() throws IOException { return in.read(); } + // note: returns an int, even though says Byte. + public final int readUnsignedByte() throws IOException {return d.readUnsignedByte();} + + /** @deprecated */ + public final String readLine() throws IOException {return d.readLine();} + + public final String readUTF() throws IOException {return d.readUTF();} + + // Note. This is a STATIC method! + public final static String readUTF(DataInput in) throws IOException {return DataInputStream.readUTF(in);} + + public final void close() throws IOException {d.close();} + + private DataInputStream d; // to get at high level readFully methods of DataInputStream + private InputStream in; // to get at the low-level read methods of InputStream + private byte w[]; // work array for buffering input +} diff --git a/java/com/meyer/muscle/support/LEDataOutputStream.java b/java/com/meyer/muscle/support/LEDataOutputStream.java new file mode 100644 index 00000000..d3d4fe0d --- /dev/null +++ b/java/com/meyer/muscle/support/LEDataOutputStream.java @@ -0,0 +1,156 @@ +/* + * LEDataOutputStream.java + * + * Copyright (c) 1998 + * Roedy Green + * Canadian Mind Products + * 5317 Barker Avenue + * Burnaby, BC Canada V5H 2N6 + * tel: (604) 435-3052 + * mailto:roedy@mindprod.com + * http://mindprod.com + * + * Copyright (c) 1998 Roedy Green of Canadian Mind Products + * Version 1.0 1998 January 6 + * 1.1 1998 January 7 - officially implements DataInput + * 1.2 1998 January 9 - add LERandomAccessFile + * 1.3 1998 August 28 + * 1.4 1998 November 10 - add new address and phone. + * 1.5 1999 October 8 - use cmp.LEDataStream package name. + * meyer 2001 Jan 12 - jaf@meyersound.com sucks it into com.meyer.muscle.support package + */ + +package com.meyer.muscle.support; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + +/** + * Very similar to DataOutputStream except it writes little-endian instead of + * big-endian binary data. + * We can't extend DataOutputStream directly since it has only final methods. + * This forces us implement LEDataOutputStream with a DataOutputStream object, + * and use wrapper methods. + */ +public class LEDataOutputStream extends OutputStream implements DataOutput { + private static final String EmbeddedCopyright = "Copyright 1998 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + /** + * constructor + */ + public LEDataOutputStream(OutputStream out) { + this.d = new DataOutputStream(out); + w = new byte[8]; // work array for composing output + } + + // L I T T L E E N D I A N W R I T E R S + // Little endian methods for multi-byte numeric types. + // Big-endian do fine for single-byte types and strings. + + /** + * like DataOutputStream.writeShort. + * also acts as a writeUnsignedShort + */ + public final void writeShort(int v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + d.write(w, 0, 2); + } + + /** + * like DataOutputStream.writeChar. + * Note the parm is an int even though this as a writeChar + */ + public final void writeChar(int v) throws IOException + { + // same code as writeShort + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + d.write(w, 0, 2); + } + + /** + * like DataOutputStream.writeInt. + */ + public final void writeInt(int v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + w[2] = (byte)(v >> 16); + w[3] = (byte)(v >> 24); + d.write(w, 0, 4); + } + + /** + * like DataOutputStream.writeLong. + */ + public final void writeLong(long v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + w[2] = (byte)(v >> 16); + w[3] = (byte)(v >> 24); + w[4] = (byte)(v >> 32); + w[5] = (byte)(v >> 40); + w[6] = (byte)(v >> 48); + w[7] = (byte)(v >> 56); + d.write(w, 0, 8); + } + + /** + * like DataOutputStream.writeFloat. + */ + public final void writeFloat(float v) throws IOException {writeInt(Float.floatToIntBits(v));} + + /** + * like DataOutputStream.writeDouble. + */ + public final void writeDouble(double v) throws IOException {writeLong(Double.doubleToLongBits(v));} + + /** + * like DataOutputStream.writeChars, flip each char. + */ + public final void writeChars(String s) throws IOException + { + int len = s.length(); + for ( int i = 0 ; i < len ; i++ ) { + writeChar(s.charAt(i)); + } + } // end writeChars + + // p u r e l y w r a p p e r m e t h o d s + // We cannot inherit since DataOutputStream is final. + + /* This method writes only one byte, even though it says int */ + public final synchronized void write(int b) throws IOException {d.write(b);} + + public final synchronized void write(byte b[], int off, int len) throws IOException {d.write(b, off, len);} + + public void flush() throws IOException {d.flush();} + + /* Only writes one byte */ + public final void writeBoolean(boolean v) throws IOException {d.writeBoolean(v);} + + public final void writeByte(int v) throws IOException {d.writeByte(v);} + + public final void writeBytes(String s) throws IOException {d.writeBytes(s);} + + public final void writeUTF(String str) throws IOException {d.writeUTF(str);} + + public final int size() {return d.size();} + + public final void write(byte b[]) throws IOException {d.write(b, 0, b.length);} + + public final void close() throws IOException {d.close();} + + // i n s t a n c e v a r i a b l e s + + private DataOutputStream d; // to get at high level write methods of DataOutputStream + private byte w[]; // work array for composing output + +} // end LEDataOutputStream + + diff --git a/java/com/meyer/muscle/support/NotEnoughDataException.java b/java/com/meyer/muscle/support/NotEnoughDataException.java new file mode 100644 index 00000000..09d2f24f --- /dev/null +++ b/java/com/meyer/muscle/support/NotEnoughDataException.java @@ -0,0 +1,20 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.support; + +import java.io.IOException; + +/** Exception that is thrown when there is enough data in a buffer + * to unflatten an entire Message. + */ +public final class NotEnoughDataException extends IOException +{ + private static final long serialVersionUID = 234010774012207621L; + private int _numMissingBytes; + + public NotEnoughDataException(int numMissingBytes) + { + _numMissingBytes = numMissingBytes; + } + + public final int getNumMissingBytes() {return _numMissingBytes;} +} diff --git a/java/com/meyer/muscle/support/Point.java b/java/com/meyer/muscle/support/Point.java new file mode 100644 index 00000000..9a5a7424 --- /dev/null +++ b/java/com/meyer/muscle/support/Point.java @@ -0,0 +1,144 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.support; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** A Java equivalent to Be's BPoint class */ +public class Point implements Flattenable +{ + /** Our data members, exposed for convenience */ + public float x = 0.0f; + public float y = 0.0f; + + /** Default constructor, sets the point to be (0, 0) */ + public Point() { /* empty */ } + + /** Constructor where you specify the initial value of the point + * @param X Initial X position + * @param Y Initial Y position + */ + public Point(float X, float Y) {set(X, Y);} + + /** Returns a clone of this object. */ + public Flattenable cloneFlat() {return new Point(x, y);} + + /** Should set this object's state equal to that of (setFromMe), or throw an UnflattenFormatException if it can't be done. + * @param setFromMe The object we want to be like. + * @throws ClassCastException if (setFromMe) isn't a Point + */ + public void setEqualTo(Flattenable setFromMe) throws ClassCastException + { + Point p = (Point) setFromMe; + set(p.x, p.y); + } + + /** Sets a new value for the point. + * @param X The new X value + * @param Y The new Y value + */ + public void set(float X, float Y) {x = X; y = Y;} + + /** If the point is outside the rectangle specified by the two arguments, + * it will be moved horizontally and/or vertically until it falls inside the rectangle. + * @param topLeft Minimum values acceptable for X and Y + * @param bottomRight Maximum values acceptable for X and Y + */ + public void constrainTo(Point topLeft, Point bottomRight) + { + if (x < topLeft.x) x = topLeft.x; + if (y < topLeft.y) y = topLeft.y; + if (x > bottomRight.x) x = bottomRight.x; + if (y > bottomRight.y) y = bottomRight.y; + } + + public String toString() {return "Point: " + x + " " + y;} + + /** Returns another Point whose values are the sum of this point's values and (p)'s values. + * @param rhs Point to add + * @return Resulting point + */ + public Point add(Point rhs) {return new Point(x+rhs.x, y+rhs.y);} + + /** Returns another Point whose values are the difference of this point's values and (p)'s values. + * @param rhs Point to subtract from this + * @return Resulting point + */ + public Point subtract(Point rhs) {return new Point(x-rhs.x, y-rhs.y);} + + /** Adds (p)'s values to this point's values */ + public void addToThis(Point p) + { + x += p.x; + y += p.y; + } + + /** Subtracts (p)'s values from this point's values */ + public void subtractFromThis(Point p) + { + x -= p.x; + y -= p.y; + } + + /** Returns true iff (p)'s values match this point's values */ + public boolean equals(Object o) + { + if (o instanceof Point) + { + Point p = (Point) o; + return ((x == p.x)&&(y == p.y)); + } + return false; + } + + /** Returns true. */ + public boolean isFixedSize() {return true;} + + /** Returns B_POINT_TYPE. */ + public int typeCode() {return TypeConstants.B_POINT_TYPE;} + + /** Returns 8 (2*sizeof(float)) */ + public int flattenedSize() {return 8;} + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException + { + out.writeFloat(x); + out.writeFloat(y); + } + + public void flatten(ByteBuffer out) throws IOException + { + out.putFloat(x); + out.putFloat(y); + } + /** + * Returns true iff (code) is B_POINT_TYPE. + */ + public boolean allowsTypeCode(int code) {return (code == B_POINT_TYPE);} + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException + { + x = in.readFloat(); + y = in.readFloat(); + } + + public void unflatten(ByteBuffer in, int numBytes) throws IOException, UnflattenFormatException + { + x = in.getFloat(); + y = in.getFloat(); + } + +} + diff --git a/java/com/meyer/muscle/support/Preferences.java b/java/com/meyer/muscle/support/Preferences.java new file mode 100644 index 00000000..decfead5 --- /dev/null +++ b/java/com/meyer/muscle/support/Preferences.java @@ -0,0 +1,97 @@ +package com.meyer.muscle.support; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import com.meyer.muscle.message.Message; + +/** + * Encapsulates a Message for easier use as a Preference holder. + * + * @version 1.0 1.22.2003 + * @author Bryan Varner + */ +public class Preferences { + File prefsFile; + protected Message prefs; + + /** + * Creates a Preferences object with a blank message, that dosen't have a save file. + */ + public Preferences() { + prefs = new Message(); + prefsFile = null; + } + + /** + * Creates a Preferences object with a default message, that dosen't have a save file. + */ + public Preferences(Message defaults) { + prefs = defaults; + prefsFile = null; + } + + /** + * Creates a new Preferences from the given file. + * @param loadFile the File to load the preferences from. + */ + public Preferences(String loadFile) throws IOException { + prefs = new Message(); + prefsFile = new File(loadFile); + DataInputStream fileStream = new DataInputStream(new FileInputStream(prefsFile)); + try { + prefs.unflatten(fileStream, -1); + } catch (Exception e) { + // You are screwed. + } + } + + /** + * Creates a new Preferences instance using defaults for the initial settings, + * then over-writing any defaults with the values from loadFile. + * @param loadFile A Flattened Message to load as Preferences. + * @param defaults An existing Message to use as the base. + */ + public Preferences(String loadFile, Message defaults) { + prefs = defaults; + try { + prefsFile = new File(loadFile); + DataInputStream fileStream = new DataInputStream(new FileInputStream(prefsFile)); + prefs.unflatten(fileStream, -1); + } catch (Exception e) { + // You are screwed. + } + } + + /** + * Saves the preferences to the file they were opened from. + * @throws IOException + */ + public void save() throws IOException { + if (prefsFile != null) { + save(prefsFile.getAbsolutePath()); + } + } + + /** + * Saves the preferences to the file specified. + * @param saveAs the file to save the preferences to. + * @throws IOException + */ + public void save(String saveAs) throws IOException { + DataOutputStream fileStream = new DataOutputStream(new FileOutputStream(saveAs, false)); + prefs.flatten(fileStream); + fileStream.close(); + } + + /** + * @return the Message this object is manipulating. + */ + public Message getMessage() { + return prefs; + } +} diff --git a/java/com/meyer/muscle/support/Rect.java b/java/com/meyer/muscle/support/Rect.java new file mode 100644 index 00000000..4e7e3060 --- /dev/null +++ b/java/com/meyer/muscle/support/Rect.java @@ -0,0 +1,230 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.txt file for details. */ +package com.meyer.muscle.support; + +import java.awt.Rectangle; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** A Java equivalent of Be's BRect class. */ +public class Rect implements Flattenable +{ + /** Exposed member variable representing the position of the left edge of the rectangle. */ + public float left = 0.0f; + + /** Exposed member variable representing the position of the top edge of the rectangle. */ + public float top = 0.0f; + + /** Exposed member variable representing the position of the right edge of the rectangle. */ + public float right = -1.0f; + + /** Exposed member variable representing the position of the bottom edge of the rectangle. */ + public float bottom = -1.0f; + + /** Default Constructor. + * Creates a rectangle with upper left point (0,0), and lower right point (-1,-1). + * Note that this rectangle has a negative area! (that is, it's imaginary) + */ + public Rect() { /* empty */ } + + /** Constructor where you specify the left, top, right, and bottom coordinates */ + public Rect(float l, float t, float r, float b) {set(l,t,r,b);} + + /** Copy constructor */ + public Rect(Rect rhs) {setEqualTo(rhs);} + + /** Constructor where you specify the leftTop point and the rightBottom point. */ + public Rect(Point leftTop, Point rightBottom) {set(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);} + + /** Returns a clone of this object. */ + public Flattenable cloneFlat() {return new Rect(left, top, right, bottom);} + + /** Sets this equal to (rhs). + * @param rhs The rectangle to become like. + * @throws ClassCastException if (rhs) isn't a Rect + */ + public void setEqualTo(Flattenable rhs) + { + Rect copyMe = (Rect) rhs; + set(copyMe.left, copyMe.top, copyMe.right, copyMe.bottom); + } + + /** Set a new position for the rectangle. */ + public void set(float l, float t, float r, float b) + { + left = l; + top = t; + right = r; + bottom = b; + } + + /** Print the rectangle's current state to (out) */ + public String toString() + { + return "Rect: leftTop=(" + left + "," + top + ") rightBottom=(" + right + "," + bottom + ")"; + } + + /** Returns the left top corner of the rectangle. */ + public Point leftTop() {return new Point(left, top);} + + /** Returns the right bottom corner of the rectangle. */ + public Point rightBottom() {return new Point(right, bottom);} + + /** Returns the left bottom corner of the rectangle. */ + public Point leftBottom() {return new Point(left, bottom);} + + /** Returns the right top corner of the rectangle. */ + public Point rightTop() {return new Point(right, top);} + + /** Set the left top corner of the rectangle. */ + public void setLeftTop(Point p) {left = p.x; top = p.y;} + + /** Set the right bottom corner of the rectangle. */ + public void setRightBottom(Point p) {right = p.x; bottom = p.y;} + + /** Set the left bottom corner of the rectangle. */ + public void setLeftBottom(Point p) {left = p.x; bottom = p.y;} + + /** Set the right top corner of the rectangle. */ + public void setRightTop(Point p) {right = p.x; top = p.y;} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + public void insetBy(Point p) {insetBy(p.x, p.y);} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + public void insetBy(float dx, float dy) {left += dx; top += dy; right -= dx; bottom -= dy;} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + public void offsetBy(Point p) {offsetBy(p.x, p.y);} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + public void offsetBy(float dx, float dy) {left += dx; top += dy; right += dx; bottom += dy;} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + public void offsetTo(Point p) {offsetTo(p.x, p.y);} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + public void offsetTo(float x, float y) {right = x + width(); bottom = y + height(); left = x; top = y;} + + /** Comparison Operator. Returns true iff (r)'s dimensions are exactly the same as this rectangle's. */ + public boolean equals(Object o) + { + if (o instanceof Rect) + { + Rect r = (Rect) o; + return (left == r.left)&&(top == r.top)&&(right == r.right)&&(bottom == r.bottom); + } + return false; + } + + /** Returns a rectangle whose area is the intersecting subset of this rectangle's and (r)'s */ + public Rect intersect(Rect r) + { + Rect ret = new Rect(this); + if (ret.left < r.left) ret.left = r.left; + if (ret.right > r.right) ret.right = r.right; + if (ret.top < r.top) ret.top = r.top; + if (ret.bottom > r.bottom) ret.bottom = r.bottom; + return ret; + } + + /** Returns a rectangle whose area is a superset of the union of this rectangle's and (r)'s */ + public Rect unify(Rect r) + { + Rect ret = new Rect(this); + if (r.left < ret.left) ret.left = r.left; + if (r.right > ret.right) ret.right = r.right; + if (r.top < ret.top) ret.top = r.top; + if (r.bottom > ret.bottom) ret.bottom = r.bottom; + return ret; + } + + /** Returns true iff this rectangle and (r) overlap in space. */ + public boolean intersects(Rect r) {return (r.intersect(this).isValid());} + + /** Returns true iff this rectangle's area is non imaginary (ie width() and height()) are both non-negative) */ + public boolean isValid() {return ((width() >= 0.0f)&&(height() >= 0.0f));} + + /** Returns the width of this rectangle. */ + public float width() {return right - left;} + + /** Returns the height of this rectangle. */ + public float height() {return bottom - top;} + + /** Returns true iff this rectangle contains the specified point. */ + public boolean contains(Point p) + { + return ((p.x >= left)&&(p.x <= right)&&(p.y >= top)&&(p.y <= bottom)); + } + + /** Returns true iff this rectangle fully the specified rectangle. */ + public boolean contains(Rect p) + { + return ((contains(p.leftTop()))&&(contains(p.rightTop()))&& + (contains(p.leftBottom()))&&(contains(p.rightBottom()))); + } + + /** Part of the Flattenable API: Returns true. */ + public boolean isFixedSize() {return true;} + + /** Part of the Flattenable API: Returns B_RECT_TYPE. */ + public int typeCode() {return B_RECT_TYPE;} + + /** Part of the Flattenable API: Returns 12 (ie 4*sizeof(float)). */ + public int flattenedSize() {return 16;} + + /** Returns true only if code is B_RECT_TYPE. */ + public boolean allowsTypeCode(int code) {return (code == B_RECT_TYPE);} + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException + { + out.writeFloat(left); + out.writeFloat(top); + out.writeFloat(right); + out.writeFloat(bottom); + } + + public void flatten(ByteBuffer out) throws IOException + { + out.putFloat(left); + out.putFloat(top); + out.putFloat(right); + out.putFloat(bottom); + } + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException + { + left = in.readFloat(); + top = in.readFloat(); + right = in.readFloat(); + bottom = in.readFloat(); + } + + public void unflatten(ByteBuffer in, int numBytes) throws IOException, UnflattenFormatException + { + left = in.getFloat(); + top = in.getFloat(); + right = in.getFloat(); + bottom = in.getFloat(); + } + + /** + * Creates a Java Rectangle from the current values. + * @return a java.awt.Rectangle representing the same area. + */ + public Rectangle getRectangle() { + return new Rectangle((int)left, (int)top, (int)right, (int)bottom); + } +} diff --git a/java/com/meyer/muscle/support/TypeConstants.java b/java/com/meyer/muscle/support/TypeConstants.java new file mode 100644 index 00000000..b94f417d --- /dev/null +++ b/java/com/meyer/muscle/support/TypeConstants.java @@ -0,0 +1,24 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.support; + +/** Java declarations for Be's type constants. The values are all the same as Be's. */ +public interface TypeConstants +{ + public static final int B_ANY_TYPE = 1095653716; // 'ANYT', // wild card + public static final int B_BOOL_TYPE = 1112493900; // 'BOOL', + public static final int B_DOUBLE_TYPE = 1145195589; // 'DBLE', + public static final int B_FLOAT_TYPE = 1179406164; // 'FLOT', + public static final int B_INT64_TYPE = 1280069191; // 'LLNG', // a.k.a. long in Java + public static final int B_INT32_TYPE = 1280265799; // 'LONG', // a.k.a. int in Java + public static final int B_INT16_TYPE = 1397248596; // 'SHRT', // a.k.a. short in Java + public static final int B_INT8_TYPE = 1113150533; // 'BYTE', // a.k.a. byte in Java + public static final int B_MESSAGE_TYPE = 1297303367; // 'MSGG', + public static final int B_POINTER_TYPE = 1347310674; // 'PNTR', // parsed as int in Java (but not very useful) + public static final int B_POINT_TYPE = 1112559188; // 'BPNT', // muscle.support.Point in Java + public static final int B_RECT_TYPE = 1380270932; // 'RECT', // muscle.support.Rect in Java + public static final int B_STRING_TYPE = 1129534546; // 'CSTR', // java.lang.String + public static final int B_OBJECT_TYPE = 1330664530; // 'OPTR', + public static final int B_RAW_TYPE = 1380013908; // 'RAWT', + public static final int B_MIME_TYPE = 1296649541; // 'MIME', +} + diff --git a/java/com/meyer/muscle/support/UnflattenFormatException.java b/java/com/meyer/muscle/support/UnflattenFormatException.java new file mode 100644 index 00000000..74e66252 --- /dev/null +++ b/java/com/meyer/muscle/support/UnflattenFormatException.java @@ -0,0 +1,23 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.support; + +import java.io.IOException; + +/** Exception that is thrown when an unflatten() attempt fails, usually + * due to unrecognized data in the input stream, or a type mismatch. + */ +public class UnflattenFormatException extends IOException +{ + private static final long serialVersionUID = 234010774012207620L; + + public UnflattenFormatException() + { + super("unexpected bytes during Flattenable unflatten"); + } + + public UnflattenFormatException(String s) + { + super(s); + } +} + diff --git a/java/com/meyer/muscle/test/TestClient.java b/java/com/meyer/muscle/test/TestClient.java new file mode 100644 index 00000000..4585d98e --- /dev/null +++ b/java/com/meyer/muscle/test/TestClient.java @@ -0,0 +1,158 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.test; + +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.IOException; + +import java.util.StringTokenizer; +import java.util.NoSuchElementException; + +import com.meyer.muscle.iogateway.AbstractMessageIOGateway; +import com.meyer.muscle.message.Message; +import com.meyer.muscle.client.MessageTransceiver; +import com.meyer.muscle.client.StorageReflectConstants; +import com.meyer.muscle.support.UnflattenFormatException; +import com.meyer.muscle.thread.MessageListener; +import com.meyer.muscle.thread.MessageQueue; + +/** A quick test class, similar to the C++ muscle/test/testreflectclient program */ +public class TestClient implements MessageListener, StorageReflectConstants +{ + public static final void main(String args[]) + { + if (args.length != 1) + { + System.out.println("Usage java com.meyer.muscle.test.TestClient 12.18.240.15"); + System.exit(5); + } + + new TestClient(args[0], (args.length >= 2) ? (new Integer(args[1])).intValue() : 2960); + } + + public TestClient(String hostName, int port) + { + // Connect here.... + MessageTransceiver mc = new MessageTransceiver(new MessageQueue(this), AbstractMessageIOGateway.MUSCLE_MESSAGE_ENCODING_ZLIB_6, 6*1024*1024); + System.out.println("Connecting to " + hostName + ":" + port); + mc.connect(hostName, port, "Session Connected", "Session Disconnected"); + + // Listen for user input + try { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String nextLine; + while((nextLine = br.readLine()) != null) + { + System.out.println("You typed: ["+nextLine+"]"); + StringTokenizer st = new StringTokenizer(nextLine); + try { + String command = st.nextToken(); + Message msg = new Message(PR_COMMAND_PING); + if (command.equalsIgnoreCase("q")) break; + else if (command.equalsIgnoreCase("t")) + { + int [] ints = {1,3}; + msg.setInts("ints", ints); + float [] floats = {2, 4}; + msg.setFloats("floats", floats); + double [] doubles = {-5, -10}; + msg.setDoubles("doubles", doubles); + byte [] bytes = {0x50}; + msg.setBytes("bytes", bytes); + short [] shorts = {3,15}; + msg.setShorts("shorts", shorts); + long [] longs = {50000}; + msg.setLongs("longs", longs); + boolean [] booleans = {true, false}; + msg.setBooleans("booleans", booleans); + String [] strings = {"Hey", "What's going on?"}; + msg.setStrings("strings", strings); + + Message q = (Message) msg.cloneFlat(); + Message r = new Message(1234); + r.setString("I'm way", "down here!"); + q.setMessage("submessage", r); + msg.setMessage("message", q); + } + else if (command.equals("s")) { + msg.what = PR_COMMAND_SETDATA; + msg.setMessage(st.nextToken(), new Message(1234)); + } + else if (command.equals("k")) { + msg.what = PR_COMMAND_KICK; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("b")) { + msg.what = PR_COMMAND_ADDBANS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("B")) { + msg.what = PR_COMMAND_REMOVEBANS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("g")) { + msg.what = PR_COMMAND_GETDATA; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("p")) { + msg.what = PR_COMMAND_SETPARAMETERS; + msg.setString(st.nextToken(), ""); + } + else if (command.equals("P")) { + msg.what = PR_COMMAND_GETPARAMETERS; + } + else if (command.equals("d")) { + msg.what = PR_COMMAND_REMOVEDATA; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("D")) { + msg.what = PR_COMMAND_REMOVEPARAMETERS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } + else if (command.equals("big")) { + msg.what = PR_COMMAND_PING; + int numFloats = 1*1024*1024; + float lotsafloats[] = new float[numFloats]; // 4MB worth of floats! + for (int i=0; i 0) + { + nextMessage = _messageQueue.removeFirstElement(); + messagesLeft = _messageQueue.size(); + } + else + { + _threadActive = false; + _myThread = null; + break; // bye bye! + } + } + + // Process the message (unsynchronized) + if (nextMessage instanceof ListControlMessage) + { + ListControlMessage lcm = (ListControlMessage) nextMessage; + if (lcm._add) + { + if (_listeners != null) _listeners.appendElement(new MessageQueue(lcm._listener)); + else + { + if (_listener != null) + { + // convert to multi-listener (asynchronous) mode + _listeners = new Queue(); + _listeners.appendElement(new MessageQueue(_listener)); + _listeners.appendElement(new MessageQueue(lcm._listener)); + _listener = null; + } + else _listener = lcm._listener; + } + } + else + { + if (lcm._listener != null) + { + if (_listeners != null) + { + for (int i=_listeners.size()-1; i>=0; i--) + { + if (((MessageQueue)_listeners.elementAt(i))._listener == lcm._listener) + { + _listeners.removeElementAt(i); + if (_listeners.size() == 1) + { + // revert back to single-listener (synchronous) mode + _listener = (MessageListener) _listeners.firstElement(); + _listeners = null; + } + break; + } + } + } + else if (_listener == lcm._listener) _listener = null; + } + else + { + _listeners = null; + _listener = null; + } + } + } + else + { + if (_listener != null) + { + try { + _listener.messageReceived(nextMessage, messagesLeft); + } + catch(Exception ex) { + ex.printStackTrace(); + } + } + else if (_listeners != null) + { + for (int i=_listeners.size()-1; i>=0; i--) + { + try { + ((MessageQueue)_listeners.elementAt(i)).messageReceived(nextMessage, messagesLeft); + } + catch(Exception ex) { + ex.printStackTrace(); + } + } + } + } + } + } + + /** This class is used to remotely interrupt() my Thread if it turns out to need interrupting + * (i.e. if an interrupt was requested before the Thread was started) + * This way the interrupt always affects the internal thread the same way, no matter what. + */ + private class ThreadInterruptor implements Runnable + { + public ThreadInterruptor(Thread thread) {_thread = thread;} + public void run() {_thread.interrupt();} + + private Thread _thread; + } + + /** Message class used to add or remove listeners */ + private class ListControlMessage + { + public ListControlMessage(MessageListener l, boolean add) + { + _listener = l; + _add = add; + } + + public MessageListener _listener; + public boolean _add; + } + + private Queue _messageQueue = new Queue(); // shared (synchronized this) + private MessageListener _listener = null; // used if we have exactly one listener + private Queue _listeners = null; // used if we have more than one listener + private boolean _threadActive = false; // shared (synchronized this) + private boolean _interruptPending = false; // shared (synchronized this) + private Thread _myThread = null; // set inside of run() (synchronized this) + private ThreadPool _threadPool = null; // if a non-default thread pool is to be used, it is set here +} + diff --git a/java/com/meyer/muscle/thread/ThreadPool.java b/java/com/meyer/muscle/thread/ThreadPool.java new file mode 100644 index 00000000..f62a947a --- /dev/null +++ b/java/com/meyer/muscle/thread/ThreadPool.java @@ -0,0 +1,116 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.muscle.thread; + +import com.meyer.muscle.queue.Queue; + +/** A ThreadPool is an object that holds a number of threads + * and lets you re-use them. This lets us avoid having + * to terminate and re-spawn threads all the time. This + * class is used a lot by the MessageQueue class. + */ +public final class ThreadPool implements Runnable +{ + /** Creates a ThreadPool with a low-water mark of 10 threads, + * and a high-water mark of 50 threads + */ + public ThreadPool() {this(10, 50);} + + /** Create a new ThreadPool + * @param lowMark Threads will be retained until this many are present. + * @param highMark No more than this many threads will be allocated at once. + */ + public ThreadPool(int lowMark, int highMark) + { + _lowMark = lowMark; + _highMark = highMark; + } + + /** Schedules a task for execution. The task may begin + * right away, or wait until there is a thread available. + */ + public synchronized void startThread(Runnable target) + { + _tasks.appendElement(target); + if ((_idleThreadCount <= 0)&&(_threadCount < _highMark)) + { + _threadCount++; // note that the new thread exists + Thread t = new Thread(_threadGroup, this, "MUSCLE worker thread-"+_threadCount); + t.setDaemon(true); + t.start(); // and off he goes + } + notify(); // wake up a thread to handle our request + } + + /** A singleton ThreadPool, for convenience */ + public static ThreadPool getDefaultThreadPool() {return _defaultThreadPool;} + + /** Change the singleton ThreadPool if you want */ + public static void setDefaultThreadPool(ThreadPool p) {_defaultThreadPool = p;} + + /** Exposed as an implementation detail. Please ignore. */ + public void run() + { + boolean keepGoing = true; + + while(keepGoing) + { + // Keep running tasks as long as any are available + while(true) + { + Runnable nextTask = null; + + // Critical section: Get the next task, if any + synchronized(this) + { + if (_tasks.size() > 0) nextTask = (Runnable) _tasks.removeFirstElement(); + else + { + if (_threadCount > _lowMark) + { + _threadCount--; + keepGoing = false; // this thread is going away -- there are too many threads! + } + } + } + + // Important that this is done in an unsynchronized section, to avoid locking the pool + if (nextTask != null) + { + try { + nextTask.run(); // do it! + } + catch(Exception e) { + e.printStackTrace(); + } + } + else break; // go back to sleep until more tasks are available + } + + if (keepGoing) + { + // No more tasks left: go to sleep + synchronized(this) + { + _idleThreadCount++; // this thread is idle again + try { + wait(); // wait until startThread() is called again. + } + catch(InterruptedException ex) { + _threadCount--; // this can happen if e.g. the applet is shutting down + keepGoing = false; + } + _idleThreadCount--; // now it's busy + } + } + } + } + + private static ThreadPool _defaultThreadPool = new ThreadPool(); // singleton + + private ThreadGroup _threadGroup = new ThreadGroup("Muscle Threads"); + private Queue _tasks = new Queue(); // Runnables that need to be run + private int _idleThreadCount = 0; // how many threads are idle + private int _threadCount = 0; // how many threads are in the pool + private int _lowMark; // minimum # of threads to retain + private int _highMark; // maximum # of threads to allocate +} diff --git a/java/html/gendocs.bat b/java/html/gendocs.bat new file mode 100755 index 00000000..6cf7d935 --- /dev/null +++ b/java/html/gendocs.bat @@ -0,0 +1,2 @@ +@echo Generating documentation files... +javadoc -d . @packages -sourcepath .. diff --git a/java/html/packages b/java/html/packages new file mode 100644 index 00000000..fe5232de --- /dev/null +++ b/java/html/packages @@ -0,0 +1,7 @@ +com.meyer.muscle.client +com.meyer.muscle.iogateway +com.meyer.muscle.message +com.meyer.muscle.queue +com.meyer.muscle.support +com.meyer.muscle.test +com.meyer.muscle.thread diff --git a/java_j2me/README.TXT b/java_j2me/README.TXT new file mode 100644 index 00000000..5d45a72a --- /dev/null +++ b/java_j2me/README.TXT @@ -0,0 +1,28 @@ +This folder contains Bryan Varner's port of the MUSCLE Java API to J2ME +(Java 2 Micro Edition). This code is designed to run on cell phones and the +like, where the Java 1.4.x APIs are not available. If you are developing for +personal computer platforms that use J2SE (Java 2 Standard Edition), you +probably want to use the Java code found in the muscle/java folder instead +of the code in this folder. + +-Jeremy 10/17/2007 + +--------------------------------------------------------- + +To build you'll need to install the WTK 2.0 (or newer) from Sun. + http://java.sun.com/products/sjwtoolkit/overview.html#3 +Once that is installed, update the wtk.home, and device.profile in build.properties. + +I've tested the build.xml with Apache Ant 1.7 on windows XP. + +We're using the Antenna (http://antenna.sourceforge.net) 1.0 tasks for Ant. +You'll need to download antenna-bin-1.0.0.jar from http://antenna.sourceforge.net and +place in the muscle/java_j2me/support folder. (it's too big to include in the MUSCLE +distribution, sorry!) + +The build.xml file has the muscle.jar and the testclient.jar segmented clearly +to demonstrate how to include the muscle.jar in a j2me application as an +external library. It might be wise to use this as a sample when starting new +mobile projects. + +-Bryan 10/18/2007 diff --git a/java_j2me/build.properties b/java_j2me/build.properties new file mode 100644 index 00000000..88492571 --- /dev/null +++ b/java_j2me/build.properties @@ -0,0 +1,8 @@ +wtk.home: c:/SonyEricsson/JavaME_SDK_CLDC/PC_Emulation/WTK2 +#wtk.home: c:/WTK2 +device.profile: SonyEricsson_JP7_240x320_Emu +#device.profile: DefaultColorPhone + +wtk.cldc.version: 1.1 +wtk.midp.version: 2.0 + diff --git a/java_j2me/build.xml b/java_j2me/build.xml new file mode 100644 index 00000000..9617343f --- /dev/null +++ b/java_j2me/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_j2me/src/com/jcraft/ChangeLog b/java_j2me/src/com/jcraft/ChangeLog new file mode 100644 index 00000000..c828fae3 --- /dev/null +++ b/java_j2me/src/com/jcraft/ChangeLog @@ -0,0 +1,115 @@ +ChangeLog of JZlib +==================================================================== +Last modified: Thu Aug 18 16:16:06 UTC 2005 + + +Changes since version 1.0.6: +- change: memory and performance optimizations in the inflate operation. + Many thanks to Paul Wakefield at platx.org(http://www.platx.org), who + suggested above improvements. +- change: added the nowrap argument to Z{Input,Output}?Stream. + + +Changes since version 1.0.5: +- ZInputStream.read(byte[], int, int) method return sometimes zero + instead of -1 at the end of stream. +- ZOutputStream.end() method should not declare IOException. +- It should be possible to call ZOutputStream.end() method more times + (because you can call close() method and after it end() method in + finally block). +- ZOutputStream.finish() method should not ignore IOException from flush(). +Many thanks to Matej Kraus at seznam.cz , who pointed out above problems. + + +Changes since version 1.0.4: +- a minor bug fix in ZInputStream + + +Changes since version 1.0.3: +- fixed a bug in closing ZOutputStream. + The inflating and deflating processes were not finished successfully. +- added 'finish' and 'end' methods to ZOutputStream.java +- added 'example/test_stream_deflate_inflate.java' + + +Changes since version 1.0.2: +- enabled pure Java implementation of adler32 and + commented out the dependency on J2SE in Adler32.java for J2ME users. + + +Changes since version 1.0.1: +- fixed a bug in deflating some trivial data, for example, + large data chunk filled with zero. +- added 'version' method to JZlib class. + + +Changes since version 1.0.0: +- added ZInputStream and ZOutputStream classes. +- fixed a typo in LICENSE.txt. + + +Changes since version 0.0.9: +- fixed a bug in setting a dictionary in the inflation process. +- The license was changed to a BSD style license. +- Z{Input,Output}Stream classes were deleted. + + +Changes since version 0.0.8: +- fixed a bug in handling a preset dictionary in the inflation process. + + +Changes since version 0.0.7: +- added methods to control the window size (the size of the history + buffer) and to handle the no-wrap option (no zlib header or check), + which is the undocumented functionality of zlib. + + +Changes since version 0.0.6: +- updated InfBlocks.java as zlib did to fix the vulnerability related to + the 'double free'. Of course, JZlib is free from such a vulnerability + like the 'double free', but JZlib had crashed with NullPointerException + by a specially-crafted block of invalid deflated data. + + +Changes since version 0.0.5: +- added 'flush()' method to com.jcraft.jzlib.ZOutputStream. +- fixed to take care when occurring the buffer overflow in + com.jcraft.jzlib.ZOutputStream. +Many thanks to Tim Bendfelt at cs.wisc.edu , who pointed out above problems. + + +Changes since version 0.0.4: +............................ +- fixed a bug in Adler32 class. +- fixed a bug in ZInputStream class +- modified ZInputStream to be extended from InputStream + instead of FileInputStream. +- modified ZOutputStream to be extended from OutputStream + instead of FileOutputStream. +- modified ZStream to be changeable wbits. Give wbits value to + the method 'deflateInit' of ZStream. +Many thanks to Bryan Keller, who reported bugs +and made suggestions. + + +Changes since version 0.0.3: +............................ +- fixed a bug in the compression level 0. +- modified to be compatible with JIKES's bug. +- added Z[Input,Output]Stream written by Lapo Luchini. + + +Changes since version 0.0.2: +............................ +- fixed a critical bug, which had arisen in the deflating operation with + NO_FLUSH and DEFAULT_COMPRESSION mode. + Many thanks to Bryan Keller(keller@neomar.com), who reported this glitch. + + +Changes since version 0.0.1: +............................ +- fixed the bad compression ratio problem, which is reported from + Martin Smith(martin@spamcop.net). The compression ratio was not so good + compared with the compression ration of zlib. Now, this problem has been + fixed and, I hope, that deflated files by JZlib and zlib are completely + same bit by bit. diff --git a/java_j2me/src/com/jcraft/LICENSE.txt b/java_j2me/src/com/jcraft/LICENSE.txt new file mode 100644 index 00000000..cdce5007 --- /dev/null +++ b/java_j2me/src/com/jcraft/LICENSE.txt @@ -0,0 +1,29 @@ +JZlib 0.0.* were released under the GNU LGPL license. Later, we have switched +over to a BSD-style license. + +------------------------------------------------------------------------------ +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/java_j2me/src/com/jcraft/README b/java_j2me/src/com/jcraft/README new file mode 100644 index 00000000..45027db9 --- /dev/null +++ b/java_j2me/src/com/jcraft/README @@ -0,0 +1,124 @@ + + JZlib + + zlib in pure Java(TM) + by ymnk@jcraft.com, JCraft,Inc. + + http://www.jcraft.com/jzlib/ + +Last modified: Fri Feb 14 13:31:26 UTC 2003 + +Description +=========== +JZlib is a re-implementation of zlib in pure Java. +The first and final aim for hacking this stuff is +to add the packet compression support to pure Java SSH systems. + + +Documentation +============= +* README files all over the source tree have info related to the stuff + in the directories. +* ChangeLog: what changed from the previous version? +* LICENSE.txt + + +Directories & Files in the Source Tree +====================================== +* com/ has source trees of JZlib. +* example/ has some samples, which demonstrate the usages. +* misc/ has some stuffs. At present, this directory includes + a patch file for MindTerm v.1.2.1, which adds the packet compression + functionality. + + +Features +======== +* Needless to say, JZlib can inflate data, which is deflated by zlib and + JZlib can generate deflated data, which is acceptable and inflated by zlib. +* JZlib supports all compression level and all flushing mode in zlib. +* JZlib does not support gzip file handling supports. +* The performance has not been estimated yet, but it will be not so bad + in deflating/inflating data stream on the low bandwidth network. +* JZlib is licensed under BSD style license. +* Any invention has not been done in developing JZlib. + So, if zlib is patent free, JZlib is also not covered by any patents. + + +Why JZlib? +========== +Java Platform API provides packages 'java.util.zip.*' for +accessing to zlib, but that support is very limited +if you need to use the essence of zlib. For example, +we needed to full access to zlib to add the packet compression +support to pure Java SSH system, but they are useless for our requirements. +The Internet draft, SSH Transport Layer Protocol, says in the section '4.2 Compression' as follows, + + The following compression methods are currently defined: + none REQUIRED no compression + zlib OPTIONAL GNU ZLIB (LZ77) compression + The "zlib" compression is described in [RFC-1950] and in [RFC-1951]. The + compression context is initialized after each key exchange, and is + passed from one packet to the next with only a partial flush being + performed at the end of each packet. A partial flush means that all data + will be output, but the next packet will continue using compression + tables from the end of the previous packet. + +To implement this functionality, the Z_PARTIAL_FLUSH mode of zlib must be +used, however JDK does not permit us to do so. It seems that this problem has +been well known and some people have already reported to +JavaSoft's BugParade(for example, BugId:4255743), +but any positive response has not been returned from JavaSoft, +so this problem will not be solved forever. +This is our motivation to hack JZlib. + + +A unofficial patch for MindTerm v.1.2.1 +======================================= +A unofficial patch file for MindTerm v.1.2.1 has included in 'misc' directory. +It adds the packet compression support to MindTerm. +Please refer to 'misc/README' file. + + +Copyrights & Disclaimers +======================== +JZlib is copyrighted by ymnk, JCraft,Inc. and is licensed through BSD +style license. Read the LICENSE.txt file for the complete license. +ZInputStream and ZOutputStream classes were contributed by Lapo Luchini. + + +Credits +======= +JZlib has been developed by ymnk@jcraft.com, +but he has just re-implemented zlib in pure Java. +So, all credit should go to authors Jean-loup Gailly and Mark Adler +and contributors of zlib. Here is the copyright notice of zlib version 1.1.3, + + |Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + | + |This software is provided 'as-is', without any express or implied + |warranty. In no event will the authors be held liable for any damages + |arising from the use of this software. + | + |Permission is granted to anyone to use this software for any purpose, + |including commercial applications, and to alter it and redistribute it + |freely, subject to the following restrictions: + | + |1. The origin of this software must not be misrepresented; you must not + | claim that you wrote the original software. If you use this software + | in a product, an acknowledgment in the product documentation would be + | appreciated but is not required. + |2. Altered source versions must be plainly marked as such, and must not be + | misrepresented as being the original software. + |3. This notice may not be removed or altered from any source distribution. + | + |Jean-loup Gailly Mark Adler + |jloup@gzip.org madler@alumni.caltech.edu + + +If you have any comments, suggestions and questions, write us +at jzlib@jcraft.com + + +``SSH is a registered trademark and Secure Shell is a trademark of +SSH Communications Security Corp (www.ssh.com)''. diff --git a/java_j2me/src/com/jcraft/jzlib/Adler32.java b/java_j2me/src/com/jcraft/jzlib/Adler32.java new file mode 100644 index 00000000..d8b6ef86 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/Adler32.java @@ -0,0 +1,94 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Adler32{ + + // largest prime smaller than 65536 + static final private int BASE=65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + static final private int NMAX=5552; + + long adler32(long adler, byte[] buf, int index, int len){ + if(buf == null){ return 1L; } + + long s1=adler&0xffff; + long s2=(adler>>16)&0xffff; + int k; + + while(len > 0) { + k=len=16){ + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + k-=16; + } + if(k!=0){ + do{ + s1+=buf[index++]&0xff; s2+=s1; + } + while(--k!=0); + } + s1%=BASE; + s2%=BASE; + } + return (s2<<16)|s1; + } + + /* + private java.util.zip.Adler32 adler=new java.util.zip.Adler32(); + long adler32(long value, byte[] buf, int index, int len){ + if(value==1) {adler.reset();} + if(buf==null) {adler.reset();} + else{adler.update(buf, index, len);} + return adler.getValue(); + } + */ +} diff --git a/java_j2me/src/com/jcraft/jzlib/Deflate.java b/java_j2me/src/com/jcraft/jzlib/Deflate.java new file mode 100644 index 00000000..2948a143 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/Deflate.java @@ -0,0 +1,1622 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public +final class Deflate{ + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_DEFAULT_COMPRESSION=-1; + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_MEM_LEVEL=8; + + static class Config{ + int good_length; // reduce lazy search above this match length + int max_lazy; // do not perform lazy search above this match length + int nice_length; // quit search above this match length + int max_chain; + int func; + Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static final private int STORED=0; + static final private int FAST=1; + static final private int SLOW=2; + static final private Config[] config_table; + static{ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + static final private String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + static final private int NeedMore=0; + + // block flush performed + static final private int BlockDone=1; + + // finish started, need only more output at next deflate + static final private int FinishStarted=2; + + // finish done, accept no more input or output + static final private int FinishDone=3; + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final private int Z_FILTERED=1; + static final private int Z_HUFFMAN_ONLY=2; + static final private int Z_DEFAULT_STRATEGY=0; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int INIT_STATE=42; + static final private int BUSY_STATE=113; + static final private int FINISH_STATE=666; + + // The deflate compression method + static final private int Z_DEFLATED=8; + + static final private int STORED_BLOCK=0; + static final private int STATIC_TREES=1; + static final private int DYN_TREES=2; + + // The three kinds of block type + static final private int Z_BINARY=0; + static final private int Z_ASCII=1; + static final private int Z_UNKNOWN=2; + + static final private int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final private int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final private int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final private int REPZ_11_138=18; + + static final private int MIN_MATCH=3; + static final private int MAX_MATCH=258; + static final private int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + static final private int MAX_BITS=15; + static final private int D_CODES=30; + static final private int BL_CODES=19; + static final private int LENGTH_CODES=29; + static final private int LITERALS=256; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + static final private int END_BLOCK=256; + + ZStream strm; // pointer back to this zlib stream + int status; // as the name implies + byte[] pending_buf; // output still pending + int pending_buf_size; // size of pending_buf + int pending_out; // next pending byte to output to the stream + int pending; // nb of bytes in the pending buffer + int noheader; // suppress zlib header and adler32 + byte data_type; // UNKNOWN, BINARY or ASCII + byte method; // STORED (for zip only) or DEFLATED + int last_flush; // value of flush param for previous deflate call + + int w_size; // LZ77 window size (32K by default) + int w_bits; // log2(w_size) (8..16) + int w_mask; // w_size - 1 + + byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + short[] head; // Heads of the hash chains or NIL. + + int ins_h; // hash index of string to be inserted + int hash_size; // number of elements in hash table + int hash_bits; // log2(hash_size) + int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + int block_start; + + int match_length; // length of best match + int prev_match; // previous match + int match_available; // set if previous match exists + int strstart; // start of string to insert + int match_start; // start of matching string + int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + int level; // compression level (1..9) + int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + int good_match; + + // Stop searching when current match exceeds this + int nice_match; + + short[] dyn_ltree; // literal and length tree + short[] dyn_dtree; // distance tree + short[] bl_tree; // Huffman tree for bit lengths + + Tree l_desc=new Tree(); // desc for literal tree + Tree d_desc=new Tree(); // desc for distance tree + Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + short[] bl_count=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + int[] heap=new int[2*L_CODES+1]; + + int heap_len; // number of elements in the heap + int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + byte[] depth=new byte[2*L_CODES+1]; + + int l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + int lit_bufsize; + + int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + int d_buf; // index of pendig_buf + + int opt_len; // bit length of current block with optimal trees + int static_len; // bit length of current block with static trees + int matches; // number of string matches in current block + int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + int bi_valid; + + Deflate(){ + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + final void put_byte(byte[] p, int start, int len){ + System.arraycopy(p, start, pending_buf, pending, len); + pending+=len; + } + + final void put_byte(byte c){ + pending_buf[pending++]=c; + } + final void put_short(int w) { + put_byte((byte)(w/*&0xff*/)); + put_byte((byte)(w>>>8)); + } + final void putShortMSB(int b){ + put_byte((byte)(b>>8)); + put_byte((byte)(b/*&0xff*/)); + } + + final void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + void send_bits(int value, int length){ + int len = length; + if (bi_valid > (int)Buf_size - len) { + int val = value; +// bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid)&0xffff); + put_short(bi_buf); + bi_buf = (short)(val >>> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } else { +// bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid)&0xffff); + bi_valid += len; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + boolean _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + pending_buf[l_buf+last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(pending_buf[l_buf+lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n (ascii_freq >>> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + void bi_flush(){ + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + put_byte((byte)bi_buf); + bi_buf>>>=8; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + void bi_windup(){ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte((byte)bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + void copy_block(int buf, // the input data + int len, // its length + boolean header // true if block header must be written + ){ + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + void flush_block_only(boolean eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>>3; + static_lenb=(static_len+3+7)>>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.arraycopy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(m>=w_size ? (short)(m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (m >= w_size ? (short)(m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<= MIN_MATCH){ + ins_h=(((ins_h)<=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<= MIN_MATCH) { + ins_h=(((ins_h)< 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + int deflateInit(ZStream strm, int level, int bits){ + return deflateInit2(strm, level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + int deflateInit(ZStream strm, int level){ + return deflateInit(strm, level, MAX_WBITS); + } + int deflateInit2(ZStream strm, int level, int method, int windowBits, + int memLevel, int strategy){ + int noheader = 0; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + noheader = 1; + windowBits = -windowBits; + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.noheader = noheader; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*4]; + pending_buf_size = lit_bufsize*4; + + d_buf = lit_bufsize/2; + l_buf = (1+2)*lit_bufsize; + + this.level = level; + +//System.out.println("level="+level); + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(strm); + } + + int deflateReset(ZStream strm){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(noheader < 0) { + noheader = 0; // was set to -1 by deflate(..., Z_FINISH); + } + status = (noheader!=0) ? BUSY_STATE : INIT_STATE; + strm.adler=strm._adler.adler32(0, null, 0, 0); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + int deflateParams(ZStream strm, int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + int deflateSetDictionary (ZStream strm, byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler=strm._adler.adler32(strm.adler, dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.arraycopy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + this.strm = strm; // just in case + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + putShortMSB((int)(strm.adler>>>16)); + putShortMSB((int)(strm.adler&0xffff)); + } + strm.adler=strm._adler.adler32(0, null, 0, 0); + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i>>16)); + putShortMSB((int)(strm.adler&0xffff)); + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + noheader = -1; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } +} diff --git a/java_j2me/src/com/jcraft/jzlib/InfBlocks.java b/java_j2me/src/com/jcraft/jzlib/InfBlocks.java new file mode 100644 index 00000000..451640c3 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/InfBlocks.java @@ -0,0 +1,613 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfBlocks{ + static final private int MANY=1440; + + // And'ing with mask[n] masks the lower n bits + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + // Table for deflate from PKZIP's appnote.txt. + static final int[] border = { // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int TYPE=0; // get type bits (3, including end bit) + static final private int LENS=1; // get lengths for stored + static final private int STORED=2;// processing stored block + static final private int TABLE=3; // get table lengths + static final private int BTREE=4; // get bit lengths tree for a dynamic block + static final private int DTREE=5; // get length, distance trees for a dynamic block + static final private int CODES=6; // processing fixed or dynamic block + static final private int DRY=7; // output remaining window bytes + static final private int DONE=8; // finished last block, done + static final private int BAD=9; // ot a data error--stuck here + + int mode; // current inflate_block mode + + int left; // if STORED, bytes left to copy + + int table; // table lengths (14 bits) + int index; // index into blens (or border) + int[] blens; // bit lengths of codes + int[] bb=new int[1]; // bit length tree depth + int[] tb=new int[1]; // bit length decoding tree + + InfCodes codes=new InfCodes(); // if CODES, current state + + int last; // true if this block is the last block + + // mode independent information + int bitk; // bits in bit buffer + int bitb; // bit buffer + int[] hufts; // single malloc for tree space + byte[] window; // sliding window + int end; // one byte after sliding window + int read; // window read pointer + int write; // window write pointer + Object checkfn; // check function + long check; // check on output + + InfTree inftree=new InfTree(); + + InfBlocks(ZStream z, Object checkfn, int w){ + hufts=new int[MANY*3]; + window=new byte[w]; + end=w; + this.checkfn = checkfn; + mode = TYPE; + reset(z, null); + } + + void reset(ZStream z, long[] c){ + if(c!=null) c[0]=check; + if(mode==BTREE || mode==DTREE){ + } + if(mode==CODES){ + codes.free(z); + } + mode=TYPE; + bitk=0; + bitb=0; + read=write=0; + + if(checkfn != null) + z.adler=check=z._adler.adler32(0L, null, 0, 0); + } + + int proc(ZStream z, int r){ + int t; // temporary storage + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + {p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} + {q=write;m=(int)(q>> 1){ + case 0: // stored + {b>>>=(3);k-=(3);} + t = k & 7; // go to byte boundary + + {b>>>=(t);k-=(t);} + mode = LENS; // get length of stored block + break; + case 1: // fixed + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[][] tl=new int[1][]; + int[][] td=new int[1][]; + + InfTree.inflate_trees_fixed(bl, bd, tl, td, z); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z); + } + + {b>>>=(3);k-=(3);} + + mode = CODES; + break; + case 2: // dynamic + + {b>>>=(3);k-=(3);} + + mode = TABLE; + break; + case 3: // illegal + + {b>>>=(3);k-=(3);} + mode = BAD; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + break; + case LENS: + + while(k<(32)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>> 16) & 0xffff) != (b & 0xffff)){ + mode = BAD; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); + break; + case STORED: + if (n == 0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + if(m==0){ + if(q==end&&read!=0){ + q=0; m=(int)(qn) t = n; + if(t>m) t = m; + System.arraycopy(z.next_in, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last!=0 ? DRY : TYPE; + break; + case TABLE: + + while(k<(14)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)< 29 || ((t >> 5) & 0x1f) > 29) + { + mode = BAD; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if(blens==null || blens.length>>=(14);k-=(14);} + + index = 0; + mode = BTREE; + case BTREE: + while (index < 4 + (table >>> 10)){ + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(3);k-=(3);} + } + + while(index < 19){ + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK){ + r = t; + if (r == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + index = 0; + mode = DTREE; + case DTREE: + while (true){ + t = table; + if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ + break; + } + + int i, j, c; + + t = bb[0]; + + while(k<(t)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + blens[index++] = c; + } + else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while(k<(t+i)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + + j += (b & inflate_mask[i]); + + b>>>=(i);k-=(i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)){ + blens=null; + mode = BAD; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + c = c == 16 ? blens[i-1] : 0; + do{ + blens[i++] = c; + } + while (--j!=0); + index = i; + } + } + + tb[0]=-1; + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[] tl=new int[1]; + int[] td=new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, bl, bd, tl, td, hufts, z); + + if (t != Z_OK){ + if (t == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + r = t; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0], z); + } + mode = CODES; + case CODES: + bitb=b; bitk=k; + z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + + if ((r = codes.proc(this, z, r)) != Z_STREAM_END){ + return inflate_flush(z, r); + } + r = Z_OK; + codes.free(z); + + p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; + q=write;m=(int)(q z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy as far as end of window + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == end){ + // wrap pointers + q = 0; + if (write == end) + write = 0; + + // compute bytes to copy + n = write - q; + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + read = q; + + // done + return r; + } +} diff --git a/java_j2me/src/com/jcraft/jzlib/InfCodes.java b/java_j2me/src/com/jcraft/jzlib/InfCodes.java new file mode 100644 index 00000000..8889d07a --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/InfCodes.java @@ -0,0 +1,604 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfCodes{ + + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + static final private int START=0; // x: set up for LEN + static final private int LEN=1; // i: get length/literal/eob next + static final private int LENEXT=2; // i: getting length extra (have base) + static final private int DIST=3; // i: get distance next + static final private int DISTEXT=4;// i: getting distance extra + static final private int COPY=5; // o: copying bytes in window, waiting for space + static final private int LIT=6; // o: got literal, waiting for output space + static final private int WASH=7; // o: got eob, possibly still output waiting + static final private int END=8; // x: got eob and all data flushed + static final private int BADCODE=9;// x: got error + + int mode; // current inflate_codes mode + + // mode dependent information + int len; + + int[] tree; // pointer into tree + int tree_index=0; + int need; // bits needed + + int lit; + + // if EXT or COPY, where and how much + int get; // bits to get for extra + int dist; // distance back to copy from + + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + int[] ltree; // literal/length/eob tree + int ltree_index; // literal/length/eob tree + int[] dtree; // distance tree + int dtree_index; // distance tree + + InfCodes(){ + } + void init(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index, ZStream z){ + mode=START; + lbits=(byte)bl; + dbits=(byte)bd; + ltree=tl; + ltree_index=tl_index; + dtree = td; + dtree_index=td_index; + tree=null; + } + + int proc(InfBlocks s, ZStream z, int r){ + int j; // temporary storage + int tindex; // temporary pointer + int e; // extra bits or operation + int b=0; // bit buffer + int k=0; // bits in bit buffer + int p=0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q= 258 && n >= 10){ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + r = inflate_fast(lbits, dbits, + ltree, ltree_index, + dtree, dtree_index, + s, z); + + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q>>=(tree[tindex+1]); + k-=(tree[tindex+1]); + + e=tree[tindex]; + + if(e == 0){ // literal + lit = tree[tindex+2]; + mode = LIT; + break; + } + if((e & 16)!=0 ){ // length + get = e & 15; + len = tree[tindex+2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3+tree[tindex+2]; + break; + } + if ((e & 32)!=0){ // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + need = dbits; + tree = dtree; + tree_index=dtree_index; + mode = DIST; + case DIST: // i: get distance next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=tree[tindex+1]; + k-=tree[tindex+1]; + + e = (tree[tindex]); + if((e & 16)!=0){ // distance + get = e & 15; + dist = tree[tindex+2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3 + tree[tindex+2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case DISTEXT: // i: getting distance extra + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + mode = COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while(f < 0){ // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len!=0){ + + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q 7){ // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++] = (byte)tp[tp_index_t_3+2]; + m--; + continue; + } + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + e &= 15; + c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); + + b>>=e; k-=e; + + // decode distance base of block to copy + while(k<(15)){ // max bits for distance code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + // get extra bits to add to distance base + e &= 15; + while(k<(e)){ // get extra bits (up to 13) + n--; + b|=(z.next_in[p++]&0xff)<>=(e); k-=(e); + + // do the copy + m -= c; + if (q >= d){ // offset before dest + // just copy + r=q-d; + if(q-r>0 && 2>(q-r)){ + s.window[q++]=s.window[r++]; // minimum count is three, + s.window[q++]=s.window[r++]; // so unroll loop a little + c-=2; + } + else{ + System.arraycopy(s.window, r, s.window, q, 2); + q+=2; r+=2; c-=2; + } + } + else{ // else offset after destination + r=q-d; + do{ + r+=s.end; // force pointer in window + }while(r<0); // covers invalid distances + e=s.end-r; + if(c>e){ // if source crosses, + c-=e; // wrapped copy + if(q-r>0 && e>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--e!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, e); + q+=e; r+=e; e=0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if(q-r>0 && c>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--c!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, c); + q+=c; r+=c; c=0; + } + break; + } + else if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + e=tp[tp_index_t_3]; + } + else{ + z.msg = "invalid distance code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + break; + } + + if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + if((e=tp[tp_index_t_3])==0){ + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++]=(byte)tp[tp_index_t_3+2]; + m--; + break; + } + } + else if((e&32)!=0){ + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_STREAM_END; + } + else{ + z.msg="invalid literal/length code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + } + while(m>=258 && n>= 10); + + // not enough input or output--restore pointers and return + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_OK; + } +} diff --git a/java_j2me/src/com/jcraft/jzlib/InfTree.java b/java_j2me/src/com/jcraft/jzlib/InfTree.java new file mode 100644 index 00000000..cbca4366 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/InfTree.java @@ -0,0 +1,520 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfTree{ + + static final private int MANY=1440; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final int fixed_bl = 9; + static final int fixed_bd = 5; + + static final int[] fixed_tl = { + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,192, + 80,7,10, 0,8,96, 0,8,32, 0,9,160, + 0,8,0, 0,8,128, 0,8,64, 0,9,224, + 80,7,6, 0,8,88, 0,8,24, 0,9,144, + 83,7,59, 0,8,120, 0,8,56, 0,9,208, + 81,7,17, 0,8,104, 0,8,40, 0,9,176, + 0,8,8, 0,8,136, 0,8,72, 0,9,240, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,200, + 81,7,13, 0,8,100, 0,8,36, 0,9,168, + 0,8,4, 0,8,132, 0,8,68, 0,9,232, + 80,7,8, 0,8,92, 0,8,28, 0,9,152, + 84,7,83, 0,8,124, 0,8,60, 0,9,216, + 82,7,23, 0,8,108, 0,8,44, 0,9,184, + 0,8,12, 0,8,140, 0,8,76, 0,9,248, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,196, + 81,7,11, 0,8,98, 0,8,34, 0,9,164, + 0,8,2, 0,8,130, 0,8,66, 0,9,228, + 80,7,7, 0,8,90, 0,8,26, 0,9,148, + 84,7,67, 0,8,122, 0,8,58, 0,9,212, + 82,7,19, 0,8,106, 0,8,42, 0,9,180, + 0,8,10, 0,8,138, 0,8,74, 0,9,244, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,204, + 81,7,15, 0,8,102, 0,8,38, 0,9,172, + 0,8,6, 0,8,134, 0,8,70, 0,9,236, + 80,7,9, 0,8,94, 0,8,30, 0,9,156, + 84,7,99, 0,8,126, 0,8,62, 0,9,220, + 82,7,27, 0,8,110, 0,8,46, 0,9,188, + 0,8,14, 0,8,142, 0,8,78, 0,9,252, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,194, + 80,7,10, 0,8,97, 0,8,33, 0,9,162, + 0,8,1, 0,8,129, 0,8,65, 0,9,226, + 80,7,6, 0,8,89, 0,8,25, 0,9,146, + 83,7,59, 0,8,121, 0,8,57, 0,9,210, + 81,7,17, 0,8,105, 0,8,41, 0,9,178, + 0,8,9, 0,8,137, 0,8,73, 0,9,242, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,202, + 81,7,13, 0,8,101, 0,8,37, 0,9,170, + 0,8,5, 0,8,133, 0,8,69, 0,9,234, + 80,7,8, 0,8,93, 0,8,29, 0,9,154, + 84,7,83, 0,8,125, 0,8,61, 0,9,218, + 82,7,23, 0,8,109, 0,8,45, 0,9,186, + 0,8,13, 0,8,141, 0,8,77, 0,9,250, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,198, + 81,7,11, 0,8,99, 0,8,35, 0,9,166, + 0,8,3, 0,8,131, 0,8,67, 0,9,230, + 80,7,7, 0,8,91, 0,8,27, 0,9,150, + 84,7,67, 0,8,123, 0,8,59, 0,9,214, + 82,7,19, 0,8,107, 0,8,43, 0,9,182, + 0,8,11, 0,8,139, 0,8,75, 0,9,246, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,206, + 81,7,15, 0,8,103, 0,8,39, 0,9,174, + 0,8,7, 0,8,135, 0,8,71, 0,9,238, + 80,7,9, 0,8,95, 0,8,31, 0,9,158, + 84,7,99, 0,8,127, 0,8,63, 0,9,222, + 82,7,27, 0,8,111, 0,8,47, 0,9,190, + 0,8,15, 0,8,143, 0,8,79, 0,9,254, + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,193, + + 80,7,10, 0,8,96, 0,8,32, 0,9,161, + 0,8,0, 0,8,128, 0,8,64, 0,9,225, + 80,7,6, 0,8,88, 0,8,24, 0,9,145, + 83,7,59, 0,8,120, 0,8,56, 0,9,209, + 81,7,17, 0,8,104, 0,8,40, 0,9,177, + 0,8,8, 0,8,136, 0,8,72, 0,9,241, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,201, + 81,7,13, 0,8,100, 0,8,36, 0,9,169, + 0,8,4, 0,8,132, 0,8,68, 0,9,233, + 80,7,8, 0,8,92, 0,8,28, 0,9,153, + 84,7,83, 0,8,124, 0,8,60, 0,9,217, + 82,7,23, 0,8,108, 0,8,44, 0,9,185, + 0,8,12, 0,8,140, 0,8,76, 0,9,249, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,197, + 81,7,11, 0,8,98, 0,8,34, 0,9,165, + 0,8,2, 0,8,130, 0,8,66, 0,9,229, + 80,7,7, 0,8,90, 0,8,26, 0,9,149, + 84,7,67, 0,8,122, 0,8,58, 0,9,213, + 82,7,19, 0,8,106, 0,8,42, 0,9,181, + 0,8,10, 0,8,138, 0,8,74, 0,9,245, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,205, + 81,7,15, 0,8,102, 0,8,38, 0,9,173, + 0,8,6, 0,8,134, 0,8,70, 0,9,237, + 80,7,9, 0,8,94, 0,8,30, 0,9,157, + 84,7,99, 0,8,126, 0,8,62, 0,9,221, + 82,7,27, 0,8,110, 0,8,46, 0,9,189, + 0,8,14, 0,8,142, 0,8,78, 0,9,253, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,195, + 80,7,10, 0,8,97, 0,8,33, 0,9,163, + 0,8,1, 0,8,129, 0,8,65, 0,9,227, + 80,7,6, 0,8,89, 0,8,25, 0,9,147, + 83,7,59, 0,8,121, 0,8,57, 0,9,211, + 81,7,17, 0,8,105, 0,8,41, 0,9,179, + 0,8,9, 0,8,137, 0,8,73, 0,9,243, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,203, + 81,7,13, 0,8,101, 0,8,37, 0,9,171, + 0,8,5, 0,8,133, 0,8,69, 0,9,235, + 80,7,8, 0,8,93, 0,8,29, 0,9,155, + 84,7,83, 0,8,125, 0,8,61, 0,9,219, + 82,7,23, 0,8,109, 0,8,45, 0,9,187, + 0,8,13, 0,8,141, 0,8,77, 0,9,251, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,199, + 81,7,11, 0,8,99, 0,8,35, 0,9,167, + 0,8,3, 0,8,131, 0,8,67, 0,9,231, + 80,7,7, 0,8,91, 0,8,27, 0,9,151, + 84,7,67, 0,8,123, 0,8,59, 0,9,215, + 82,7,19, 0,8,107, 0,8,43, 0,9,183, + 0,8,11, 0,8,139, 0,8,75, 0,9,247, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,207, + 81,7,15, 0,8,103, 0,8,39, 0,9,175, + 0,8,7, 0,8,135, 0,8,71, 0,9,239, + 80,7,9, 0,8,95, 0,8,31, 0,9,159, + 84,7,99, 0,8,127, 0,8,63, 0,9,223, + 82,7,27, 0,8,111, 0,8,47, 0,9,191, + 0,8,15, 0,8,143, 0,8,79, 0,9,255 + }; + static final int[] fixed_td = { + 80,5,1, 87,5,257, 83,5,17, 91,5,4097, + 81,5,5, 89,5,1025, 85,5,65, 93,5,16385, + 80,5,3, 88,5,513, 84,5,33, 92,5,8193, + 82,5,9, 90,5,2049, 86,5,129, 192,5,24577, + 80,5,2, 87,5,385, 83,5,25, 91,5,6145, + 81,5,7, 89,5,1537, 85,5,97, 93,5,24577, + 80,5,4, 88,5,769, 84,5,49, 92,5,12289, + 82,5,13, 90,5,3073, 86,5,193, 192,5,24577 + }; + + // Tables for deflate from PKZIP's appnote.txt. + static final int[] cplens = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + }; + + // see note #13 above about 258 + static final int[] cplext = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + }; + + static final int[] cpdist = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + static final int[] cpdext = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + static final int BMAX=15; // maximum bit length of any code + + int[] hn = null; // hufts used in space + int[] v = null; // work area for huft_build + int[] c = null; // bit length count table + int[] r = null; // table entry for structure assignment + int[] u = null; // table stack + int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX) + int bindex, + int n, // number of codes (assumed <= 288) + int s, // number of simple-valued codes (0..s-1) + int[] d, // list of base values for non-simple codes + int[] e, // list of extra bits for non-simple codes + int[] t, // result: starting table + int[] m, // maximum lookup bits, returns actual + int[] hp,// space for trees + int[] hn,// hufts used in space + int[] v // working area: values in order of bit length + ){ + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do { + c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX + }while(i!=0); + + if(c[0] == n){ // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if(c[j]!=0) break; + k = j; // minimum code length + if(l < j){ + l = j; + } + for (i = BMAX; i!=0; i--){ + if(c[i]!=0) break; + } + g = i; // maximum code length + if(l > i){ + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1){ + if ((y -= c[j]) < 0){ + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0){ + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i!=0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do { + if ((j = b[bindex+p]) != 0){ + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++){ + a = c[k]; + while (a--!=0){ + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l){ + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if((f=1<<(j=k-w))>a+1){ // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if(j < z){ + while (++j < z){ // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY){ // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /*hp+*/ hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if(h!=0){ + x[h]=i; // save pattern for backing up + r[0]=(byte)j; // bits in this table + r[1]=(byte)l; // bits to dump before this table + j=i>>>(w - l); + r[2] = (int)(q - u[h-1] - j); // offset to this table + System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table + } + else{ + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (byte)(k - w); + if (p >= n){ + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s){ + r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else{ + r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists + r[2]=d[v[p++] - s]; + } + + // fill code-like entries with r + f=1<<(k-w); + for (j=i>>>w;j>>= 1){ + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]){ + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + int inflate_trees_bits(int[] c, // 19 code lengths + int[] bb, // bits tree desired/actual depth + int[] tb, // bits tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + initWorkArea(19); + hn[0]=0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed dynamic bit lengths tree"; + } + else if(result == Z_BUF_ERROR || bb[0] == 0){ + z.msg = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + int inflate_trees_dynamic(int nl, // number of literal/length codes + int nd, // number of distance codes + int[] c, // that many (total) code lengths + int[] bl, // literal desired/actual bit depth + int[] bd, // distance desired/actual bit depth + int[] tl, // literal/length tree result + int[] td, // distance tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + + // build literal/length tree + initWorkArea(288); + hn[0]=0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0){ + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR){ + z.msg = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)){ + if (result == Z_DATA_ERROR){ + z.msg = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR){ + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth + int[] bd, //distance desired/actual bit depth + int[][] tl,//literal/length tree result + int[][] td,//distance tree result + ZStream z //for memory allocation + ){ + bl[0]=fixed_bl; + bd[0]=fixed_bd; + tl[0]=fixed_tl; + td[0]=fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize){ + if(hn==null){ + hn=new int[1]; + v=new int[vsize]; + c=new int[BMAX+1]; + r=new int[3]; + u=new int[BMAX]; + x=new int[BMAX+1]; + } + if(v.lengthstate); + return Z_OK; + } + + int inflateInit(ZStream z, int w){ + z.msg = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + nowrap = 0; + if(w < 0){ + w = - w; + nowrap = 1; + } + + // set window size + if(w<8 ||w>15){ + inflateEnd(z); + return Z_STREAM_ERROR; + } + wbits=w; + + z.istate.blocks=new InfBlocks(z, + z.istate.nowrap!=0 ? null : this, + 1<>4)+8>z.istate.wbits){ + z.istate.mode = BAD; + z.msg="invalid window size"; + z.istate.marker = 5; // can't try inflateSync + break; + } + z.istate.mode=FLAG; + case FLAG: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + b = (z.next_in[z.next_in_index++])&0xff; + + if((((z.istate.method << 8)+b) % 31)!=0){ + z.istate.mode = BAD; + z.msg = "incorrect header check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + if((b&PRESET_DICT)==0){ + z.istate.mode = BLOCKS; + break; + } + z.istate.mode = DICT4; + case DICT4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=DICT3; + case DICT3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode=DICT2; + case DICT2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode=DICT1; + case DICT1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need += (z.next_in[z.next_in_index++]&0xffL); + z.adler = z.istate.need; + z.istate.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z.istate.mode = BAD; + z.msg = "need dictionary"; + z.istate.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + + r = z.istate.blocks.proc(z, r); + if(r == Z_DATA_ERROR){ + z.istate.mode = BAD; + z.istate.marker = 0; // can try inflateSync + break; + } + if(r == Z_OK){ + r = f; + } + if(r != Z_STREAM_END){ + return r; + } + r = f; + z.istate.blocks.reset(z, z.istate.was); + if(z.istate.nowrap!=0){ + z.istate.mode=DONE; + break; + } + z.istate.mode=CHECK4; + case CHECK4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=CHECK3; + case CHECK3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode = CHECK2; + case CHECK2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode = CHECK1; + case CHECK1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=(z.next_in[z.next_in_index++]&0xffL); + + if(((int)(z.istate.was[0])) != ((int)(z.istate.need))){ + z.istate.mode = BAD; + z.msg = "incorrect data check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + z.istate.mode = DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } + } + } + + + int inflateSetDictionary(ZStream z, byte[] dictionary, int dictLength){ + int index=0; + int length = dictLength; + if(z==null || z.istate == null|| z.istate.mode != DICT0) + return Z_STREAM_ERROR; + + if(z._adler.adler32(1L, dictionary, 0, dictLength)!=z.adler){ + return Z_DATA_ERROR; + } + + z.adler = z._adler.adler32(0, null, 0, 0); + + if(length >= (1<>>7)]); + } + + short[] dyn_tree; // the dynamic tree + int max_code; // largest code with non zero frequency + StaticTree stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + void gen_bitlen(Deflate s){ + short[] tree = dyn_tree; + short[] stree = stat_desc.static_tree; + int[] extra = stat_desc.extra_bits; + int base = stat_desc.extra_base; + int max_length = stat_desc.max_length; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap + + for(h=s.heap_max+1; h max_length){ bits = max_length; overflow++; } + tree[n*2+1] = (short)bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n*2]; + s.opt_len += f * (bits + xbits); + if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); + } + if (overflow == 0) return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) { + n = s.bl_count[bits]; + while (n != 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m*2+1] != bits) { + s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2]; + tree[m*2+1] = (short)bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + void build_tree(Deflate s){ + short[] tree=dyn_tree; + short[] stree=stat_desc.static_tree; + int elems=stat_desc.elems; + int n, m; // iterate over heap elements + int max_code=-1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for(n=0; n=1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node=elems; // next internal node of the tree + do{ + // n = node of least frequency + n=s.heap[1]; + s.heap[1]=s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m=s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node*2] = (short)(tree[n*2] + tree[m*2]); + s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1); + tree[n*2+1] = tree[m*2+1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while(s.heap_len>=2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + static void gen_codes(short[] tree, // the tree to decorate + int max_code, // largest code with non zero frequency + short[] bl_count // number of codes at each bit length + ){ + short[] next_code=new short[MAX_BITS+1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>>=1; + res<<=1; + } + while(--len>0); + return res>>>1; + } +} + diff --git a/java_j2me/src/com/jcraft/jzlib/ZInputStream.java b/java_j2me/src/com/jcraft/jzlib/ZInputStream.java new file mode 100644 index 00000000..d94c80c8 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/ZInputStream.java @@ -0,0 +1,150 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2001 Lapo Luchini. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS +OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; +import java.io.*; +import com.jcraft.jzlib.j2me.*; + +public class ZInputStream extends FilterInputStream { + + protected ZStream z=new ZStream(); + protected int bufsize=512; + protected int flush=JZlib.Z_NO_FLUSH; + protected byte[] buf=new byte[bufsize], + buf1=new byte[1]; + protected boolean compress; + + protected InputStream in=null; + + public ZInputStream(InputStream in) { + this(in, false); + } + public ZInputStream(InputStream in, boolean nowrap) { + super(in); + this.in=in; + z.inflateInit(nowrap); + compress=false; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + public ZInputStream(InputStream in, int level) { + super(in); + this.in=in; + z.deflateInit(level); + compress=true; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + /*public int available() throws IOException { + return inf.finished() ? 0 : 1; + }*/ + + public int read() throws IOException { + if(read(buf1, 0, 1)==-1) + return(-1); + return(buf1[0]&0xFF); + } + + private boolean nomoreinput=false; + + public int read(byte[] b, int off, int len) throws IOException { + if(len==0) + return(0); + int err; + z.next_out=b; + z.next_out_index=off; + z.avail_out=len; + do { + if((z.avail_in==0)&&(!nomoreinput)) { // if buffer is empty and more input is avaiable, refill it + z.next_in_index=0; + z.avail_in=in.read(buf, 0, bufsize);//(bufsize0 || z.avail_out==0); + } + + public int getFlushMode() { + return(flush); + } + + public void setFlushMode(int flush) { + this.flush=flush; + } + + public void finish() throws IOException { + int err; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=bufsize; + if(compress){ err=z.deflate(JZlib.Z_FINISH); } + else{ err=z.inflate(JZlib.Z_FINISH); } + if(err!=JZlib.Z_STREAM_END && err != JZlib.Z_OK) + throw new ZStreamException((compress?"de":"in")+"flating: "+z.msg); + if(bufsize-z.avail_out>0){ + out.write(buf, 0, bufsize-z.avail_out); + } + } + while(z.avail_in>0 || z.avail_out==0); + flush(); + } + public void end() { + if(z==null) + return; + if(compress){ z.deflateEnd(); } + else{ z.inflateEnd(); } + z.free(); + z=null; + } + public void close() throws IOException { + try{ + try{finish();} + catch (IOException ignored) {} + } + finally{ + end(); + out.close(); + out=null; + } + } + + /** + * Returns the total number of bytes input so far. + */ + public long getTotalIn() { + return z.total_in; + } + + /** + * Returns the total number of bytes output so far. + */ + public long getTotalOut() { + return z.total_out; + } + + public void flush() throws IOException { + out.flush(); + } + +} diff --git a/java_j2me/src/com/jcraft/jzlib/ZStream.java b/java_j2me/src/com/jcraft/jzlib/ZStream.java new file mode 100644 index 00000000..334475e9 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/ZStream.java @@ -0,0 +1,211 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + public byte[] next_in; // next input byte + public int next_in_index; + public int avail_in; // number of bytes available at next_in + public long total_in; // total nb of input bytes read so far + + public byte[] next_out; // next output byte should be put there + public int next_out_index; + public int avail_out; // remaining free space at next_out + public long total_out; // total nb of bytes output so far + + public String msg; + + Deflate dstate; + Inflate istate; + + int data_type; // best guess about the data type: ascii or binary + + public long adler; + Adler32 _adler=new Adler32(); + + public int inflateInit(){ + return inflateInit(DEF_WBITS); + } + public int inflateInit(boolean nowrap){ + return inflateInit(DEF_WBITS, nowrap); + } + public int inflateInit(int w){ + return inflateInit(w, false); + } + + public int inflateInit(int w, boolean nowrap){ + istate=new Inflate(); + return istate.inflateInit(this, nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + return istate.inflate(this, f); + } + public int inflateEnd(){ + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(this); + istate = null; + return ret; + } + public int inflateSync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(this); + } + public int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(this, dictionary, dictLength); + } + + public int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + public int deflateInit(int level, boolean nowrap){ + return deflateInit(level, MAX_WBITS, nowrap); + } + public int deflateInit(int level, int bits){ + return deflateInit(level, bits, false); + } + public int deflateInit(int level, int bits, boolean nowrap){ + dstate=new Deflate(); + return dstate.deflateInit(this, level, nowrap?-bits:bits); + } + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + return dstate.deflate(this, flush); + } + public int deflateEnd(){ + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + return ret; + } + public int deflateParams(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(this, level, strategy); + } + public int deflateSetDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(this, dictionary, dictLength); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + void flush_pending(){ + int len=dstate.pending; + + if(len>avail_out) len=avail_out; + if(len==0) return; + + if(dstate.pending_buf.length<=dstate.pending_out || + next_out.length<=next_out_index || + dstate.pending_buf.length<(dstate.pending_out+len) || + next_out.length<(next_out_index+len)){ + System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+ + ", "+next_out.length+", "+next_out_index+", "+len); + System.out.println("avail_out="+avail_out); + } + + System.arraycopy(dstate.pending_buf, dstate.pending_out, + next_out, next_out_index, len); + + next_out_index+=len; + dstate.pending_out+=len; + total_out+=len; + avail_out-=len; + dstate.pending-=len; + if(dstate.pending==0){ + dstate.pending_out=0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + int read_buf(byte[] buf, int start, int size) { + int len=avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + avail_in-=len; + + if(dstate.noheader==0) { + adler=_adler.adler32(adler, next_in, next_in_index, len); + } + System.arraycopy(next_in, next_in_index, buf, start, len); + next_in_index += len; + total_in += len; + return len; + } + + public void free(){ + next_in=null; + next_out=null; + msg=null; + _adler=null; + } +} diff --git a/java_j2me/src/com/jcraft/jzlib/ZStreamException.java b/java_j2me/src/com/jcraft/jzlib/ZStreamException.java new file mode 100644 index 00000000..9f5cb0af --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/ZStreamException.java @@ -0,0 +1,46 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class ZStreamException extends java.io.IOException { + public ZStreamException() { + super(); + } + public ZStreamException(String s) { + super(s); + } + + private static final long serialVersionUID = 0; +} diff --git a/java_j2me/src/com/jcraft/jzlib/j2me/FilterInputStream.java b/java_j2me/src/com/jcraft/jzlib/j2me/FilterInputStream.java new file mode 100644 index 00000000..1beda813 --- /dev/null +++ b/java_j2me/src/com/jcraft/jzlib/j2me/FilterInputStream.java @@ -0,0 +1,51 @@ +package com.jcraft.jzlib.j2me; + +import java.io.*; + +/** + * FilterInputStream re-implementation. + * Allows the building and use of jzlib on J2ME. + */ +public class FilterInputStream extends InputStream { + protected InputStream in; + + protected FilterInputStream(InputStream in) { + this.in = in; + } + + public int read() throws IOException { + return in.read(); + } + + public int read(byte[] b) throws IOException { + return in.read(b); + } + + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + + public long skip(long n) throws IOException { + return in.skip(n); + } + + public int available() throws IOException { + return in.available(); + } + + public void close() throws IOException { + in.close(); + } + + public void mark(int readlimit) { + in.mark(readlimit); + } + + public void reset() throws IOException { + in.reset(); + } + + public boolean markSupported() { + return in.markSupported(); + } +} \ No newline at end of file diff --git a/java_j2me/src/com/lcs/micromuscle/client/MessageTransceiver.java b/java_j2me/src/com/lcs/micromuscle/client/MessageTransceiver.java new file mode 100644 index 00000000..07cfeb74 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/client/MessageTransceiver.java @@ -0,0 +1,448 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.client; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import javax.microedition.io.StreamConnection; +import javax.microedition.io.ServerSocketConnection; +import javax.microedition.io.Connector; + +import com.meyer.micromuscle.iogateway.AbstractMessageIOGateway; +import com.meyer.micromuscle.iogateway.MessageIOGatewayFactory; +import com.meyer.micromuscle.message.Message; +import com.meyer.micromuscle.support.LEDataInputStream; +import com.meyer.micromuscle.support.LEDataOutputStream; +import com.meyer.micromuscle.thread.MessageListener; +import com.meyer.micromuscle.thread.MessageQueue; + +/** This class is a handy utility class that acts as + * an interface to a TCP socket to send and receive + * Messages over. + */ +public class MessageTransceiver implements MessageListener +{ + /** Creates a MessageTransceiver that will send replies to the given queue, + * using the provided outgoing compression level. + * @param repliesTo where to send status & reply messages + * @param compressionLevel the outgoing compression level to use + */ + public MessageTransceiver(MessageQueue repliesTo, int compressionLevel) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(compressionLevel); + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Creates a MessageTransceiver that will send replies to the given queue. + * @param repliesTo where to send status & reply messages + */ + public MessageTransceiver(MessageQueue repliesTo) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(); // default protocol, default compression + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Start up a transceiver that automatically uses the specified socket. + * @param repliesTo where to send status & reply messages + * @param s pre-bound socket to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + * @param compressionLevel the outgoing compression level to use. + */ + public MessageTransceiver(MessageQueue repliesTo, StreamConnection s, Object successTag, Object failTag, int compressionLevel) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(compressionLevel); + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Start up a transceiver that automatically uses the specified socket. + * @param repliesTo where to send status & reply messages + * @param s pre-bound socket to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + * @author Bryan Varner + */ + public MessageTransceiver(MessageQueue repliesTo, StreamConnection s, Object successTag, Object failTag) + { + _repliesTo = repliesTo; + _gateway = MessageIOGatewayFactory.getMessageIOGateway(); + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Creates a MessageTransceiver that will send replies to the given queue, and + * use the given IO gateway for I/O purposes. + * @param repliesTo where to send status & reply messages + * @param ioGateway the AbstractMessageIOGateway to use to flatten and unflatten Messages. + */ + public MessageTransceiver(MessageQueue repliesTo, AbstractMessageIOGateway ioGateway) + { + _repliesTo = repliesTo; + _gateway = ioGateway; + _sendQueue = new MessageQueue(new MessageTransmitter()); + } + + /** Start up a transceiver that automatically connects to the specified socket. + * @param repliesTo where to send status & reply messages + * @param ioGateway the AbstractMessageIOGateway to use to flatten and unflatten Messages. + * @param s pre-bound socket to run the MessageTransceiver on. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + * @author Bryan Varner + */ + public MessageTransceiver(MessageQueue repliesTo, AbstractMessageIOGateway ioGateway, StreamConnection s, Object successTag, Object failTag) + { + _repliesTo = repliesTo; + _gateway = ioGateway; + _sendQueue = new MessageQueue(new MessageTransmitter(s, successTag, failTag)); + } + + /** Tells the internal thread to connect to the given host and port + * Connection is done asynchronously, and an OperationCompleteMessage + * will be sent to the repliesTo queue when it has been done. + * @param hostName IP address or host name of computer to connect to + * @param port Port number to connect to (2960 is the port for a MUSCLE server) + * @param successTag Any value you want--if non-null, this will be sent back to indicate that the connection succeeded. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection failed or was broken. + */ + public void connect(String hostName, int port, Object successTag, Object failTag) + { + _sendQueue.postMessage(new ConnectMessage(hostName, port, successTag, failTag)); + } + + /** Tells the internal thread to listen on the given port. + * Connection is done asynchronoulsy. + * With this you will receive multiple copies of successTag. The first signals that we have bound to the port. + * If continually listening, this is all you will receive. However, if you are doing a one-shot listen(), the + * second successTag will signify that the connection has been established. + * @param s A constructed ServerSocket that has already properly bound to it's port. + * @param successTag Any value you want--if non-null, this will be sent back to indicate that a new connection has been established. + * @param failTag Any value you want--if non-null, this will be sent back to indicate that the connection has been broken. (must connect first, we're listening) + * @param continualyListen If true, the listen thread will continue to accept sockets until the MessageTranciever dies. If false, only a single connection is accepted. + * @author Bryan Varner + */ + public void listen(ServerSocketConnection s, Object successTag, Object failTag, boolean continuallyListen) + { + _continuallyListen = continuallyListen; + _sendQueue.postMessage(new ConnectMessage(s, successTag, failTag)); + } + + /** Forces the transceiver to stop listening immediately if it is in a mode to continually listen. + * Of course, this is asynchronous. + * @author Bryan Varner + */ + public void stopListening() + { + _continuallyListen = true; + if (_currentConnectThread instanceof ListenThread) _currentConnectThread.interrupt(); + } + + /** Tells the internal thread to disconnect. This will be done asynchronously. + * When the disconnection is done, the failTag specified previously in the connect() + * call will be sent to your reply queue. + */ + public void disconnect() + { + _sendQueue.postMessage(new ConnectMessage(null, 0, null, null)); + } + + /** Tells the internal thread to send a Message out over the TCP socket. + * This will be done asynchronously. + */ + public void sendOutgoingMessage(Message msg) + { + _sendQueue.postMessage(msg); + } + + /** + * Set a tag that will be sent whenever our queue of outgoing Messages + * becomes empty. (Default is NULL, meaning that no notifications will be sent) + * @param tag The notification tag to send, or NULL to disable. + */ + public void setNotifyOutputQueueDrainedTag(Object tag) + { + _outputQueueDrainedTag = tag; + } + + /** Any received Messages will be sent out to the TCP socket */ + public void messageReceived(Object msg, int numLeft) + { + if (msg instanceof Message) sendOutgoingMessage((Message)msg); + } + + /** Internal control class */ + private final class ConnectMessage + { + public ConnectMessage(String hostName, int port, Object successTag, Object failTag) + { + _hostName = hostName; + _port = port; + _successTag = successTag; + _failTag = failTag; + _sock = null; + } + + public ConnectMessage(ServerSocketConnection s, Object successTag, Object failTag) + { + _hostName = null; + _port = 0; + _successTag = successTag; + _failTag = failTag; + _sock = s; + } + + public String _hostName; + public int _port; + public Object _successTag; + public Object _failTag; + public ServerSocketConnection _sock; + } + + /** Logic for the connecting-and-sending thread */ + private final class MessageTransmitter implements MessageListener + { + public MessageTransmitter() + { + // empty + } + + public MessageTransmitter(StreamConnection s, Object successTag, Object failTag) + { + _failTag = failTag; + _currentConnectThread = null; // no longer in the connect period + if (s != null) + { + try { + _socket = s; + _out = new LEDataOutputStream(_socket.openDataOutputStream()); + new MessageReceiver(new LEDataInputStream(_socket.openDataInputStream()), _connectionID); // start the read thread + sendReply(successTag); // ok, done! + } + catch(IOException ex) { + disconnect(_connectionID); + } + } + else sendReply(_failTag); + } + + public void messageReceived(Object message, int numLeft) + { + if (message instanceof Message) + { + if (_out != null) + { + try { + _gateway.flattenMessage(_out, (Message) message); + } + catch(IOException ex) { + disconnect(_connectionID); + } + } + } + else if (message instanceof ConnectMessage) + { + disconnect(_connectionID); // make sure our previous connection is gone + + ConnectMessage cmsg = (ConnectMessage) message; + if (cmsg._hostName != null) + { + // Do the actual connecting in Yet Another Thread, so that we can remain + // responsive to the user's requests during the (possibly lengthy) connect period. + _currentConnectThread = new ConnectThread(cmsg); // connect thread is started in ctor + } + else if (cmsg._sock != null) + { + // If a socket was passed in, we know we need to listen! + _currentConnectThread = new ListenThread(cmsg); + } + } + else if (message instanceof ConnectThread) + { + // When our ConnectThread has finished, he sends himself back to us with the connect results. + ConnectThread ct = (ConnectThread) message; + StreamConnection s = ct._socket; + ConnectMessage cmsg = ct._cmsg; + if (ct == _currentConnectThread) + { + _currentConnectThread = null; // no longer in the connect period + if (s != null) + { + try { + _socket = s; + _failTag = cmsg._failTag; + _out = new LEDataOutputStream(_socket.openDataOutputStream()); + new MessageReceiver(new LEDataInputStream(_socket.openDataInputStream()), _connectionID); // start the read thread + sendReply(cmsg._successTag); // ok, done! + } + catch(IOException ex) { + disconnect(_connectionID); + } + } + else sendReply(cmsg._failTag); + } + else + { + // oops, no use for the socket, so just close it and forget about it + if (s != null) try {s.close();} catch(Exception ex) {/* don't care */} + sendReply(cmsg._failTag); + } + } + else if (message instanceof MessageReceiver) disconnect(((MessageReceiver)message).connectionID()); + + if (numLeft == 0) + { + if (_out != null) + { + try { + _out.flush(); + } + catch(IOException ex) { + ex.printStackTrace(); + } + } + if (_outputQueueDrainedTag != null) sendReply(_outputQueueDrainedTag); + } + } + + /** Close the connection and notify the user (only if the connection ID is correct) */ + private void disconnect(int id) + { + if ((_socket != null)&&(_connectionID == id)) + { + try { + _out.close(); + _socket.close(); + } + catch(IOException ex) { + // ignore + } + sendReply(_failTag); + _out = null; + _socket = null; + _failTag = null; + _currentConnectThread = null; + _connectionID++; + } + } + + /** Send a message back to the user */ + private void sendReply(Object replyObj) + { + if ((_repliesTo != null)&&(replyObj != null)) _repliesTo.postMessage(replyObj); + } + + /** Logic for our receive-incoming-data thread */ + private class MessageReceiver implements MessageListener + { + public MessageReceiver(DataInput in, int cid) + { + _in = in; + _connectionID = cid; + (new MessageQueue(this)).postMessage(null); // start the background reader thread + } + + public void messageReceived(Object msg, int numLeft) + { + try { + while(true) sendReply(_gateway.unflattenMessage(_in)); + } + catch(IOException ex) { + // oops, socket broke... notify out master that we're leaving (by posting ourself as a message, teehee) + _sendQueue.postMessage(this); + } + } + + public int connectionID() {return _connectionID;} + + private int _connectionID; + private DataInput _in; + } + + private Object _failTag = null; + private StreamConnection _socket = null; + private LEDataOutputStream _out = null; + } + + // A little class for doing TCP connects in a separate thread (so the MessageTransceiver won't block during the connect) */ + private class ConnectThread implements MessageListener + { + private MessageQueue myQueue = null; + + public ConnectThread(ConnectMessage cmsg) + { + _cmsg = cmsg; + myQueue = new MessageQueue(this); + myQueue.postMessage(null); // just to kick off the thread + } + + public void messageReceived(Object obj, int numLeft) + { + try { + _socket = (StreamConnection)Connector.open("socket://" + _cmsg._hostName + ":" + _cmsg._port); + } + catch(IOException ex) { + // do nothing + } + _sendQueue.postMessage(this); + } + + public Runnable getThread(){ + return myQueue; + } + + public void interrupt(){ + myQueue.interrupt(); + } + + public StreamConnection _socket = null; + public ConnectMessage _cmsg; + } + + // A little class for listening for TCP connections in a separate thread. -- Author: Bryan Varner + // We override the listener method of ConnectThread and block for any incoming connections to said port. + private class ListenThread extends ConnectThread + { + public ListenThread(ConnectMessage cmsg) + { + super(cmsg); + } + + public void messageReceived(Object obj, int numLeft) + { + ServerSocketConnection servSock = _cmsg._sock; + + try { + while(true) { + // At this point we know we want at least one connection, we'll test for continuance at the end. + _socket = servSock.acceptAndOpen(); + + if (_continuallyListen == false) + { + // If we aren't supposed to keep listening, post this object, killing this thread. + _sendQueue.postMessage(this); + } + else + { + // If we are supposed to keep goin, send the new socket back to the object that started the transceiver, and let them deal with it. + if (_repliesTo != null) _repliesTo.postMessage(_socket); + } + } + } catch (IOException ex){ + // You're getting the preverbial screw. Hope you're enjoying it. + } + } + } + + private AbstractMessageIOGateway _gateway; + private MessageQueue _repliesTo; + private MessageQueue _sendQueue; + private int _connectionID = 0; + private ConnectThread _currentConnectThread = null; // non-null if we are connecting or listening (in a slave thread) + private boolean _continuallyListen = false; + private Object _outputQueueDrainedTag = null; +} diff --git a/java_j2me/src/com/lcs/micromuscle/client/StorageReflectConstants.java b/java_j2me/src/com/lcs/micromuscle/client/StorageReflectConstants.java new file mode 100644 index 00000000..b0e88594 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/client/StorageReflectConstants.java @@ -0,0 +1,487 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.client; + +/** Constants that are understood by the StorageReflectSession class of the standard MUSCLE server + * See documentation or comments at the end of this file for what they all mean + */ +public interface StorageReflectConstants +{ + /** Unused dummy value ('!Pc0'), marks the beginning of the reserved command array */ + public static final int BEGIN_PR_COMMANDS = 558916400; + + /** Adds/replaces the given fields in the parameters table */ + public static final int PR_COMMAND_SETPARAMETERS = 558916401; + + /** Returns the current parameter set to the client */ + public static final int PR_COMMAND_GETPARAMETERS = 558916402; + + /** deletes the parameters specified in PR_NAME_KEYS */ + public static final int PR_COMMAND_REMOVEPARAMETERS = 558916403; + + /** Adds/replaces the given message in the data table */ + public static final int PR_COMMAND_SETDATA = 558916404; + + /** Retrieves the given message(s) in the data table */ + public static final int PR_COMMAND_GETDATA = 558916405; + + /** Removes the gives message(s) from the data table */ + public static final int PR_COMMAND_REMOVEDATA = 558916406; + + /** Removes data from outgoing result messages */ + public static final int PR_COMMAND_JETTISONRESULTS = 558916407; + + /** Insert nodes underneath a node, as an ordered list */ + public static final int PR_COMMAND_INSERTORDEREDDATA = 558916408; + + /** Echo this message back to the sending client */ + public static final int PR_COMMAND_PING = 558916409; + + /** Kick matching clients off the server (Requires privilege) */ + public static final int PR_COMMAND_KICK = 558916410; + + /** Add ban patterns to the server's ban list (Requires privilege) */ + public static final int PR_COMMAND_ADDBANS = 558916411; + + /** Remove ban patterns from the server's ban list (Requires privilege) */ + public static final int PR_COMMAND_REMOVEBANS = 558916412; + + /** Submessages under PR_NAME_KEYS are executed in order, as if they came separately */ + public static final int PR_COMMAND_BATCH = 558916413; + + /** Server will ignore this message */ + public static final int PR_COMMAND_NOOP = 558916414; + + /** Move one or more intries in a node index to a different spot in the index */ + public static final int PR_COMMAND_REORDERDATA = 558916415; + + /** Add require patterns to the server's require list (Requires ban privilege) */ + public static final int PR_COMMAND_ADDREQUIRES = 558916416; + + /** Remove require patterns from the server's require list (Requires ban privilege) */ + public static final int PR_COMMAND_REMOVEREQUIRES = 558916417; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED11 = 558916418; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED12 = 558916419; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED13 = 558916420; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED14 = 558916421; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED15 = 558916422; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED16 = 558916423; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED17 = 558916424; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED18 = 558916425; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED19 = 558916426; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED20 = 558916427; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED21 = 558916428; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED22 = 558916429; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED23 = 558916430; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED24 = 558916431; + + /** Reserved for future expansion */ + public static final int PR_COMMAND_RESERVED25 = 558916432; + + /** Dummy value to indicate the end of the reserved command range */ + public static final int END_PR_COMMANDS = 558916433; + + /** Marks beginning of range of 'what' codes that may be generated by the StorageReflectSession and sent back to the client */ + public static final int BEGIN_PR_RESULTS = 558920240; + + /** Sent to client in response to PR_COMMAND_GETPARAMETERS */ + public static final int PR_RESULT_PARAMETERS = 558920241; + + /** Sent to client in response to PR_COMMAND_GETDATA, or subscriptions */ + public static final int PR_RESULT_DATAITEMS = 558920242; + + /** Sent to client to tell him that we don't know how to process his request message */ + public static final int PR_RESULT_ERRORUNIMPLEMENTED = 558920243; + + /** Notification that an entry has been inserted into an ordered index */ + public static final int PR_RESULT_INDEXUPDATED = 558920244; + + /** Response from a PR_COMMAND_PING message */ + public static final int PR_RESULT_PONG = 558920245; + + /** Your client isn't allowed to do something it tried to do */ + public static final int PR_RESULT_ERRORACCESSDENIED = 558920246; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED4 = 558920247; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED5 = 558920248; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED6 = 558920249; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED7 = 558920250; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED8 = 558920251; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED9 = 558920252; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED10 = 558920253; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED11 = 558920254; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED12 = 558920255; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED13 = 558920256; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED14 = 558920257; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED15 = 558920258; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED16 = 558920259; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED17 = 558920260; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED18 = 558920261; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED19 = 558920262; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED20 = 558920263; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED21 = 558920264; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED22 = 558920265; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED23 = 558920266; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED24 = 558920267; + + /** Reserved for future expansion */ + public static final int PR_RESULT_RESERVED25 = 558920268; + + /** Reserved for future expansion */ + public static final int END_PR_RESULTS = 558920269; + + /** Privilege bit, indicates that the client is allowed to kick other clients off the MUSCLE server */ + public static final int PR_PRIVILEGE_KICK = 0; + + /** Privilege bit, indicates that the client is allowed to ban other clients from the MUSCLE server */ + public static final int PR_PRIVILEGE_ADDBANS = 1; + + /** Privilege bit, indicates that the client is allowed to unban other clients from the MUSCLE server */ + public static final int PR_PRIVILEGE_REMOVEBANS = 2; + + /** Number of defined privilege bits */ + public static final int PR_NUM_PRIVILEGES = 3; + + /** Op-code that indicates that an entry was inserted at the given slot index, with the given ID */ + public static final char INDEX_OP_ENTRYINSERTED = 'i'; + + /** Op-code that indicates that an entry was removed from the given slot index, had the given ID */ + public static final char INDEX_OP_ENTRYREMOVED = 'r'; + + /** Op-code that indicates that the index was cleared */ + public static final char INDEX_OP_CLEARED = 'c'; + + /** Field name for key-strings (often node paths or regex expressions) */ + public static final String PR_NAME_KEYS = "!SnKy"; + + /** Field name to contains node path strings of removed data items */ + public static final String PR_NAME_REMOVED_DATAITEMS = "!SnRd"; + + /** Field name (any type): If present in a PR_COMMAND_SETPARAMETERS message, disables inital-value-send from new subscriptions */ + public static final String PR_NAME_SUBSCRIBE_QUIETLY = "!SnQs"; + + /** Field name (any type): If present in a PR_COMMAND_SETDATA message, subscribers won't be notified about the change. */ + public static final String PR_NAME_SET_QUIETLY = "!SnQ2"; + + /** Field name (any type): If present in a PR_COMMAND_REMOVEDATA message, subscribers won't be notified about the change. */ + public static final String PR_NAME_REMOVE_QUIETLY = "!SnQ3"; + + /** Field name (any type): If set as parameter, include ourself in wildcard matches */ + public static final String PR_NAME_REFLECT_TO_SELF = "!Self"; + + /** Field name (any type): If set as a parameter, disable all subscription update messaging. */ + public static final String PR_NAME_DISABLE_SUBSCRIPTIONS = "!Dsub"; + + /** Field name of int parameter; sets max # of items per PR_RESULT_DATAITEMS message */ + public static final String PR_NAME_MAX_UPDATE_MESSAGE_ITEMS = "!MxUp"; + + /** Field name of String returned in parameter set; contains this session's /host/sessionID path */ + public static final String PR_NAME_SESSION_ROOT = "!Root"; + + /** Field name for Message: In PR_RESULT_ERROR_* messages, holds the client's message that failed to execute. */ + public static final String PR_NAME_REJECTED_MESSAGE = "!Rjct"; + + /** Field name of int32 bitchord of client's PR_PRIVILEGE_* bits. */ + public static final String PR_NAME_PRIVILEGE_BITS = "!Priv"; + + /** Field name of int64 indicating how many more bytes are available for MUSCLE server to use */ + public static final String PR_NAME_SERVER_MEM_AVAILABLE = "!Mav"; + + /** Field name of int64 indicating how many bytes the MUSCLE server currently has allocated */ + public static final String PR_NAME_SERVER_MEM_USED = "!Mus"; + + /** Field name of int64 indicating how the maximum number of bytes the MUSCLE server may have allocated at once. */ + public static final String PR_NAME_SERVER_MEM_MAX = "!Mmx"; + + /** Field name of String indicating version of MUSCLE that the server was compiled from */ + public static final String PR_NAME_SERVER_VERSION = "!Msv"; + + /** Field name of int64 indicating how many microseconds have elapsed since the server was started. */ + public static final String PR_NAME_SERVER_UPTIME = "!Mup"; + + /** Field name of int32 indicating how many database nodes may be uploaded by this client (total). */ + public static final String PR_NAME_MAX_NODES_PER_SESSION = "!Mns"; + + /** Field name of a string that the server will replace with the session ID string of your + * session in any outgoing client-to-client messages. */ + public static final String PR_NAME_SESSION = "session"; + + /** this field name's submessage is the payload of the current node + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODEDATA = "data"; + + /** this field name's submessage represents the children of the current node (recursive) + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODECHILDREN = "kids"; + + /** this field name's submessage represents the index of the current node + * in the message created by StorageReflectSession::SaveNodeTreeToMessage() + */ + public static final String PR_NAME_NODEINDEX = "index"; + + /** + * Field name of a message that causes the server to reply with compressed messages. + * This will only work with the Java API if the JZLib library ( http://www.jcraft.com/jzlib/ ) + * is on the classpath. + */ + public static final String PR_NAME_REPLY_ENCODING = "!Enc"; +} + +// This is a specialization of AbstractReflectSession that adds several +// useful capabilities to the Reflect Server. Abilities include: +// - Messages can specify (via wildcard path matching) which other +// clients they should be reflected to. If the message doesn't specify +// a path, then the session's default path can be used. +// - Clients can upload and store data on the server (in the server's RAM only). +// This data will remain accessible to all sessions until the client disconnects. +// - Clients can "subscribe" to server state information and be automatically +// notified when the information has changed. +// +// CLIENT-TO-SERVER MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_COMMAND_SETPARAMETERS: +// All fields of the message are placed into the session's parameter set. +// Any fields in the previous parameters set with matching names are replaced. +// Currently parsed parameter names are as follows: +// +// "SUBSCRIBE:" : Any parameter name that begins with the prefix SUBSCRIBE: +// is taken to represent a request to monitor the contents of +// all nodes whose pathnames match the path that follows. So, +// for example, the parameter name +// +// SUBSCRIBE:/*/*/Joe +// +// Indicates a request to watch all nodes with paths that match +// the regular expression /*/*/Joe. The value +// these parameters are unimportant, and are not looked at. +// Thus, these parameters may be of any type. Once a SUBSCRIBE +// parameter has been added, any data nodes that match the specified +// path will be returned immediately to the client in a PR_RESULT_DATAITEMS +// message. Furthermore, any time these nodes are modified or deleted, +// or any time a new node is added that matches the path, another +// PR_RESULT_DATAITEMS message will be sent to notify the client of +// the change. +// +// PR_NAME_KEYS : If set, any non-"special" messages without a +// PR_NAME_KEYS field will be reflected to clients +// who match at least one of the set of key-paths +// listed here. (Should be one or more string values) +// +// PR_NAME_REFLECT_TO_SELF : If set, wildcard matches can match the current session. +// Otherwise, wildcard matches with the current session will +// be suppressed (on the grounds that your client already knows +// the value of anything that it uploaded, and doesn't need to +// be reminded of it). This field may be of any type, only +// its existence/non-existence is relevant. +// +// +// if 'what' is PR_COMMAND_GETPARAMETERS: +// Causes a PR_RESULT_PARAMETERS message to be returned to the client. The returned message +// contains the entire current parameter set. +// +// if 'what' is PR_COMMAND_REMOVEPARAMETERS: +// The session looks for PR_NAME_KEYS string entrys. For each string found +// under this entry, any matching field in the parameters message are deleted. +// Wildcards are permitted in these strings. (So e.g. "*" would remove ALL parameters) +// +// if 'what' is PR_COMMAND_SETDATA: +// Scans the message for all fields of type message. Each message field +// should contain only one message. The field's name is parsed as a local +// key-path of the data item (e.g. "myData", or "imageInfo/colors/red"). +// Each contained message will be stored in the local session's data tree under +// that key path. (Note: fields that start with a '/' are not allowed, and +// will be ignored!) +// +// if 'what' is PR_COMMAND_REMOVEDATA: +// Removes all data nodes that match the path(s) in the PR_NAME_KEYS string field. +// Paths should be specified relative to this session's root node (i.e. they should +// not start with a slash) +// +// if 'what' is PR_COMMAND_GETDATA: +// The session looks for one or more strings in the PR_NAME_KEYS field. Each +// string represents a key-path indicating which information the client is +// interested in retrieving. If there is no leading slash, "/*/*/" will be +// implicitely prepended. Here are some valid example key-paths: +// /*/*/color (gets "color" from all hostnames, all session IDs) +// /joe.blow.com/*/shape (gets "shape" from all sessions connected from joe.blow.com) +// /joe.blow.com/19435935093/sound (gets "sound" from a single session) +// /*/*/vehicleTypes/* (gets all vehicle types from all clients) +// j* (equivalent to "/*/*/j*") +// shape/* (equivalent to "/*/*/shape/*") +// The union of all the sets of matching data nodes specified by these paths will be +// added to a single PR_RESULT_DATAITEMS message which is then passed back to the client. +// Each matching message is added with its full path as a field name. +// +// if 'what' is PR_COMMAND_INSERTORDEREDDATA: +// The session looks for one or more messages in the PR_NAME_KEYS field. Each +// string represents a wildpath, rooted at this session's node (read: no leading +// slash should be present) that specifies zero or more data nodes to insert ordered/ +// indexed children under. Each node in the union of these node sets will have new +// ordered/indexed child nodes created underneath it. The names of these new child +// nodes will be chosen algorithmically by the server. There will be one child node +// created for each sub-message in this message. Sub-messages may be added under any +// field name; if the field name happens to be the name of a currently indexed child, +// the new message node will be be inserted *before* the specified child in the index. +// Otherwise, it will be appended to the end of the index. Clients who have subscribed +// to the specified nodes will see the updates to the index; clients who have subscribed +// to the children will get updates of the actual data as well. +// +// if 'what' is PR_COMMAND_PING: +// The session will change the message's 'what' code to PR_RESULT_PONG, and send +// it right back to the client. In this way, the client can find out (a) that +// the server is still alive, (b) how long it takes the server to respond, and +// (c) that any previously sent operations have finished. +// +// if 'what' is PR_COMMAND_KICK: +// The server will look for one or more strings in the PR_NAME_KEYS field. It will +// do a search of the database for any nodes matching one or more of the node paths +// specified by these strings, and will kick any session with matching nodes off +// of the server. Of course, this will only be done if the client who sent the +// PR_COMMAND_KICK field has PR_PRIVILEGE_KICK access. +// +// if 'what' is PR_COMMAND_ADDBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be added to the server's "banned IP list", and +// subsequent connection attempts from IP addresses matching any of these ban strings +// will be denied. Of course, this will only be done if the client who sent the +// PR_COMMAND_ADDBANS field has PR_PRIVILEGE_ADDBANS access. +// +// if 'what' is PR_COMMAND_REMOVEBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be used a pattern-matching strings against the +// current set of "ban" patterns. Any "ban" patterns that are matched by any +// of the PR_NAME_KEYS strings will be removed from the "banned IP patterns" set, +// so that IP addresses who matched those patterns will be able to connect to +// the server again. Of course, this will only be done if the client who sent the +// PR_COMMAND_REMOVEBANS field has PR_PRIVILEGE_REMOVEBANS access. +// +// if 'what' is PR_COMMAND_RESERVED_*: +// The server will change the 'what' code of your message to PR_RESULT_UNIMPLEMENTED, +// and send it back to your client. +// +// if 'what' is PR_RESULT_*: +// The message will be silently dropped. You are not allowed to send PR_RESULT_(*) +// messages to the server, and should be very ashamed of yourself for even thinking +// about it. +// +// All other 'what' codes +// Messages with other 'what' codes are simply reflected to other clients verbatim. +// If a PR_NAME_KEYS string field is found in the message, then it will be parsed as a +// set of key-paths, and only other clients who can match at least one of these key-paths +// will receive the message. If no PR_NAME_KEYS field is found, then the parameter +// named PR_NAME_KEYS will be used as a default value. If that parameter isn't +// found, then the message will be reflected to all clients (except this one). +// +// SERVER-TO-CLIENT MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_RESULT_PARAMETERS: +// The message contains the complete parameter set that is currently associated with +// this client on the server. Parameters may have any field name, and be of any type. +// Certain parameter names are recognized by the StorageReflectSession as having special +// meaning; see the documentation on PR_COMMAND_SETPARAMETERS for information about these. +// +// if 'what' is PR_RESULT_DATAITEMS: +// The message contains information about data that is stored on the server. All stored +// data is stored in the form of Messages. Thus, all data in this message will be +// in the form of a message field, with the field's name being the fully qualified path +// of the node it represents (e.g. "/my.computer.com/5/MyNodeName") and the value being +// the stored data itself. Occasionally it is necessary to inform the client that a data +// node has been deleted; this is done by adding the deceased node's path name as a string +// to the PR_NAME_REMOVED_DATAITEM field. If multiple nodes were removed, there may be +// more than one string present in the PR_NAME_REMOVED_DATAITEM field. +// +// if 'what' is PR_RESULT_INDEXUPDATED: +// The message contains information about index entries that were added (via PR_COMMAND_INSERTORDERREDDATA) +// to a node that the client is subscribed to. Each entry's field name is the fully qualified +// path of a subscribed node, and the value(s) are strings of this format: "%c%lu:%s", %c is +// a single character that is one of the INDEX_OP_* values, the %lu is an index the item was added to +// or removed from, and %s is the key string for the child in question. +// Note that there may be more than one value per field! +// +// if 'what' is PR_RESULT_ERRORUNIMPLEMENTED: +// Your message is being returned to you because it tried to use functionality that +// hasn't been implemented on the server side. This usually happens if you are trying +// to use a new MUSCLE feature with an old MUSCLE server. +// +// if 'what' is PR_RESULT_PONG: +// Your PR_COMMAND_PING message has been returned to you as a PR_RESULT_PONG message. +// +// if 'what' is PR_RESULT_ERRORACCESSDENIED: +// You tried to do something that you don't have permission to do (such as kick, ban, +// or unban another user). +// +// if 'what' is anything else: +// This message was reflected to your client by a neighboring client session. The content +// of the message is not specified by the StorageReflectSession; it just passes any message +// on verbatim. + diff --git a/java_j2me/src/com/lcs/micromuscle/iogateway/AbstractMessageIOGateway.java b/java_j2me/src/com/lcs/micromuscle/iogateway/AbstractMessageIOGateway.java new file mode 100644 index 00000000..322b637a --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/iogateway/AbstractMessageIOGateway.java @@ -0,0 +1,42 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ + +package com.meyer.micromuscle.iogateway; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import com.meyer.micromuscle.support.UnflattenFormatException; +import com.meyer.micromuscle.message.Message; + +/** Interface for an object that knows how to translate bytes + * into Messages, and vice versa. + */ +public interface AbstractMessageIOGateway +{ + public final static int MUSCLE_MESSAGE_DEFAULT_ENCODING = 1164862256; // 'Enc0' ... our default (plain-vanilla) encoding + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_1 = 1164862257; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_2 = 1164862258; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_3 = 1164862259; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_4 = 1164862260; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_5 = 1164862261; + /** This is the recommended CPU vs space-savings setting for zlib */ + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_6 = 1164862262; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_7 = 1164862263; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_8 = 1164862264; + public final static int MUSCLE_MESSAGE_ENCODING_ZLIB_9 = 1164862265; + + /** Reads from the input stream until a Message can be assembled and returned. + * @param in The input stream to read from. + * @throws IOException if there is an error reading from the stream. + * @throws UnflattenFormatException if there is an error parsing the data in the stream. + * @return The next assembled Message. + */ + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException; + + /** Converts the given Message into bytes and sends it out the stream. + * @param out the data stream to send the converted bytes to. + * @param message the Message to convert. + * @throws IOException if there is an error writing to the stream. + */ + public void flattenMessage(DataOutput out, Message msg) throws IOException; +} diff --git a/java_j2me/src/com/lcs/micromuscle/iogateway/JZLibMessageIOGateway.java b/java_j2me/src/com/lcs/micromuscle/iogateway/JZLibMessageIOGateway.java new file mode 100644 index 00000000..ce8cf811 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/iogateway/JZLibMessageIOGateway.java @@ -0,0 +1,128 @@ +package com.meyer.micromuscle.iogateway; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; +import com.meyer.micromuscle.message.Message; +import com.meyer.micromuscle.support.LEDataInputStream; +import com.meyer.micromuscle.support.LEDataOutputStream; +import com.meyer.micromuscle.support.UnflattenFormatException; + +class JZLibMessageIOGateway extends MessageIOGateway +{ + protected ZStream _deflateStream; + protected ZStream _inflateStream; + + protected ByteArrayOutputStream _outputByteBuffer; + protected LEDataOutputStream _leOutputStream; + + public JZLibMessageIOGateway() + { + super(); + } + + public JZLibMessageIOGateway(int encoding) + { + super(encoding); + } + + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException + { + int numBytes = in.readInt(); + int encoding = in.readInt(); + if (encoding == MUSCLE_MESSAGE_DEFAULT_ENCODING) + { + Message pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + int independentValue = in.readInt(); + int flatSize = in.readInt(); + boolean needToInit = false; + boolean independent = (independentValue == ZLIB_CODEC_HEADER_INDEPENDENT); + if (_inflateStream == null) + { + _inflateStream = new ZStream(); + needToInit = true; + } + if (independent) needToInit = true; + byte[] currChunk = new byte[numBytes - 8]; + byte[] uncompressedData = new byte[flatSize]; + // Get the data + in.readFully(currChunk); + // Uncompress the data + _inflateStream.next_in = currChunk; + _inflateStream.next_in_index = 0; + _inflateStream.next_out = uncompressedData; + _inflateStream.next_out_index = 0; + if (needToInit) _inflateStream.inflateInit(); + + _inflateStream.avail_in = currChunk.length; + _inflateStream.avail_out = uncompressedData.length; + int err = JZlib.Z_DATA_ERROR; + while((_inflateStream.next_out_index 0) + { + err = _deflateStream.deflate(JZlib.Z_SYNC_FLUSH); + if ((err == JZlib.Z_STREAM_ERROR)||(err == JZlib.Z_DATA_ERROR)||(err == JZlib.Z_NEED_DICT)||(err == JZlib.Z_BUF_ERROR)) throw new IOException("Problem compressing the outgoing message."); + } + + out.writeInt(_deflateStream.next_out_index + 8); + out.writeInt(_outgoingEncoding); + out.writeInt(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + out.writeInt(flatSize); + out.write(compressed, 0, _deflateStream.next_out_index); + _outputByteBuffer.reset(); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGateway.java b/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGateway.java new file mode 100644 index 00000000..6f01d1b8 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGateway.java @@ -0,0 +1,60 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.iogateway; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import com.meyer.micromuscle.message.Message; +import com.meyer.micromuscle.support.UnflattenFormatException; + +/** A gateway that converts to and from the 'standard' MUSCLE flattened message byte stream. */ +public class MessageIOGateway implements AbstractMessageIOGateway +{ + protected int _outgoingEncoding; + + public MessageIOGateway() + { + _outgoingEncoding = MUSCLE_MESSAGE_DEFAULT_ENCODING; + } + + public MessageIOGateway(int encoding) + { + if (encoding < MUSCLE_MESSAGE_DEFAULT_ENCODING || encoding > MUSCLE_MESSAGE_ENCODING_ZLIB_9) + { + throw new RuntimeException("The encoding passed is not supported"); + } + _outgoingEncoding = encoding; + } + + /** Reads from the input stream until a Message can be assembled and returned. + * @param in The input stream to read from. + * @throws IOException if there is an error reading from the stream. + * @throws UnflattenFormatException if there is an error parsing the data in the stream. + * @return The next assembled Message. + */ + public Message unflattenMessage(DataInput in) throws IOException, UnflattenFormatException + { + int numBytes = in.readInt(); + int encoding = in.readInt(); + if (encoding != MUSCLE_MESSAGE_DEFAULT_ENCODING) throw new IOException(); + Message pmsg = pmsg = new Message(); + pmsg.unflatten(in, numBytes); + return pmsg; + } + + /** Converts the given Message into bytes and sends it out the stream. + * @param out the data stream to send the converted bytes to. + * @param message the Message to convert. + * @throws IOException if there is an error writing to the stream. + */ + public void flattenMessage(DataOutput out, Message msg) throws IOException + { + out.writeInt(msg.flattenedSize()); + out.writeInt(MUSCLE_MESSAGE_DEFAULT_ENCODING); + msg.flatten(out); + } + + protected final static int ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219; // 'zlic' + protected final static int ZLIB_CODEC_HEADER_DEPENDENT = 2053925218; // 'zlib' +} diff --git a/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGatewayFactory.java b/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGatewayFactory.java new file mode 100644 index 00000000..79ef7d35 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/iogateway/MessageIOGatewayFactory.java @@ -0,0 +1,22 @@ +package com.meyer.micromuscle.iogateway; + +public class MessageIOGatewayFactory +{ + // This class is a factory and shouldn't be created anywhere + private MessageIOGatewayFactory() + { + // empty + } + + public static AbstractMessageIOGateway getMessageIOGateway() + { + return MessageIOGatewayFactory.getMessageIOGateway(AbstractMessageIOGateway.MUSCLE_MESSAGE_DEFAULT_ENCODING); + } + + public static AbstractMessageIOGateway getMessageIOGateway(int encoding) + { + if (encoding == AbstractMessageIOGateway.MUSCLE_MESSAGE_DEFAULT_ENCODING) return new MessageIOGateway(); + System.out.println("New JZLibGateway..."); + return new JZLibMessageIOGateway(encoding); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/message/FieldNotFoundException.java b/java_j2me/src/com/lcs/micromuscle/message/FieldNotFoundException.java new file mode 100644 index 00000000..0b661ca7 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/message/FieldNotFoundException.java @@ -0,0 +1,21 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ + +package com.meyer.micromuscle.message; + +/** Exception that is thrown if you try to access a Message + * field that isn't present in the Message. + */ +public class FieldNotFoundException extends MessageException +{ + private static final long serialVersionUID = -1387490200161526582L; + + public FieldNotFoundException(String s) + { + super(s); + } + + public FieldNotFoundException() + { + super("Message entry not found"); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/message/FieldTypeMismatchException.java b/java_j2me/src/com/lcs/micromuscle/message/FieldTypeMismatchException.java new file mode 100644 index 00000000..a9d4a74d --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/message/FieldTypeMismatchException.java @@ -0,0 +1,20 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.message; + +/** Exception that is thrown if you try to access a field in a Message + * by the wrong type (e.g. calling getInt() on a string field or somesuch) + */ +public class FieldTypeMismatchException extends MessageException +{ + private static final long serialVersionUID = -8562539748755552587L; + + public FieldTypeMismatchException(String s) + { + super(s); + } + + public FieldTypeMismatchException() + { + super("Message entry type mismatch"); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/message/Message.java b/java_j2me/src/com/lcs/micromuscle/message/Message.java new file mode 100644 index 00000000..4df764ac --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/message/Message.java @@ -0,0 +1,1854 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import com.meyer.micromuscle.support.Flattenable; +import com.meyer.micromuscle.support.LEDataInputStream; +import com.meyer.micromuscle.support.LEDataOutputStream; +import com.meyer.micromuscle.support.Point; +import com.meyer.micromuscle.support.Rect; +import com.meyer.micromuscle.support.UnflattenFormatException; + +/** + * This class is sort of similar to Be's BMessage class. When flattened, + * the resulting byte stream is compatible with the flattened + * buffers of MUSCLE's C++ Message class. + * It only acts as a serializable data container; it does not + * include any threading capabilities. + */ +public final class Message implements Flattenable +{ + /** Oldest serialization protocol version parsable by this code's unflatten() methods */ + public static final int OLDEST_SUPPORTED_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /** Newest serialization protocol version parsable by this code's unflatten() methods, + * as well as the version of the protocol produce by this code's flatten() methods. */ + public static final int CURRENT_PROTOCOL_VERSION = 1347235888; // 'PM00' + + /** 32 bit 'what' code, for quick identification of message types. Set this however you like. */ + public int what = 0; + + /** Default Constructor. */ + public Message() + { + if (_empty == null) _empty = new Hashtable(); // IE5 is lame + } + + /** Constructor. + * @param w The 'what' member variable will be set to the value you specify here. + */ + public Message(int w) + { + this(); + what = w; + } + + /** Copy Constructor. + * @param copy The Message to make us a (wholly independant) copy of. + */ + public Message(Message copyMe) + { + this(); + setEqualTo(copyMe); + } + + /** Returns an independent copy of this Message */ + public Flattenable cloneFlat() + { + Message clone = new Message(); + clone.setEqualTo(this); + return clone; + } + + /** Sets this Message equal to (c) + * @param c What to make ourself a clone of. Should be a Message + * @throws ClassCastException if (c) isn't a Message. + */ + public void setEqualTo(Flattenable c) throws ClassCastException + { + clear(); + Message copyMe = (Message)c; + what = copyMe.what; + Enumeration fields = copyMe.fieldNames(); + while(fields.hasMoreElements()) + { + try { + copyMe.copyField((String)fields.nextElement(), this); + } + catch(FieldNotFoundException ex) { + ex.printStackTrace(); // should never happen + } + } + } + + /** Returns an Enumeration of Strings that are the + * field names present in this Message + */ + public Enumeration fieldNames() + { + return (_fieldTable != null) ? _fieldTable.keys() : _empty.keys(); + } + + /** Returns the given 'what' constant as a human readable 4-byte string, e.g. "BOOL", "BYTE", etc. + * @param w Any 32-bit value you would like to have turned into a String. + */ + public static String whatString(int w) + { + byte [] temp = new byte[4]; + for (int i=0; i<4; i++) + { + byte b = (byte)((w >> ((3-i)*8)) & 0xFF); + if ((b<' ')||(b>'~')) b = '?'; + temp[i] = b; + } + return new String(temp); + } + + /** Returns the number of field names of the given type that are present in the Message. + * @param type The type of field to count, or B_ANY_TYPE to count all field types. + * @return The number of matching fields, or zero if there are no fields of the appropriate type. + */ + public int countFields(int type) + { + if (_fieldTable == null) return 0; + if (type == B_ANY_TYPE) return _fieldTable.size(); + + int count = 0; + Enumeration e = _fieldTable.elements(); + while(e.hasMoreElements()) + { + MessageField field = (MessageField) e.nextElement(); + if (field.typeCode() == type) count++; + } + return count; + } + + /** Returns the total number of fields in this Message. */ + public int countFields() {return countFields(B_ANY_TYPE);} + + /** Returns true iff there are no fields in this Message. */ + public boolean isEmpty() {return (countFields() == 0);} + + /** Returns a string that is a summary of the contents of this Message. Good for debugging. */ + public String toString() + { + String ret = "Message: what='" + whatString(what) + "' ("+what+"), countFields=" + countFields() + ", flattenedSize=" + flattenedSize() + "\n"; + // Better to not use the keyword "enum" - it is reserved in JDK 1.5 + Enumeration enumeration = fieldNames(); + while(enumeration.hasMoreElements()) + { + String fieldName = (String) enumeration.nextElement(); + ret += " " + fieldName + ": " + _fieldTable.get(fieldName).toString() + "\n"; + } + return ret; + } + + /** Renames a field. + * @param old_entry Field name to rename from. + * @param new_entry Field name to rename to. If a field with this name already exists, it will be replaced. + * @throws FieldNotFoundException if a field named (old_entry) can't be found. + */ + public void renameField(String old_entry, String new_entry) throws FieldNotFoundException + { + MessageField field = getField(old_entry); + _fieldTable.remove(old_entry); + _fieldTable.put(new_entry, field); + } + + /** Returns false (Messages can be of various sizes, depending on how many fields they have, etc.) */ + public boolean isFixedSize() {return false;} + + /** Returns B_MESSAGE_TYPE */ + public int typeCode() {return B_MESSAGE_TYPE;} + + /** Returns The number of bytes it would take to flatten this Message into a byte buffer. */ + public int flattenedSize() + { + int sum = 4 + 4 + 4; // 4 bytes for the protocol revision #, 4 bytes for the number-of-entries field, 4 bytes for what code + if (_fieldTable != null) + { + Enumeration e = fieldNames(); + while(e.hasMoreElements()) + { + String fieldName = (String) e.nextElement(); + MessageField field = (MessageField) _fieldTable.get(fieldName); + + // 4 bytes for the name length, name data, 4 bytes for entry type code, 4 bytes for entry data length, entry data + sum += 4 + (fieldName.length()+1) + 4 + 4 + field.flattenedSize(); + } + } + return sum; + } + + /** Returns true iff (code) is B_MESSAGE_TYPE */ + public boolean allowsTypeCode(int code) {return (code == B_MESSAGE_TYPE);} + + /** + * Converts this Message into a flattened stream of bytes that can be saved to disk + * or sent over a network, and later converted back into an identical Message object. + * @param out The stream to output bytes to. (Should generally be an LEOutputDataStream object) + * @throws IOException if there is a problem outputting the bytes. + */ + public void flatten(DataOutput out) throws IOException + { + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + out.writeInt(CURRENT_PROTOCOL_VERSION); + out.writeInt(what); + out.writeInt(countFields()); + Enumeration e = fieldNames(); + while(e.hasMoreElements()) + { + String name = (String) e.nextElement(); + MessageField field = (MessageField) _fieldTable.get(name); + out.writeInt(name.length()+1); + byte[] nameChars = name.getBytes(); + out.write(nameChars, 0, nameChars.length); + out.writeByte(0); // terminating NUL byte + out.writeInt(field.typeCode()); + out.writeInt(field.flattenedSize()); + field.flatten(out); + } + } + + /** + * Convert bytes from the given stream back into a Message. Any previous contents of + * this Message will be erased, and replaced with the data specified in the byte buffer. + * @param in The stream to get bytes from. (Should generally be an LEInputDataStream object) + * @param numBytes The number of bytes this object takes up in the stream, or negative if the number is not known. + * @throws UnflattenFormatException if the data in (buf) wasn't well-formed. + * @throws IOException if there was an error reading from the input stream. + */ + public void unflatten(DataInput in, int numBytes) throws UnflattenFormatException, IOException + { + clear(); + int protocolVersion = in.readInt(); + if ((protocolVersion > CURRENT_PROTOCOL_VERSION)||(protocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)) throw new UnflattenFormatException("Version mismatch error"); + what = in.readInt(); + int numEntries = in.readInt(); + byte [] stringBuf = null; + if (numEntries > 0) ensureFieldTableAllocated(); + for (int i=0; i5)?fieldNameLength:5)*2]; + in.readFully(stringBuf, 0, fieldNameLength); + MessageField field = getCreateOrReplaceField(new String(stringBuf, 0, fieldNameLength-1), in.readInt()); + field.unflatten(in, in.readInt()); + _fieldTable.put(new String(stringBuf, 0, fieldNameLength-1), field); + } + } + + /** Sets the given field name to contain a single boolean value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setBoolean(String name, boolean val) + { + MessageField field = getCreateOrReplaceField(name, B_BOOL_TYPE); + boolean [] array = (boolean[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new boolean[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given boolean values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of boolean values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setBooleans(String name, boolean [] vals) {setObjects(name, B_BOOL_TYPE, vals, vals.length);} + + /** Returns the first boolean value in the given field. + * @param name Name of the field to look for a boolean value in. + * @return The first boolean value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_BOOL_TYPE field. + */ + public boolean getBoolean(String name) throws MessageException {return getBooleans(name)[0];} + + /** Returns the first boolean value in the given field, or the default specified. + * @param name Name of the field to look for a boolean value in + * @param def Default value if the field dosen't exist or is the wrong type. + */ + public boolean getBoolean(String name, boolean def) { + try { + return getBoolean(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of boolean values. + * @param name Name of the field to look for boolean values in. + * @return The array of boolean values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_BOOL_TYPE field. + */ + public boolean[] getBooleans(String name) throws MessageException {return (boolean[]) getData(name, B_BOOL_TYPE);} + + /** Sets the given field name to contain a single byte value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setByte(String name, byte val) + { + MessageField field = getCreateOrReplaceField(name, B_INT8_TYPE); + byte [] array = (byte[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new byte[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given byte values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of byte values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setBytes(String name, byte [] vals) {setObjects(name, B_INT8_TYPE, vals, vals.length);} + + /** Returns the first byte value in the given field. + * @param name Name of the field to look for a byte value in. + * @return The first byte value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT8_TYPE field. + */ + public byte getByte(String name) throws MessageException {return getBytes(name)[0];} + + /** Returns the first byte value in the given field. + * @param name Name of the field to look for a byte value in. + * @param def Default value to return if the field dosen't exist or is the wrong type. + * @return The first byte value in the field. + */ + public byte getByte(String name, byte def) { + try { + return getByte(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of byte values. + * @param name Name of the field to look for byte values in. + * @return The array of byte values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT8_TYPE field. + */ + public byte[] getBytes(String name) throws MessageException {return (byte[]) getData(name, B_INT8_TYPE);} + + /** Sets the given field name to contain a single short value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setShort(String name, short val) + { + MessageField field = getCreateOrReplaceField(name, B_INT16_TYPE); + short [] array = (short[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new short[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given short values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of short values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setShorts(String name, short [] vals) {setObjects(name, B_INT16_TYPE, vals, vals.length);} + + /** Returns the first short value in the given field. + * @param name Name of the field to look for a short value in. + * @return The first short value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT16_TYPE field. + */ + public short getShort(String name) throws MessageException {return getShorts(name)[0];} + + /** Returns the first short value in the given field. + * @param name Name of the field to look for a short value in. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return The first short value in the field. + */ + public short getShort(String name, short def) { + try { + return getShort(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of short values. + * @param name Name of the field to look for short values in. + * @return The array of short values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT16_TYPE field. + */ + public short[] getShorts(String name) throws MessageException {return (short[]) getData(name, B_INT16_TYPE);} + + /** Sets the given field name to contain a single int value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setInt(String name, int val) + { + MessageField field = getCreateOrReplaceField(name, B_INT32_TYPE); + int [] array = (int[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new int[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given int values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of int values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setInts(String name, int [] vals) {setObjects(name, B_INT32_TYPE, vals, vals.length);} + + /** Returns the first int value in the given field. + * @param name Name of the field to look for a int value in. + * @return The first int value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT32_TYPE field. + */ + public int getInt(String name) throws MessageException {return getInts(name)[0];} + + /** Returns the first int value in the given field. + * @param name Name of the field to look for a int value in. + * @param def The value to return if the field dosen't exist, or if the field is the incorrect type. + * @return The first int value in the field. + */ + public int getInt(String name, int def) { + try { + return getInt(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of int values. + * @param name Name of the field to look for int values in. + * @return The array of int values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT32_TYPE field. + */ + public int[] getInts(String name) throws MessageException {return (int[]) getData(name, B_INT32_TYPE);} + + /** Returns the contents of the given field as an array of int values. + * @param name Name of the field to look for int values in. + * @param defs The Default array to return in the event that one does not exist, or an error occurs. + * @return The array of int values associated with (name). Note that this array is still part of this Message. + */ + public int[] getInts(String name, int[] defs) { + try { + return getInts(name); + } catch (MessageException me) { + return defs; + } + } + + /** Sets the given field name to contain a single long value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setLong(String name, long val) + { + MessageField field = getCreateOrReplaceField(name, B_INT64_TYPE); + long [] array = (long[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new long[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given long values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of long values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setLongs(String name, long [] vals) {setObjects(name, B_INT64_TYPE, vals, vals.length);} + + /** Returns the first long value in the given field. + * @param name Name of the field to look for a long value in. + * @return The first long value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT64_TYPE field. + */ + public long getLong(String name) throws MessageException {return getLongs(name)[0];} + + /** Returns the first long value in the given field. + * @param name Name of the field to look for a long value in. + * @param def The Default value to return if the field dosen't exist, or if it's the wrong type. + * @return The first long value in the field. + */ + public long getLong(String name, long def) { + try { + return getLong(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of long values. + * @param name Name of the field to look for long values in. + * @return The array of long values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_INT64_TYPE field. + */ + public long[] getLongs(String name) throws MessageException {return (long[]) getData(name, B_INT64_TYPE);} + + /** Sets the given field name to contain a single float value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setFloat(String name, float val) + { + MessageField field = getCreateOrReplaceField(name, B_FLOAT_TYPE); + float [] array = (float[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new float[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given float values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of float values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setFloats(String name, float [] vals) {setObjects(name, B_FLOAT_TYPE, vals, vals.length);} + + /** Returns the first float value in the given field. + * @param name Name of the field to look for a float value in. + * @return The first float value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_FLOAT_TYPE field. + */ + public float getFloat(String name) throws MessageException {return getFloats(name)[0];} + + /** Returns the first float value in the given field. + * @param name Name of the field to look for a float value in. + * @param def the Default value to return if the field dosen't exist, or is the wrong type. + * @return The first float value in the field. + */ + public float getFloat(String name, float def) { + try { + return getFloat(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of float values. + * @param name Name of the field to look for float values in. + * @return The array of float values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_FLOAT_TYPE field. + */ + public float[] getFloats(String name) throws MessageException {return (float[]) getData(name, B_FLOAT_TYPE);} + + /** Sets the given field name to contain a single double value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setDouble(String name, double val) + { + MessageField field = getCreateOrReplaceField(name, B_DOUBLE_TYPE); + double [] array = (double[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new double[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given double values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of double values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setDoubles(String name, double [] vals) {setObjects(name, B_DOUBLE_TYPE, vals, vals.length);} + + /** Returns the first double value in the given field. + * @param name Name of the field to look for a double value in. + * @return The first double value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_DOUBLE_TYPE field. + */ + public double getDouble(String name) throws MessageException {return getDoubles(name)[0];} + + /** Returns the first double value in the given field. + * @param name Name of the field to look for a double value in. + * @param def The Default value to return if the field dosen't exist. + * @return The first double value in the field. + */ + public double getDouble(String name, double def) { + try { + return getDouble(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of double values. + * @param name Name of the field to look for double values in. + * @return The array of double values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_DOUBLE_TYPE field. + */ + public double[] getDoubles(String name) throws MessageException {return (double[]) getData(name, B_DOUBLE_TYPE);} + + /** Sets the given field name to contain a single String value. Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + */ + public void setString(String name, String val) + { + MessageField field = getCreateOrReplaceField(name, B_STRING_TYPE); + String [] array = (String[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new String[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given String values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of String values to assign to the field. Note that the array is not copied; rather the passed-in array becomes part of the Message. + */ + public void setStrings(String name, String [] vals) {setObjects(name, B_STRING_TYPE, vals, vals.length);} + + /** + * @param name The name of the field to access. + * @return the value(s) associated with the requested field name + * @throws MessageFieldNotFoundException If the given field name isn't present in the message + * @throws MessageFieldTypeMismatchException If the given field exists, but is the wrong type of data. + */ + public String getString(String name) throws MessageException {return getStrings(name)[0];} + + /** + * @param name The name of the field to access. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return the value(s) associated with the requested field name + */ + public String getString(String name, String def) { + try { + return getString(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of String values. + * @param name Name of the field to look for String values in. + * @return The array of String values associated with (name). Note that this array is still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_STRING_TYPE field. + */ + public String[] getStrings(String name) throws MessageException {return (String[]) getData(name, B_STRING_TYPE);} + + /** Returns the contents of the given field as an array of String values. + * @param name Name of the field to look for String values in. + * @param def the Default values to return. + * @return The array of String values associated with (name). Note that this array is still part of this Message. + */ + public String[] getStrings(String name, String[] def) { + try { + return (String[]) getData(name, B_STRING_TYPE); + } catch (MessageException me) { + return def; + } + } + + /** Sets the given field name to contain a single Message value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in mesage object becomes part of this Message. + */ + public void setMessage(String name, Message val) + { + MessageField field = getCreateOrReplaceField(name, B_MESSAGE_TYPE); + Message [] array = (Message[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Message[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Message values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of Message objects to assign to the field. Note that the neither the array nor the + * Message objects are copied; rather both the array and the Messages become part of this Message. + */ + public void setMessages(String name, Message [] vals) {setObjects(name, B_MESSAGE_TYPE, vals, vals.length);} + + /** Returns the first Message value in the given field. + * @param name Name of the field to look for a Message value in. + * @return The first Message value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_MESSAGE_TYPE field. + */ + public Message getMessage(String name) throws MessageException {return getMessages(name)[0];} + + /** Returns the contents of the given field as an array of Message values. + * @param name Name of the field to look for Message values in. + * @return The array of Message values associated with (name). Note that the array and the Messages + * it holds are still part of this Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_MESSAGE_TYPE field. + */ + public Message[] getMessages(String name) throws MessageException {return (Message[]) getData(name, B_MESSAGE_TYPE);} + + /** Sets the given field name to contain a single Point value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in object becomes part of this Message. + */ + public void setPoint(String name, Point val) + { + MessageField field = getCreateOrReplaceField(name, B_POINT_TYPE); + Point [] array = (Point[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Point[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Point values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of Point objects to assign to the field. Note that neither the array nor the + * Point objects are copied; rather both the array and the Points become part of this Message. + */ + public void setPoints(String name, Point [] vals) {setObjects(name, B_POINT_TYPE, vals, vals.length);} + + /** Returns the first Point value in the given field. + * @param name Name of the field to look for a Point value in. + * @return The first Point value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_POINT_TYPE field. + */ + public Point getPoint(String name) throws MessageException {return getPoints(name)[0];} + + /** Returns the first Point value in the given field. + * @param name Name of the field to look for a Point value in. + * @param def The Default value to return if the field dosen't exist or is the wrong type. + * @return The first Point value in the field. + */ + public Point getPoint(String name, Point def) { + try { + return getPoint(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of Point values. + * @param name Name of the field to look for Point values in. + * @return The array of Point values associated with (name). Note that the array and the + * objects it holds are still part of the Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_POINT_TYPE field. + */ + public Point[] getPoints(String name) throws MessageException {return (Point[]) getData(name, B_POINT_TYPE);} + + /** Sets the given field name to contain a single Rect value. + * Any previous field contents are replaced. + * @param name Name of the field to set + * @param val Value that will become the sole value in the specified field. + * Note that a copy of (val) is NOT made; the passed-in object becomes part of this Message. + */ + public void setRect(String name, Rect val) + { + MessageField field = getCreateOrReplaceField(name, B_RECT_TYPE); + Rect [] array = (Rect[]) field.getData(); + if ((array == null)||(field.size() != 1)) array = new Rect[1]; + array[0] = val; + field.setPayload(array, 1); + } + + /** Sets the given field name to contain the given Rect values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of Rect objects to assign to the field. Note that neither the array nor the + * Rect objects are copied; rather both the array and the Rects become part of this Message. + */ + public void setRects(String name, Rect [] vals) {setObjects(name, B_RECT_TYPE, vals, vals.length);} + + /** Returns the first Rect value in the given field. Note that the returned object is still part of this Message. + * @param name Name of the field to look for a Rect value in. + * @return The first Rect value in the field. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_RECT_TYPE field. + */ + public Rect getRect(String name) throws MessageException {return getRects(name)[0];} + + /** Returns the first Rect value in the given field. Note that the returned object is still part of this Message. + * @param name Name of the field to look for a Rect value in. + * @param def the default value to return if the field dosen't exist or is the wrong type. + * @return The first Rect value in the field. + */ + public Rect getRect(String name, Rect def) { + try { + return getRect(name); + } catch (MessageException me) { + return def; + } + } + + /** Returns the contents of the given field as an array of Rect values. + * @param name Name of the field to look for Rect values in. + * @return The array of Rect values associated with (name). Note that the array and the Rects that + * it holds are still part of the Message. + * @throws FieldNotFoundException if a field with the given name does not exist. + * @throws FieldTypeMismatchException if the field with the given name is not a B_RECT_TYPE field. + */ + public Rect[] getRects(String name) throws MessageException {return (Rect[]) getData(name, B_RECT_TYPE);} + + /** Sets the given field name to contain the flattened bytes of the single given Flattenable object. + * Any previous field contents are replaced. The type code of the field is determined by calling val.typeCode(). + * @param name Name of the field to set + * @param val The object whose bytes are to be flattened out and put into this field. + * (val) will be flattened and the resulting bytes kept. (val) does not become part of the Message object. + */ + public void setFlat(String name, Flattenable val) + { + int type = val.typeCode(); + MessageField field = getCreateOrReplaceField(name, type); + Object payload = field.getData(); + switch(type) + { + // For these types, we have explicit support for holding the objects in memory, so we'll just clone them + case B_MESSAGE_TYPE: + { + Message array[] = ((payload != null)&&(((Message[])payload).length == 1)) ? ((Message[])payload) : new Message[1]; + array[1] = (Message) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_POINT_TYPE: + { + Point array[] = ((payload != null)&&(((Point[])payload).length == 1)) ? ((Point[])payload) : new Point[1]; + array[1] = (Point) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + case B_RECT_TYPE: + { + Rect array[] = ((payload != null)&&(((Rect[])payload).length == 1)) ? ((Rect[])payload) : new Rect[1]; + array[1] = (Rect) val.cloneFlat(); + field.setPayload(array, 1); + } + break; + + // For everything else, we have to store the objects as byte buffers + default: + { + byte array[][] = ((payload != null)&&(((byte[][])payload).length == 1)) ? ((byte[][])payload) : new byte[1][]; + array[0] = flattenToArray(val, array[0]); + field.setPayload(array, 1); + } + break; + } + } + + /** Sets the given field name to contain the given Flattenable values. Any previous field contents are replaced. + * @param name Name of the field to set vlaues. + * @param val Array of Flattenable objects to assign to the field. The objects are all flattened and + * the flattened data is put into the Message; the objects themselves do not become part of the message. + * Note that if the objects are Messages, Points, or Rects, they will be cloned rather than flattened. + */ + public void setFlats(String name, Flattenable [] vals) + { + int type = vals[0].typeCode(); + int len = vals.length; + MessageField field = getCreateOrReplaceField(name, type); + switch(type) + { + // For these types, we have explicit support for holding the objects in memory, so we'll just clone them + case B_MESSAGE_TYPE: + { + Message array[] = new Message[len]; + for (int i=0; i 0) _numItems = numBytes / flattenedItemSize; + + switch(_type) + { + case B_BOOL_TYPE: + { + boolean [] array = new boolean[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = (in.readByte() > 0) ? true : false; + _payload = array; + } + break; + + case B_INT8_TYPE: + { + byte [] array = new byte[_numItems]; + in.readFully(array); // wow, easy + _payload = array; + } + break; + + case B_INT16_TYPE: + { + short [] array = new short[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readShort(); + _payload = array; + } + break; + + case B_FLOAT_TYPE: + { + float [] array = new float[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readFloat(); + _payload = array; + } + break; + + case B_INT32_TYPE: + { + int [] array = new int[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readInt(); + _payload = array; + } + break; + + case B_INT64_TYPE: + { + long [] array = new long[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readLong(); + _payload = array; + } + break; + + case B_DOUBLE_TYPE: + { + double [] array = new double[_numItems]; + for (int i=0; i<_numItems; i++) array[i] = in.readDouble(); + _payload = array; + } + break; + + case B_POINT_TYPE: + { + Point [] array = new Point[_numItems]; + for (int i=0; i<_numItems; i++) + { + Point p = array[i] = new Point(); + p.unflatten(in, p.flattenedSize()); + } + _payload = array; + } + break; + + case B_RECT_TYPE: + { + Rect [] array = new Rect[_numItems]; + for (int i=0; i<_numItems; i++) + { + Rect r = array[i] = new Rect(); + r.unflatten(in, r.flattenedSize()); + } + _payload = array; + } + break; + + case B_MESSAGE_TYPE: + { + Vector temp = new Vector(); + while(numBytes > 0) + { + Message subMessage = new Message(); + int subMessageSize = in.readInt(); + subMessage.unflatten(in, subMessageSize); + temp.addElement(subMessage); + numBytes -= (subMessageSize + 4); // 4 for the size int + } + _numItems = temp.size(); + Message array[] = new Message[_numItems]; + for (int j=0; j<_numItems; j++) array[j] = (Message) temp.elementAt(j); + _payload = array; + } + break; + + case B_STRING_TYPE: + { + _numItems = in.readInt(); + String array[] = new String[_numItems]; + byte [] temp = null; // lazy-allocated + for (int i=0; i<_numItems; i++) + { + int nextStringLen = in.readInt(); + if ((temp == null)||(temp.length < nextStringLen)) temp = new byte[nextStringLen]; + in.readFully(temp, 0, nextStringLen); // includes nul terminator + try { + array[i] = new String(temp, 0, nextStringLen-1, "UTF8"); // don't include the NUL byte + } catch (UnsupportedEncodingException uee) { + array[i] = new String(temp, 0, nextStringLen-1); // don't include the NUL byte + } + } + _payload = array; + } + break; + + default: + { + _numItems = in.readInt(); + byte [][] array = new byte[_numItems][]; + for (int i=0; i<_numItems; i++) + { + array[i] = new byte[in.readInt()]; + in.readFully(array[i]); + } + _payload = array; + } + break; + } + } + + /** Prints some debug info about our state to (out) */ + public String toString() + { + String ret = " Type='"+whatString(_type)+"', " + _numItems + " items: "; + int pitems = (_numItems < 10) ? _numItems : 10; + switch(_type) + { + case B_BOOL_TYPE: + { + boolean [] array = (boolean[]) _payload; + for (int i=0; i 0) + { + int numToCopy = (len < numLeft) ? len : numLeft; + System.arraycopy(_buf, _bytesRead, b, offset, numToCopy); + _bytesRead += numToCopy; + return numToCopy; + } + else return -1; + } + public synchronized void reset() throws IOException {_bytesRead = _mark;} + public long skip(long n) throws IOException + { + if (_buf != null) + { + int numLeft = _buf.length - _bytesRead; + if (numLeft >= n) + { + _bytesRead += n; + return n; + } + else + { + _bytesRead += numLeft; + return numLeft; + } + } + else return 0; + } + + public void setInputBuffer(byte [] buf) + { + _buf = buf; + _bytesRead = 0; + _mark = 0; + } + + private byte [] _buf = null; + private int _mark = 0; + private int _bytesRead = 0; + } + + /** Used for more efficient flattening of Flattenables to byte arrays */ + private class PreAllocatedByteArrayOutputStream extends OutputStream + { + public void close() throws IOException {_buf = null;} + public void write(byte[] b, int off, int len) throws IOException + { + if (_buf != null) + { + int numLeft = _buf.length - _bytesWritten; + if (numLeft >= len) + { + System.arraycopy(b, off, _buf, _bytesWritten, len); + _bytesWritten += len; + return; + } + } + throw new IOException("no space left in buffer "); + } + + public void write(int b) throws IOException + { + if ((_buf != null)&&(_bytesWritten < _buf.length)) _buf[_bytesWritten++] = (byte) b; + else throw new IOException("no space left in buffer"); + } + + public void setOutputBuffer(byte [] buf) + { + _buf = buf; + _bytesWritten = 0; + } + + private byte _buf[] = null; + private int _bytesWritten = 0; + } + + // These are all allocated the first time they are needed, and then kept in case they are needed again + private PreAllocatedByteArrayInputStream _pais = null; + private PreAllocatedByteArrayOutputStream _paos = null; + private LEDataInputStream _lepais = null; + private LEDataOutputStream _lepaos = null; + + private Hashtable _fieldTable = null; // our name -> MessageField table + private static Hashtable _empty; // = new Hashtable(0); // had to change this to be done on-demand in the ctor, as IE was throwing fits :^( +} + diff --git a/java_j2me/src/com/lcs/micromuscle/message/MessageException.java b/java_j2me/src/com/lcs/micromuscle/message/MessageException.java new file mode 100644 index 00000000..73cb4f27 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/message/MessageException.java @@ -0,0 +1,18 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.message; + +/** Base class for the various Exceptions that the Message class may throw */ +public class MessageException extends Exception +{ + private static final long serialVersionUID = -7107595031653595454L; + + public MessageException(String s) + { + super(s); + } + + public MessageException() + { + super("Error accessing a Message field"); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/queue/Queue.java b/java_j2me/src/com/lcs/micromuscle/queue/Queue.java new file mode 100644 index 00000000..1259a6ff --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/queue/Queue.java @@ -0,0 +1,226 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.queue; + +/* This file is Copyright 2000 Level Control Systems. See the included LICENSE.txt file for details. */ + +/** This class implements a templated double-ended queue data structure. + * Adding or removing items from the head or tail of a Queue is (on average) + * an O(1) operation. + */ +public final class Queue +{ + /** Constructor. + */ + public Queue() + { + // empty + } + + /** Copy constructor. */ + public Queue(Queue copyMe) + { + setEqualTo(copyMe); + } + + /** Make (this) a shallow copy of the passed-in queue. + * @param setTo What to set this queue equal to. + */ + public void setEqualTo(Queue setTo) + { + removeAllElements(); + int len = setTo.size(); + ensureSize(len); + for (int i=0; i 0) + { + ret = _queue[_headIndex]; + _queue[_headIndex] = null; // allow garbage collection + _headIndex = nextIndex(_headIndex); + _elementCount--; + } + return ret; + } + + /** Removes and returns the element at the tail of the Queue. + * @return The removed Object, or null if the Queue was empty. + */ + public Object removeLastElement() + { + Object ret = null; + if (_elementCount > 0) + { + ret = _queue[_tailIndex]; + _queue[_tailIndex] = null; // allow garbage collection + _tailIndex = prevIndex(_tailIndex); + _elementCount--; + } + return ret; + } + + + /** removes the element at the (index)'th position in the queue. + * @param idx Which element to remove--can range from zero + * (head of the queue) to CountElements()-1 (tail of the queue). + * @return The removed Object, or null if the Queue was empty. + * Note that this method is somewhat inefficient for indices that + * aren't at the head or tail of the queue (i.e. O(n) time) + * @throws IndexOutOfBoundsException if (idx) isn't a valid index. + */ + public Object removeElementAt(int idx) + { + if (idx == 0) return removeFirstElement(); + + int index = internalizeIndex(idx); + Object ret = _queue[index]; + while(index != _tailIndex) + { + int next = nextIndex(index); + _queue[index] = _queue[next]; + index = next; + } + _queue[_tailIndex] = null; // allow garbage collection + _tailIndex = prevIndex(_tailIndex); + _elementCount--; + return ret; + } + + /** Copies the (index)'th element into (returnElement). + * @param index Which element to get--can range from zero + * (head of the queue) to (CountElements()-1) (tail of the queue). + * @return The Object at the given index. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public Object elementAt(int index) {return _queue[internalizeIndex(index)];} + + /** Replaces the (index)'th element in the queue with (newElement). + * @param index Which element to replace--can range from zero + * (head of the queue) to (CountElements()-1) (tail of the queue). + * @param newElement The element to place into the queue at the (index)'th position. + * @return The Object that was previously in the (index)'th position. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public Object setElementAt(int index, Object newElement) + { + int iidx = internalizeIndex(index); + Object ret = _queue[iidx]; + _queue[iidx] = newElement; + return ret; + } + + /** Inserts (element) into the (nth) slot in the array. InsertElementAt(0) + * is the same as addHead(element), InsertElementAt(CountElements()) is the same + * as addTail(element). Other positions will involve an O(n) shifting of contents. + * @param index The position at which to insert the new element. + * @param newElement The element to insert into the queue. + * @throws IndexOutOfBoundsException if (index) isn't a valid index. + */ + public void insertElementAt(int index, Object newElement) + { + if (index == 0) prependElement(newElement); + else + { + // Harder case: inserting into the middle of the array + appendElement(null); // just to allocate the extra slot + int lo = (int) index; + for (int i=_elementCount-1; i>lo; i--) _queue[internalizeIndex(i)] = _queue[internalizeIndex(i-1)]; + _queue[internalizeIndex(index)] = newElement; + } + } + + /** Removes all elements from the queue. */ + public void removeAllElements() {while(_elementCount > 0) removeLastElement();} + + /** Returns the number of elements in the queue. (This number does not include pre-allocated space) */ + public int size() {return _elementCount;} + + /** Returns true iff their are no elements in the queue. */ + public boolean isEmpty() {return (_elementCount == 0);} + + /** Returns the head element in the queue. You must not call this when the queue is empty! */ + public Object firstElement() {return elementAt(0);} + + /** Returns the tail element in the queue. You must not call this when the queue is empty! */ + public Object lastElement() {return elementAt(_elementCount-1);} + + /** Makes sure there is enough space preallocated to hold at least + * (numElements) elements. You only need to call this if + * you wish to minimize the number of array reallocations done. + * @param numElements the minimum amount of elements to pre-allocate space for in the Queue. + */ + public void ensureSize(int numElements) + { + if ((_queue == null)||(_queue.length < numElements)) + { + Object newQueue[] = new Object[(numElements < 5) ? 5 : numElements*2]; + if (_elementCount > 0) + { + for (int i=0; i<_elementCount; i++) newQueue[i] = elementAt(i); + _headIndex = 0; + _tailIndex = _elementCount-1; + } + _queue = newQueue; + } + } + + /** Returns the last index of the given (element), or -1 if (element) is + * not found in the list. O(n) search time. + * @param element The element to look for. + * @return The index of (element), or -1 if no such element is present. + */ + public int indexOf(Object element) + { + if (_queue != null) for (int i=size()-1; i>=0; i--) if (elementAt(i) == element) return i; + return -1; + } + + private int nextIndex(int idx) {return (idx >= getArraySize()-1) ? 0 : idx+1;} + private int prevIndex(int idx) {return (idx <= 0) ? getArraySize()-1 : idx-1;} + + // Translates a user-index into an index into the _queue array. + private int internalizeIndex(int idx) + { + if ((idx < 0)||(idx >= _elementCount)) throw new IndexOutOfBoundsException("bad index " + idx + " (queuelen=" + _elementCount + ")"); + return (_headIndex + idx) % getArraySize(); + } + + private int getArraySize() {return (_queue != null) ? _queue.length : 0;} + + private Object _queue[] = null; // demand-allocated object array + private int _elementCount = 0; // number of valid elements in the array + private int _headIndex = -1; // index of the first filled slot, or -1 + private int _tailIndex = -1; // index of the last filled slot, or -1 +} + + diff --git a/java_j2me/src/com/lcs/micromuscle/support/Flattenable.java b/java_j2me/src/com/lcs/micromuscle/support/Flattenable.java new file mode 100644 index 00000000..e1b6c392 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/Flattenable.java @@ -0,0 +1,51 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.support; +import java.io.IOException; +import java.io.DataOutput; +import java.io.DataInput; + +/** Interface for objects that can be flattened and unflattened from Be-style byte streams */ +public interface Flattenable extends TypeConstants +{ + /** Should return true iff every object of this type has a flattened size that is known at compile time. */ + public boolean isFixedSize(); + + /** Should return the type code identifying this type of object. */ + public int typeCode(); + + /** Should return the number of bytes needed to store this object in its current state. */ + public int flattenedSize(); + + /** Should return a clone of this object */ + public Flattenable cloneFlat(); + + /** Should set this object's state equal to that of (setFromMe), or throw an UnflattenFormatException if it can't be done. + * @param setFromMe The object we want to be like. + * @throws ClassCastException if (setFromMe) is the wrong type. + */ + public void setEqualTo(Flattenable setFromMe) throws ClassCastException; + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException; + + /** + * Should return true iff a buffer with type_code (code) can be used to reconstruct + * this object's state. + * @param code A type code ant, e.g. B_RAW_TYPE or B_STRING_TYPE, or something custom. + * @return True iff this object can Unflatten from a buffer of the given type, false otherwise. + */ + public boolean allowsTypeCode(int code); + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @param numBytes The number of bytes the object takes up in the stream, or negative if this is unknown. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException; +} diff --git a/java_j2me/src/com/lcs/micromuscle/support/LEDataInputStream.java b/java_j2me/src/com/lcs/micromuscle/support/LEDataInputStream.java new file mode 100644 index 00000000..37c74ba5 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/LEDataInputStream.java @@ -0,0 +1,160 @@ +/* + * LEDataInputStream.java + * + * Copyright (c) 1998 + * Roedy Green + * Canadian Mind Products + * 5317 Barker Avenue + * Burnaby, BC Canada V5H 2N6 + * tel: (604) 435-3052 + * mailto:roedy@mindprod.com + * http://mindprod.com + * + * + * Version 1.0 1998 January 6 + * 1.1 1998 January 7 - officially implements DataInput + * 1.2 1998 January 9 - add LERandomAccessFile + * 1.3 1998 August 27 - fix bug, readFully instead of read. + * 1.4 1998 November 10 - add address and phone. + * 1.5 1999 October 8 - use cmp.LEDataStream package name. + * meyer 2001 Jan 12 - jaf@meyersound.com sucks it into com.meyer.micromuscle.support package + */ + +package com.meyer.micromuscle.support; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Very similar to DataInputStream except it reads little-endian instead of + * big-endian binary data. + * We can't extend DataInputStream directly since it has only final methods. + * This forces us implement LEDataInputStream with a DataInputStream object, + * and use wrapper methods. + */ +public class LEDataInputStream extends InputStream implements DataInput { + private static final String EmbeddedCopyright = "Copyright 1998 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + /** + * constructor + */ + public LEDataInputStream(InputStream in) { + this.in = in; + this.d = new DataInputStream(in); + w = new byte[8]; + } + + // L I T T L E E N D I A N R E A D E R S + // Little endian methods for multi-byte numeric types. + // Big-endian do fine for single-byte types and strings. + /** + * like DataInputStream.readShort except little endian. + */ + public final short readShort() throws IOException + { + d.readFully(w, 0, 2); + return (short)( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readUnsignedShort except little endian. + * Note, returns int even though it reads a short. + */ + public final int readUnsignedShort() throws IOException + { + d.readFully(w, 0, 2); + return ( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readChar except little endian. + */ + public final char readChar() throws IOException + { + d.readFully(w, 0, 2); + return (char) ( + (w[1]&0xff) << 8 | + (w[0]&0xff)); + } + + /** + * like DataInputStream.readInt except little endian. + */ + public final int readInt() throws IOException + { + d.readFully(w, 0, 4); + return + (w[3]) << 24 | + (w[2]&0xff) << 16 | + (w[1]&0xff) << 8 | + (w[0]&0xff); + } + + /** + * like DataInputStream.readLong except little endian. + */ + public final long readLong() throws IOException + { + d.readFully(w, 0, 8); + return + (long)(w[7]) << 56 | /* long cast needed or shift done modulo 32 */ + (long)(w[6]&0xff) << 48 | + (long)(w[5]&0xff) << 40 | + (long)(w[4]&0xff) << 32 | + (long)(w[3]&0xff) << 24 | + (long)(w[2]&0xff) << 16 | + (long)(w[1]&0xff) << 8 | + (long)(w[0]&0xff); + } + + /** + * like DataInputStream.readFloat except little endian. + */ + public final float readFloat() throws IOException {return Float.intBitsToFloat(readInt());} + + /** + * like DataInputStream.readDouble except little endian. + */ + public final double readDouble() throws IOException {return Double.longBitsToDouble(readLong());} + + // p u r e l y w r a p p e r m e t h o d s + // We can't simply inherit since dataInputStream is final. + + /* Watch out, may return fewer bytes than requested. */ + public final int read(byte b[], int off, int len) throws IOException + { + // For efficiency, we avoid one layer of wrapper + return in.read(b, off, len); + } + + public final void readFully(byte b[]) throws IOException {d.readFully(b, 0, b.length);} + + public final void readFully(byte b[], int off, int len) throws IOException {d.readFully(b, off, len);} + + public final int skipBytes(int n) throws IOException {return d.skipBytes(n);} + + /* only reads one byte */ + public final boolean readBoolean() throws IOException {return d.readBoolean();} + + public final byte readByte() throws IOException {return d.readByte();} + + public int read() throws IOException { return in.read(); } + // note: returns an int, even though says Byte. + public final int readUnsignedByte() throws IOException {return d.readUnsignedByte();} + + public final String readUTF() throws IOException {return d.readUTF();} + + // Note. This is a STATIC method! + public final static String readUTF(DataInput in) throws IOException {return DataInputStream.readUTF(in);} + + public final void close() throws IOException {d.close();} + + private DataInputStream d; // to get at high level readFully methods of DataInputStream + private InputStream in; // to get at the low-level read methods of InputStream + private byte w[]; // work array for buffering input +} diff --git a/java_j2me/src/com/lcs/micromuscle/support/LEDataOutputStream.java b/java_j2me/src/com/lcs/micromuscle/support/LEDataOutputStream.java new file mode 100644 index 00000000..d2935b67 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/LEDataOutputStream.java @@ -0,0 +1,156 @@ +/* + * LEDataOutputStream.java + * + * Copyright (c) 1998 + * Roedy Green + * Canadian Mind Products + * 5317 Barker Avenue + * Burnaby, BC Canada V5H 2N6 + * tel: (604) 435-3052 + * mailto:roedy@mindprod.com + * http://mindprod.com + * + * Copyright (c) 1998 Roedy Green of Canadian Mind Products + * Version 1.0 1998 January 6 + * 1.1 1998 January 7 - officially implements DataInput + * 1.2 1998 January 9 - add LERandomAccessFile + * 1.3 1998 August 28 + * 1.4 1998 November 10 - add new address and phone. + * 1.5 1999 October 8 - use cmp.LEDataStream package name. + * meyer 2001 Jan 12 - jaf@meyersound.com sucks it into com.meyer.micromuscle.support package + */ + +package com.meyer.micromuscle.support; +import java.io.OutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; + + +/** + * Very similar to DataOutputStream except it writes little-endian instead of + * big-endian binary data. + * We can't extend DataOutputStream directly since it has only final methods. + * This forces us implement LEDataOutputStream with a DataOutputStream object, + * and use wrapper methods. + */ +public class LEDataOutputStream extends OutputStream implements DataOutput { + private static final String EmbeddedCopyright = "Copyright 1998 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + /** + * constructor + */ + public LEDataOutputStream(OutputStream out) { + this.d = new DataOutputStream(out); + w = new byte[8]; // work array for composing output + } + + // L I T T L E E N D I A N W R I T E R S + // Little endian methods for multi-byte numeric types. + // Big-endian do fine for single-byte types and strings. + + /** + * like DataOutputStream.writeShort. + * also acts as a writeUnsignedShort + */ + public final void writeShort(int v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + d.write(w, 0, 2); + } + + /** + * like DataOutputStream.writeChar. + * Note the parm is an int even though this as a writeChar + */ + public final void writeChar(int v) throws IOException + { + // same code as writeShort + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + d.write(w, 0, 2); + } + + /** + * like DataOutputStream.writeInt. + */ + public final void writeInt(int v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + w[2] = (byte)(v >> 16); + w[3] = (byte)(v >> 24); + d.write(w, 0, 4); + } + + /** + * like DataOutputStream.writeLong. + */ + public final void writeLong(long v) throws IOException + { + w[0] = (byte) v; + w[1] = (byte)(v >> 8); + w[2] = (byte)(v >> 16); + w[3] = (byte)(v >> 24); + w[4] = (byte)(v >> 32); + w[5] = (byte)(v >> 40); + w[6] = (byte)(v >> 48); + w[7] = (byte)(v >> 56); + d.write(w, 0, 8); + } + + /** + * like DataOutputStream.writeFloat. + */ + public final void writeFloat(float v) throws IOException {writeInt(Float.floatToIntBits(v));} + + /** + * like DataOutputStream.writeDouble. + */ + public final void writeDouble(double v) throws IOException {writeLong(Double.doubleToLongBits(v));} + + /** + * like DataOutputStream.writeChars, flip each char. + */ + public final void writeChars(String s) throws IOException + { + int len = s.length(); + for ( int i = 0 ; i < len ; i++ ) { + writeChar(s.charAt(i)); + } + } // end writeChars + + // p u r e l y w r a p p e r m e t h o d s + // We cannot inherit since DataOutputStream is final. + + /* This method writes only one byte, even though it says int */ + public final synchronized void write(int b) throws IOException {d.write(b);} + + public final synchronized void write(byte b[], int off, int len) throws IOException {d.write(b, off, len);} + + public void flush() throws IOException {d.flush();} + + /* Only writes one byte */ + public final void writeBoolean(boolean v) throws IOException {d.writeBoolean(v);} + + public final void writeByte(int v) throws IOException {d.writeByte(v);} + + public final void writeBytes(String s) throws IOException {d.writeChars(s);} + + public final void writeUTF(String str) throws IOException {d.writeUTF(str);} + +// public final int size() {return d.size();} + + public final void write(byte b[]) throws IOException {d.write(b, 0, b.length);} + + public final void close() throws IOException {d.close();} + + // i n s t a n c e v a r i a b l e s + + private DataOutputStream d; // to get at high level write methods of DataOutputStream + private byte w[]; // work array for composing output + +} // end LEDataOutputStream + + diff --git a/java_j2me/src/com/lcs/micromuscle/support/Point.java b/java_j2me/src/com/lcs/micromuscle/support/Point.java new file mode 100644 index 00000000..51d4e892 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/Point.java @@ -0,0 +1,133 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.support; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** A Java equivalent to Be's BPoint class */ +public class Point implements Flattenable +{ + /** Our data members, exposed for convenience */ + public float x = 0.0f; + public float y = 0.0f; + + /** Default constructor, sets the point to be (0, 0) */ + public Point() { /* empty */ } + + /** Constructor where you specify the initial value of the point + * @param X Initial X position + * @param Y Initial Y position + */ + public Point(float X, float Y) {set(X, Y);} + + /** Returns a clone of this object. */ + public Flattenable cloneFlat() {return new Point(x, y);} + + /** Should set this object's state equal to that of (setFromMe), or throw an UnflattenFormatException if it can't be done. + * @param setFromMe The object we want to be like. + * @throws ClassCastException if (setFromMe) isn't a Point + */ + public void setEqualTo(Flattenable setFromMe) throws ClassCastException + { + Point p = (Point) setFromMe; + set(p.x, p.y); + } + + /** Sets a new value for the point. + * @param X The new X value + * @param Y The new Y value + */ + public void set(float X, float Y) {x = X; y = Y;} + + /** If the point is outside the rectangle specified by the two arguments, + * it will be moved horizontally and/or vertically until it falls inside the rectangle. + * @param topLeft Minimum values acceptable for X and Y + * @param bottomRight Maximum values acceptable for X and Y + */ + public void constrainTo(Point topLeft, Point bottomRight) + { + if (x < topLeft.x) x = topLeft.x; + if (y < topLeft.y) y = topLeft.y; + if (x > bottomRight.x) x = bottomRight.x; + if (y > bottomRight.y) y = bottomRight.y; + } + + public String toString() {return "Point: " + x + " " + y;} + + /** Returns another Point whose values are the sum of this point's values and (p)'s values. + * @param rhs Point to add + * @return Resulting point + */ + public Point add(Point rhs) {return new Point(x+rhs.x, y+rhs.y);} + + /** Returns another Point whose values are the difference of this point's values and (p)'s values. + * @param rhs Point to subtract from this + * @return Resulting point + */ + public Point subtract(Point rhs) {return new Point(x-rhs.x, y-rhs.y);} + + /** Adds (p)'s values to this point's values */ + public void addToThis(Point p) + { + x += p.x; + y += p.y; + } + + /** Subtracts (p)'s values from this point's values */ + public void subtractFromThis(Point p) + { + x -= p.x; + y -= p.y; + } + + /** Returns true iff (p)'s values match this point's values */ + public boolean equals(Object o) + { + if (o instanceof Point) + { + Point p = (Point) o; + return ((x == p.x)&&(y == p.y)); + } + return false; + } + + /** Returns true. */ + public boolean isFixedSize() {return true;} + + /** Returns B_POINT_TYPE. */ + public int typeCode() {return TypeConstants.B_POINT_TYPE;} + + /** Returns 8 (2*sizeof(float)) */ + public int flattenedSize() {return 8;} + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException + { + out.writeFloat(x); + out.writeFloat(y); + } + + /** + * Returns true iff (code) is B_POINT_TYPE. + */ + public boolean allowsTypeCode(int code) {return (code == B_POINT_TYPE);} + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException + { + x = in.readFloat(); + y = in.readFloat(); + } + + public int hashCode() {return Float.floatToIntBits(x) + Float.floatToIntBits(y);} +} + diff --git a/java_j2me/src/com/lcs/micromuscle/support/Preferences.java b/java_j2me/src/com/lcs/micromuscle/support/Preferences.java new file mode 100644 index 00000000..398928de --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/Preferences.java @@ -0,0 +1,117 @@ +package com.meyer.micromuscle.support; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import javax.microedition.rms.RecordStore; +import javax.microedition.rms.RecordStoreException; + +import com.meyer.micromuscle.message.Message; + +/** + * Encapsulates a Message for easier use as a Preference holder. + * Stores flattened messages into a single Record within a J2ME RecordStore. + * + * @version 1.1 10.16.2007 + * @author Bryan Varner + */ +public class Preferences { + RecordStore prefsRecords; + protected Message prefs; + + /** + * Creates a Preferences object with a blank message, that dosen't have a save file. + */ + public Preferences() { + prefs = new Message(); + prefsRecords = null; + } + + /** + * Creates a Preferences object with a default message, that dosen't have a save file. + */ + public Preferences(Message defaults) { + prefs = defaults; + prefsRecords = null; + } + + /** + * Creates a new Preferences from the given file. + * @param loadStore the File to load the preferences from. + */ + public Preferences(RecordStore recordStore) { + prefs = new Message(); + prefsRecords = recordStore; + + byte[] b = new byte[0]; + try { + b = prefsRecords.getRecord(1); + } catch (Exception ex) { } + + if (b.length > 0) { + ByteArrayInputStream bais = new ByteArrayInputStream(b); + try { + prefs.unflatten(new DataInputStream(bais), -1); + } catch (Exception e) { + // You are screwed. + } + } + } + + /** + * Creates a new Preferences instance using defaults for the initial settings, + * then over-writing any defaults with the values from loadFile. + * @param loadFile A Flattened Message to load as Preferences. + * @param defaults An existing Message to use as the base. + */ + public Preferences(RecordStore recordStore, Message defaults) { + prefs = defaults; + try { + prefsRecords = recordStore; + ByteArrayInputStream bais = new ByteArrayInputStream(prefsRecords.getRecord(1)); + + prefs.unflatten(new DataInputStream(bais), -1); + } catch (Exception e) { + // You are screwed. + } + } + + /** + * Saves the preferences to the file they were opened from. + * @throws RecordStoreException + */ + public void save() throws RecordStoreException { + if (prefsRecords != null) { + save(prefsRecords); + } + } + + /** + * Saves the preferences to the file specified. + * @param saveAs the file to save the preferences to. + * @throws RecordStoreException + */ + public void save(RecordStore saveAs) throws RecordStoreException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + prefs.flatten(new DataOutputStream(baos)); + } catch (java.io.IOException ex) { + throw new RecordStoreException("Failed to flatten message.\n"); + } + + if (saveAs.getRecord(1) != null) { + saveAs.addRecord(baos.toByteArray(), 0, baos.size()); + } else { + saveAs.setRecord(1, baos.toByteArray(), 0, baos.size()); + } + } + + /** + * @return the Message this object is manipulating. + */ + public Message getMessage() { + return prefs; + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/support/Rect.java b/java_j2me/src/com/lcs/micromuscle/support/Rect.java new file mode 100644 index 00000000..ce5a6f55 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/Rect.java @@ -0,0 +1,204 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.txt file for details. */ +package com.meyer.micromuscle.support; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** A Java equivalent of Be's BRect class. */ +public class Rect implements Flattenable +{ + /** Exposed member variable representing the position of the left edge of the rectangle. */ + public float left = 0.0f; + + /** Exposed member variable representing the position of the top edge of the rectangle. */ + public float top = 0.0f; + + /** Exposed member variable representing the position of the right edge of the rectangle. */ + public float right = -1.0f; + + /** Exposed member variable representing the position of the bottom edge of the rectangle. */ + public float bottom = -1.0f; + + /** Default Constructor. + * Creates a rectangle with upper left point (0,0), and lower right point (-1,-1). + * Note that this rectangle has a negative area! (that is, it's imaginary) + */ + public Rect() { /* empty */ } + + /** Constructor where you specify the left, top, right, and bottom coordinates */ + public Rect(float l, float t, float r, float b) {set(l,t,r,b);} + + /** Copy constructor */ + public Rect(Rect rhs) {setEqualTo(rhs);} + + /** Constructor where you specify the leftTop point and the rightBottom point. */ + public Rect(Point leftTop, Point rightBottom) {set(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);} + + /** Returns a clone of this object. */ + public Flattenable cloneFlat() {return new Rect(left, top, right, bottom);} + + /** Sets this equal to (rhs). + * @param rhs The rectangle to become like. + * @throws ClassCastException if (rhs) isn't a Rect + */ + public void setEqualTo(Flattenable rhs) + { + Rect copyMe = (Rect) rhs; + set(copyMe.left, copyMe.top, copyMe.right, copyMe.bottom); + } + + /** Set a new position for the rectangle. */ + public void set(float l, float t, float r, float b) + { + left = l; + top = t; + right = r; + bottom = b; + } + + /** Print the rectangle's current state to (out) */ + public String toString() + { + return "Rect: leftTop=(" + left + "," + top + ") rightBottom=(" + right + "," + bottom + ")"; + } + + /** Returns the left top corner of the rectangle. */ + public Point leftTop() {return new Point(left, top);} + + /** Returns the right bottom corner of the rectangle. */ + public Point rightBottom() {return new Point(right, bottom);} + + /** Returns the left bottom corner of the rectangle. */ + public Point leftBottom() {return new Point(left, bottom);} + + /** Returns the right top corner of the rectangle. */ + public Point rightTop() {return new Point(right, top);} + + /** Set the left top corner of the rectangle. */ + public void setLeftTop(Point p) {left = p.x; top = p.y;} + + /** Set the right bottom corner of the rectangle. */ + public void setRightBottom(Point p) {right = p.x; bottom = p.y;} + + /** Set the left bottom corner of the rectangle. */ + public void setLeftBottom(Point p) {left = p.x; bottom = p.y;} + + /** Set the right top corner of the rectangle. */ + public void setRightTop(Point p) {right = p.x; top = p.y;} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + public void insetBy(Point p) {insetBy(p.x, p.y);} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + public void insetBy(float dx, float dy) {left += dx; top += dy; right -= dx; bottom -= dy;} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + public void offsetBy(Point p) {offsetBy(p.x, p.y);} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + public void offsetBy(float dx, float dy) {left += dx; top += dy; right += dx; bottom += dy;} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + public void offsetTo(Point p) {offsetTo(p.x, p.y);} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + public void offsetTo(float x, float y) {right = x + width(); bottom = y + height(); left = x; top = y;} + + /** Comparison Operator. Returns true iff (r)'s dimensions are exactly the same as this rectangle's. */ + public boolean equals(Object o) + { + if (o instanceof Rect) + { + Rect r = (Rect) o; + return (left == r.left)&&(top == r.top)&&(right == r.right)&&(bottom == r.bottom); + } + return false; + } + + /** Returns a rectangle whose area is the intersecting subset of this rectangle's and (r)'s */ + public Rect intersect(Rect r) + { + Rect ret = new Rect(this); + if (ret.left < r.left) ret.left = r.left; + if (ret.right > r.right) ret.right = r.right; + if (ret.top < r.top) ret.top = r.top; + if (ret.bottom > r.bottom) ret.bottom = r.bottom; + return ret; + } + + /** Returns a rectangle whose area is a superset of the union of this rectangle's and (r)'s */ + public Rect unify(Rect r) + { + Rect ret = new Rect(this); + if (r.left < ret.left) ret.left = r.left; + if (r.right > ret.right) ret.right = r.right; + if (r.top < ret.top) ret.top = r.top; + if (r.bottom > ret.bottom) ret.bottom = r.bottom; + return ret; + } + + /** Returns true iff this rectangle and (r) overlap in space. */ + public boolean intersects(Rect r) {return (r.intersect(this).isValid());} + + /** Returns true iff this rectangle's area is non imaginary (i.e. width() and height()) are both non-negative) */ + public boolean isValid() {return ((width() >= 0.0f)&&(height() >= 0.0f));} + + /** Returns the width of this rectangle. */ + public float width() {return right - left;} + + /** Returns the height of this rectangle. */ + public float height() {return bottom - top;} + + /** Returns true iff this rectangle contains the specified point. */ + public boolean contains(Point p) + { + return ((p.x >= left)&&(p.x <= right)&&(p.y >= top)&&(p.y <= bottom)); + } + + /** Returns true iff this rectangle fully the specified rectangle. */ + public boolean contains(Rect p) + { + return ((contains(p.leftTop()))&&(contains(p.rightTop()))&& + (contains(p.leftBottom()))&&(contains(p.rightBottom()))); + } + + /** Part of the Flattenable API: Returns true. */ + public boolean isFixedSize() {return true;} + + /** Part of the Flattenable API: Returns B_RECT_TYPE. */ + public int typeCode() {return B_RECT_TYPE;} + + /** Part of the Flattenable API: Returns 12 (i.e. 4*sizeof(float)). */ + public int flattenedSize() {return 16;} + + /** Returns true only if code is B_RECT_TYPE. */ + public boolean allowsTypeCode(int code) {return (code == B_RECT_TYPE);} + + /** + * Should store this object's state into (buffer). + * @param out The DataOutput to send the data to. + * @throws IOException if there is a problem writing out the output bytes. + */ + public void flatten(DataOutput out) throws IOException + { + out.writeFloat(left); + out.writeFloat(top); + out.writeFloat(right); + out.writeFloat(bottom); + } + + /** + * Should attempt to restore this object's state from the given buffer. + * @param in The stream to read the object from. + * @throws IOException if there is a problem reading in the input bytes. + * @throws UnflattenFormatException if the bytes that were read in weren't in the expected format. + */ + public void unflatten(DataInput in, int numBytes) throws IOException, UnflattenFormatException + { + left = in.readFloat(); + top = in.readFloat(); + right = in.readFloat(); + bottom = in.readFloat(); + } +} diff --git a/java_j2me/src/com/lcs/micromuscle/support/TypeConstants.java b/java_j2me/src/com/lcs/micromuscle/support/TypeConstants.java new file mode 100644 index 00000000..5a4435c3 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/TypeConstants.java @@ -0,0 +1,24 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.support; + +/** Java declarations for Be's type constants. The values are all the same as Be's. */ +public interface TypeConstants +{ + public static final int B_ANY_TYPE = 1095653716; // 'ANYT', // wild card + public static final int B_BOOL_TYPE = 1112493900; // 'BOOL', + public static final int B_DOUBLE_TYPE = 1145195589; // 'DBLE', + public static final int B_FLOAT_TYPE = 1179406164; // 'FLOT', + public static final int B_INT64_TYPE = 1280069191; // 'LLNG', // a.k.a. long in Java + public static final int B_INT32_TYPE = 1280265799; // 'LONG', // a.k.a. int in Java + public static final int B_INT16_TYPE = 1397248596; // 'SHRT', // a.k.a. short in Java + public static final int B_INT8_TYPE = 1113150533; // 'BYTE', // a.k.a. byte in Java + public static final int B_MESSAGE_TYPE = 1297303367; // 'MSGG', + public static final int B_POINTER_TYPE = 1347310674; // 'PNTR', // parsed as int in Java (but not very useful) + public static final int B_POINT_TYPE = 1112559188; // 'BPNT', // muscle.support.Point in Java + public static final int B_RECT_TYPE = 1380270932; // 'RECT', // muscle.support.Rect in Java + public static final int B_STRING_TYPE = 1129534546; // 'CSTR', // java.lang.String + public static final int B_OBJECT_TYPE = 1330664530; // 'OPTR', + public static final int B_RAW_TYPE = 1380013908; // 'RAWT', + public static final int B_MIME_TYPE = 1296649541; // 'MIME', +} + diff --git a/java_j2me/src/com/lcs/micromuscle/support/UnflattenFormatException.java b/java_j2me/src/com/lcs/micromuscle/support/UnflattenFormatException.java new file mode 100644 index 00000000..1bd8013c --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/support/UnflattenFormatException.java @@ -0,0 +1,23 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.support; + +import java.io.IOException; + +/** Exception that is thrown when an unflatten() attempt fails, usually + * due to unrecognized data in the input stream, or a type mismatch. + */ +public class UnflattenFormatException extends IOException +{ + private static final long serialVersionUID = 234010774012207620L; + + public UnflattenFormatException() + { + super("unexpected bytes during Flattenable unflatten"); + } + + public UnflattenFormatException(String s) + { + super(s); + } +} + diff --git a/java_j2me/src/com/lcs/micromuscle/test/TestClient.java b/java_j2me/src/com/lcs/micromuscle/test/TestClient.java new file mode 100644 index 00000000..40916fd3 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/test/TestClient.java @@ -0,0 +1,252 @@ +package com.meyer.micromuscle.test; + +import javax.microedition.midlet.*; +import javax.microedition.lcdui.*; + +import com.meyer.micromuscle.message.Message; +import com.meyer.micromuscle.client.MessageTransceiver; +import com.meyer.micromuscle.client.StorageReflectConstants; +import com.meyer.micromuscle.thread.MessageListener; +import com.meyer.micromuscle.thread.MessageQueue; +import com.meyer.micromuscle.iogateway.AbstractMessageIOGateway; + + +public class TestClient extends MIDlet implements CommandListener, MessageListener, StorageReflectConstants { + Display display; + + Command cmdExit; + Command cmdConnect; + Command cmdDisconnect; + Command cmdClear; + Command cmdSend; + + TextBox txtIO; + + Form frmConnectParams; + TextField txtHost; + TextField txtPort; + + MessageTransceiver mc; + + public TestClient() { + display = Display.getDisplay(this); + + cmdConnect = new Command("Connect", Command.OK, 1); + cmdDisconnect = new Command("Disconnect", Command.OK, 3); + cmdExit = new Command("Exit", Command.EXIT, 3); + cmdClear = new Command("Clear", Command.OK, 1); + cmdSend = new Command("Send", Command.OK, 2); + + txtIO = new TextBox("I/O", "", 512, TextField.ANY); + txtIO.addCommand(cmdClear); + txtIO.addCommand(cmdSend); + txtIO.addCommand(cmdDisconnect); + txtIO.setCommandListener(this); + + frmConnectParams = new Form("Connection Settings"); + txtHost = new TextField("Host:", "30.243.231.39", 18, TextField.ANY); + txtPort = new TextField("Port:", "2960", 4, TextField.NUMERIC); + frmConnectParams.append(txtHost); + frmConnectParams.append(txtPort); + frmConnectParams.addCommand(cmdConnect); + frmConnectParams.addCommand(cmdExit); + frmConnectParams.setCommandListener(this); + + MessageQueue mq = new MessageQueue(this); + mc = new MessageTransceiver(mq, AbstractMessageIOGateway.MUSCLE_MESSAGE_ENCODING_ZLIB_6); + } + + protected void startApp() { + display.setCurrent(frmConnectParams); + } + + protected void pauseApp() { + } + + protected void destroyApp(boolean unconditional) { + mc.disconnect(); + } + + private void exit() { + destroyApp(false); + notifyDestroyed(); + } + + public void commandAction(Command c, Displayable d) { + if (c == cmdExit) { + exit(); + } else if (c == cmdConnect) { + connect(); + } else if (c == cmdDisconnect) { + disconnect(); + } else if (c == cmdClear) { + txtIO.setString(""); + } else { + sendCommands(txtIO.getString()); + } + } + + private void connect() { + mc.connect(txtHost.getString(), Integer.parseInt(txtPort.getString()), "Session Connected", "Session Disconnected"); + display.setCurrent(txtIO); + } + + private void disconnect() { + mc.disconnect(); + } + + public synchronized void messageReceived(Object message, int numLeft) { + if (message instanceof Message) { + txtIO.setString(message.toString()); + } else { + txtIO.setString(message.toString()); + if (message.equals("Session Connected")) { + mc.sendOutgoingMessage(new Message(PR_COMMAND_GETPARAMETERS)); + } else if (message.equals("Session Disconnected")) { + display.setCurrent(frmConnectParams); + } + } + } + + private void sendCommands(String line) { + StringTokenizer st = new StringTokenizer(line); + + String command = st.nextToken(); + Message msg = new Message(PR_COMMAND_PING); + if (command.equalsIgnoreCase("q")) { + exit(); + } else if (command.equalsIgnoreCase("t")) { + int [] ints = {1,3}; + msg.setInts("ints", ints); + float [] floats = {2, 4}; + msg.setFloats("floats", floats); + double [] doubles = {-5, -10}; + msg.setDoubles("doubles", doubles); + byte [] bytes = {0x50}; + msg.setBytes("bytes", bytes); + short [] shorts = {3,15}; + msg.setShorts("shorts", shorts); + long [] longs = {50000}; + msg.setLongs("longs", longs); + boolean [] booleans = {true, false}; + msg.setBooleans("booleans", booleans); + String [] strings = {"Hey", "What's going on?"}; + msg.setStrings("strings", strings); + + Message q = (Message) msg.cloneFlat(); + Message r = new Message(1234); + r.setString("I'm way", "down here!"); + q.setMessage("submessage", r); + msg.setMessage("message", q); + } else if (command.equals("s")) { + msg.what = PR_COMMAND_SETDATA; + msg.setMessage(st.nextToken(), new Message(1234)); + } else if (command.equals("k")) { + msg.what = PR_COMMAND_KICK; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("b")) { + msg.what = PR_COMMAND_ADDBANS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("B")) { + msg.what = PR_COMMAND_REMOVEBANS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("g")) { + msg.what = PR_COMMAND_GETDATA; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("p")) { + msg.what = PR_COMMAND_SETPARAMETERS; + msg.setString(st.nextToken(), ""); + } else if (command.equals("P")) { + msg.what = PR_COMMAND_GETPARAMETERS; + } else if (command.equals("d")) { + msg.what = PR_COMMAND_REMOVEDATA; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("D")) { + msg.what = PR_COMMAND_REMOVEPARAMETERS; + msg.setString(PR_NAME_KEYS, st.nextToken()); + } else if (command.equals("big")) { + msg.what = PR_COMMAND_PING; + int numFloats = 1*1024*1024; + float lotsafloats[] = new float[numFloats]; // 4MB worth of floats! + for (int i=0; i 0) + { + nextMessage = _messageQueue.removeFirstElement(); + messagesLeft = _messageQueue.size(); + } + else + { + _threadActive = false; + _myThread = null; + break; // bye bye! + } + } + + // Process the message (unsynchronized) + if (nextMessage instanceof ListControlMessage) + { + ListControlMessage lcm = (ListControlMessage) nextMessage; + if (lcm._add) + { + if (_listeners != null) _listeners.appendElement(new MessageQueue(lcm._listener)); + else + { + if (_listener != null) + { + // convert to multi-listener (asynchronous) mode + _listeners = new Queue(); + _listeners.appendElement(new MessageQueue(_listener)); + _listeners.appendElement(new MessageQueue(lcm._listener)); + _listener = null; + } + else _listener = lcm._listener; + } + } + else + { + if (lcm._listener != null) + { + if (_listeners != null) + { + for (int i=_listeners.size()-1; i>=0; i--) + { + if (((MessageQueue)_listeners.elementAt(i))._listener == lcm._listener) + { + _listeners.removeElementAt(i); + if (_listeners.size() == 1) + { + // revert back to single-listener (synchronous) mode + _listener = (MessageListener) _listeners.firstElement(); + _listeners = null; + } + break; + } + } + } + else if (_listener == lcm._listener) _listener = null; + } + else + { + _listeners = null; + _listener = null; + } + } + } + else + { + if (_listener != null) + { + try { + _listener.messageReceived(nextMessage, messagesLeft); + } + catch(Exception ex) { + ex.printStackTrace(); + } + } + else if (_listeners != null) + { + for (int i=_listeners.size()-1; i>=0; i--) + { + try { + ((MessageQueue)_listeners.elementAt(i)).messageReceived(nextMessage, messagesLeft); + } + catch(Exception ex) { + ex.printStackTrace(); + } + } + } + } + } + } + + /** This class is used to remotely interrupt() my Thread if it turns out to need interrupting + * (i.e. if an interrupt was requested before the Thread was started) + * This way the interrupt always affects the internal thread the same way, no matter what. + */ + private class ThreadInterruptor implements Runnable + { + public ThreadInterruptor(Thread thread) {_thread = thread;} + public void run() {_thread.interrupt();} + + private Thread _thread; + } + + /** Message class used to add or remove listeners */ + private class ListControlMessage + { + public ListControlMessage(MessageListener l, boolean add) + { + _listener = l; + _add = add; + } + + public MessageListener _listener; + public boolean _add; + } + + private Queue _messageQueue = new Queue(); // shared (synchronized this) + private MessageListener _listener = null; // used if we have exactly one listener + private Queue _listeners = null; // used if we have more than one listener + private boolean _threadActive = false; // shared (synchronized this) + private boolean _interruptPending = false; // shared (synchronized this) + private Thread _myThread = null; // set inside of run() (synchronized this) + private ThreadPool _threadPool = null; // if a non-default thread pool is to be used, it is set here +} + diff --git a/java_j2me/src/com/lcs/micromuscle/thread/ThreadPool.java b/java_j2me/src/com/lcs/micromuscle/thread/ThreadPool.java new file mode 100644 index 00000000..2aea3f26 --- /dev/null +++ b/java_j2me/src/com/lcs/micromuscle/thread/ThreadPool.java @@ -0,0 +1,113 @@ +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.TXT file for details. */ +package com.meyer.micromuscle.thread; + +import com.meyer.micromuscle.queue.Queue; + +/** A ThreadPool is an object that holds a number of threads + * and lets you re-use them. This lets us avoid having + * to terminate and re-spawn threads all the time. This + * class is used a lot by the MessageQueue class. + */ +public final class ThreadPool implements Runnable +{ + /** Creates a ThreadPool with a low-water mark of 2 threads, + * and a high-water mark of 5 threads + */ + public ThreadPool() {this(2, 5);} + + /** Create a new ThreadPool + * @param lowMark Threads will be retained until this many are present. + * @param highMark No more than this many threads will be allocated at once. + */ + public ThreadPool(int lowMark, int highMark) + { + _lowMark = lowMark; + _highMark = highMark; + } + + /** Schedules a task for execution. The task may begin + * right away, or wait until there is a thread available. + */ + public synchronized void startThread(Runnable target) + { + _tasks.appendElement(target); + if ((_idleThreadCount <= 0)&&(_threadCount < _highMark)) + { + _threadCount++; // note that the new thread exists + Thread t = new Thread(this); + t.start(); // and off he goes + } + notify(); // wake up a thread to handle our request + } + + /** A singleton ThreadPool, for convenience */ + public static ThreadPool getDefaultThreadPool() {return _defaultThreadPool;} + + /** Change the singleton ThreadPool if you want */ + public static void setDefaultThreadPool(ThreadPool p) {_defaultThreadPool = p;} + + /** Exposed as an implementation detail. Please ignore. */ + public void run() + { + boolean keepGoing = true; + + while(keepGoing) + { + // Keep running tasks as long as any are available + while(true) + { + Runnable nextTask = null; + + // Critical section: Get the next task, if any + synchronized(this) + { + if (_tasks.size() > 0) nextTask = (Runnable) _tasks.removeFirstElement(); + else + { + if (_threadCount > _lowMark) + { + _threadCount--; + keepGoing = false; // this thread is going away -- there are too many threads! + } + } + } + + // Important that this is done in an unsynchronized section, to avoid locking the pool + if (nextTask != null) + { + try { + nextTask.run(); // do it! + } + catch(Exception e) { + e.printStackTrace(); + } + } + else break; // go back to sleep until more tasks are available + } + + if (keepGoing) + { + // No more tasks left: go to sleep + synchronized(this) + { + _idleThreadCount++; // this thread is idle again + try { + wait(); // wait until startThread() is called again. + } + catch(InterruptedException ex) { + ex.printStackTrace(); // shouldn't ever happen + } + _idleThreadCount--; // now it's busy + } + } + } + } + + private static ThreadPool _defaultThreadPool = new ThreadPool(); // singleton + + private Queue _tasks = new Queue(); // Runnables that need to be run + private int _idleThreadCount = 0; // how many threads are idle + private int _threadCount = 0; // how many threads are in the pool + private int _lowMark; // minimum # of threads to retain + private int _highMark; // maximum # of threads to allocate +} diff --git a/java_j2me/support/readme.txt b/java_j2me/support/readme.txt new file mode 100644 index 00000000..50d75ed3 --- /dev/null +++ b/java_j2me/support/readme.txt @@ -0,0 +1,2 @@ +Please place the antenna-bin-1.0.0.jar file here! +(You can download it from http://antenna.sourceforge.net/ ) diff --git a/message/Message.cpp b/message/Message.cpp new file mode 100644 index 00000000..bea30dae --- /dev/null +++ b/message/Message.cpp @@ -0,0 +1,2334 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "util/ByteBuffer.h" +#include "util/Queue.h" +#include "message/Message.h" + +namespace muscle { + +static void DoIndents(uint32 num, String & s) {for (uint32 i=0; i _pool##X; DECLARECLONE(X) +#endif + +MessageRef GetMessageFromPool(uint32 what) +{ + MessageRef ref(_messagePool.ObtainObject()); + if (ref()) ref()->what = what; + return ref; +} + +MessageRef GetMessageFromPool(const Message & copyMe) +{ + MessageRef ref(_messagePool.ObtainObject()); + if (ref()) *(ref()) = copyMe; + return ref; +} + +MessageRef GetMessageFromPool(const uint8 * flatBytes, uint32 numBytes) +{ + MessageRef ref(_messagePool.ObtainObject()); + if ((ref())&&(ref()->Unflatten(flatBytes, numBytes) != B_NO_ERROR)) ref.Reset(); + return ref; +} + +MessageRef GetMessageFromPool(ObjectPool & pool, uint32 what) +{ + MessageRef ref(pool.ObtainObject()); + if (ref()) ref()->what = what; + return ref; +} + +MessageRef GetMessageFromPool(ObjectPool & pool, const Message & copyMe) +{ + MessageRef ref(pool.ObtainObject()); + if (ref()) *(ref()) = copyMe; + return ref; +} + +MessageRef GetMessageFromPool(ObjectPool & pool, const uint8 * flatBytes, uint32 numBytes) +{ + MessageRef ref(pool.ObtainObject()); + if ((ref())&&(ref()->Unflatten(flatBytes, numBytes) != B_NO_ERROR)) ref.Reset(); + return ref; +} + +MessageRef GetLightweightCopyOfMessageFromPool(const Message & copyMe) +{ + MessageRef ref(_messagePool.ObtainObject()); + if (ref()) ref()->BecomeLightweightCopyOf(copyMe); + return ref; +} + +MessageRef GetLightweightCopyOfMessageFromPool(ObjectPool & pool, const Message & copyMe) +{ + MessageRef ref(pool.ObtainObject()); + if (ref()) ref()->BecomeLightweightCopyOf(copyMe); + return ref; +} + +/* This class is for the private use of the Message class only! + * It represents one "name" of the message, which can in turn represent 1 or more + * data items of a single type. + */ +class AbstractDataArray : public FlatCountable, private CountedObject +{ +public: + // Should add the given item to our internal array. + virtual status_t AddDataItem(const void * data, uint32 size) = 0; + + // Should remove the (index)'th item from our internal array. + virtual status_t RemoveDataItem(uint32 index) = 0; + + // Prepends the given data item to the beginning of our array. + virtual status_t PrependDataItem(const void * data, uint32 size) = 0; + + // Clears the array + virtual void Clear(bool releaseDataBuffers) = 0; + + // Normalizes the array + virtual void Normalize() = 0; + + // Sets (setDataLoc) to point to the (index)'th item in our array. + // Result is not guaranteed to remain valid after this object is modified. + virtual status_t FindDataItem(uint32 index, const void ** setDataLoc) const = 0; + + // Should replace the (index)'th data item in the array with (data). + virtual status_t ReplaceDataItem(uint32 index, const void * data, uint32 size) = 0; + + // Returns the size (in bytes) of the item in the (index)'th slot. + virtual uint32 GetItemSize(uint32 index) const = 0; + + // Returns the number of items currently in the array + virtual uint32 GetNumItems() const = 0; + + // Convenience methods + bool HasItems() const {return (GetNumItems()>0);} + bool IsEmpty() const {return (GetNumItems()==0);} + + // Returns a 32-bit checksum for this array + virtual uint32 CalculateChecksum(bool countNonFlattenableFields) const = 0; + + // Returns true iff all elements in the array have the same size + virtual bool ElementsAreFixedSize() const = 0; + + // Flattenable interface + virtual bool IsFixedSize() const {return false;} + + // returns a separate (deep) copy of this array + virtual RefCountableRef Clone() const = 0; + + // Returns true iff this array should be included when flattening. + virtual bool IsFlattenable() const = 0; + + // For debugging: returns a description of our contents as a String + virtual void AddToString(String & s, uint32 maxRecurseLevel, int indent) const = 0; + + // Returns true iff this array is identical to (rhs). If (compareContents) is false, + // only the array lengths and type codes are checked, not the data itself. + bool IsEqualTo(const AbstractDataArray * rhs, bool compareContents) const + { + if ((TypeCode() != rhs->TypeCode())||(GetNumItems() != rhs->GetNumItems())) return false; + return compareContents ? AreContentsEqual(rhs) : true; + } + +protected: + /** Must be implemented by each subclass to return true iff (rhs) is of the same type + * and has the same data as (*this). The TypeCode() and GetNumItems() of (rhs) are + * guaranteed to equal those of this AbstractDataArray. Called by IsEqualTo(). + */ + virtual bool AreContentsEqual(const AbstractDataArray * rhs) const = 0; +}; + +template class FixedSizeDataArray : public AbstractDataArray +{ +public: + FixedSizeDataArray() {/* empty */} + virtual ~FixedSizeDataArray() {/* empty */} + + virtual uint32 GetNumItems() const {return _data.GetNumItems();} + + virtual void Clear(bool releaseDataBuffers) {_data.Clear(releaseDataBuffers);} + virtual void Normalize() {_data.Normalize();} + + virtual status_t AddDataItem(const void * item, uint32 size) + { + return (size == sizeof(DataType)) ? (item ? _data.AddTail(*((DataType *)item)) : _data.AddTail()) : B_ERROR; + } + + virtual status_t PrependDataItem(const void * item, uint32 size) + { + return (size == sizeof(DataType)) ? (item ? _data.AddHead(*((DataType *)item)) : _data.AddHead()) : B_ERROR; + } + + virtual uint32 GetItemSize(uint32 /*index*/) const {return sizeof(DataType);} + virtual status_t RemoveDataItem(uint32 index) {return _data.RemoveItemAt(index);} + + virtual status_t FindDataItem(uint32 index, const void ** setDataLoc) const + { + if (index < _data.GetNumItems()) + { + *setDataLoc = &_data[index]; + return (*setDataLoc) ? B_NO_ERROR : B_ERROR; + } + return B_ERROR; + } + + virtual status_t ReplaceDataItem(uint32 index, const void * data, uint32 size) + { + return (size == sizeof(DataType)) ? _data.ReplaceItemAt(index, *((DataType *)data)) : B_ERROR; + } + + virtual bool ElementsAreFixedSize() const {return true;} + + virtual bool IsFlattenable() const {return true;} + + const DataType & ItemAt(int i) const {return _data[i];} + + FixedSizeDataArray & operator=(const FixedSizeDataArray & rhs) + { + if (this != &rhs) _data = rhs._data; + return *this; + } + +protected: + virtual bool AreContentsEqual(const AbstractDataArray * rhs) const + { + const FixedSizeDataArray * trhs = dynamic_cast *>(rhs); + if (trhs == NULL) return false; + + for (int32 i=GetNumItems()-1; i>=0; i--) if (ItemAt(i) != trhs->ItemAt(i)) return false; + return true; + } + + Queue _data; +}; + +// An array of ephemeral objects that won't be flattened +class TagDataArray : public FixedSizeDataArray +{ +public: + TagDataArray() {/* empty */} + virtual ~TagDataArray() {/* empty */} + + virtual void Flatten(uint8 *) const + { + MCRASH("Message::TagDataArray:Flatten() This method should never be called!"); + } + + // Flattenable interface + virtual uint32 FlattenedSize() const {return 0;} // tags don't get flattened, so they take up no space + + virtual status_t Unflatten(const uint8 *, uint32) + { + MCRASH("Message::TagDataArray:Unflatten() This method should never be called!"); + return B_NO_ERROR; // just to keep the compiler happy + } + + virtual uint32 TypeCode() const {return B_TAG_TYPE;} + + virtual RefCountableRef Clone() const; + + virtual bool IsFlattenable() const {return false;} + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + // This is the best we can do, since we don't know our elements' types + return TypeCode() + GetNumItems(); + } + +protected: + virtual void AddToString(String & s, uint32, int indent) const + { + uint32 numItems = GetNumItems(); + for (uint32 i=0; i class FixedSizeFlatObjectArray : public FixedSizeDataArray +{ +public: + FixedSizeFlatObjectArray() + { + // empty + } + + virtual ~FixedSizeFlatObjectArray() + { + // empty + } + + virtual void Flatten(uint8 * buffer) const + { + uint32 numItems = this->_data.GetNumItems(); + for (uint32 i=0; i_data[i].Flatten(buffer); + buffer += FlatItemSize; + } + } + + // Flattenable interface + virtual uint32 FlattenedSize() const {return this->_data.GetNumItems() * FlatItemSize;} + + virtual status_t Unflatten(const uint8 * buffer, uint32 numBytes) + { + this->_data.Clear(); + if (numBytes % FlatItemSize) + { + LogTime(MUSCLE_LOG_DEBUG, "FixedSizeDataArray %p: Unexpected numBytes " UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC "\n", this, numBytes, FlatItemSize); + return B_ERROR; // length must be an even multiple of item size, or something's wrong! + } + + DataType temp; + uint32 numItems = numBytes / FlatItemSize; + if (this->_data.EnsureSize(numItems) == B_NO_ERROR) + { + for (uint32 i=0; i_data.AddTail(temp) != B_NO_ERROR)) + { + LogTime(MUSCLE_LOG_DEBUG, "FixedSizeDataArray %p: Error unflattened item " UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC "\n", this, i, numItems); + return B_ERROR; + } + buffer += FlatItemSize; + } + return B_NO_ERROR; + } + else return B_ERROR; + } + + virtual uint32 TypeCode() const {return ItemTypeCode;} + +protected: + virtual void AddToString(String & s, uint32, int indent) const + { + uint32 numItems = this->GetNumItems(); + for (uint32 i=0; i_data[i]); + } + } + + virtual void AddItemToString(String & s, const DataType & item) const = 0; +}; + +/* This class handles storage of all the primitive numeric types for us. */ +template class PrimitiveTypeDataArray : public FixedSizeDataArray +{ +public: + PrimitiveTypeDataArray() {/* empty */} + virtual ~PrimitiveTypeDataArray() {/* empty */} + + virtual void Flatten(uint8 * buffer) const + { + DataType * dBuf = (DataType *) buffer; + switch(this->_data.GetNumItems()) + { + case 0: + // do nothing + break; + + case 1: + ConvertToNetworkByteOrder(dBuf, this->_data.HeadPointer(), 1); + break; + + default: + { + uint32 len0 = 0; + const DataType * array0 = this->_data.GetArrayPointer(0, len0); + if (array0) ConvertToNetworkByteOrder(dBuf, array0, len0); + + uint32 len1; + const DataType * array1 = this->_data.GetArrayPointer(1, len1); + if (array1) ConvertToNetworkByteOrder(&dBuf[len0], array1, len1); + } + break; + } + } + + virtual status_t Unflatten(const uint8 * buffer, uint32 numBytes) + { + this->_data.Clear(); + if (numBytes % sizeof(DataType)) + { + LogTime(MUSCLE_LOG_DEBUG, "PrimitiveTypeDataArray %p: Unexpected numBytes " UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC "\n", this, numBytes, (uint32) sizeof(DataType)); + return B_ERROR; // length must be an even multiple of item size, or something's wrong! + } + + uint32 numItems = numBytes / sizeof(DataType); + if (this->_data.EnsureSize(numItems, true) == B_NO_ERROR) + { + // Note that typically you can't rely on the contents of a Queue object to + // be stored in a single, contiguous array like this, but in this case we've + // called Clear() and then EnsureSize(), so we know that the array's headPointer + // is at the front of the array, and we are safe to do this. --jaf + ConvertFromNetworkByteOrder(this->_data.HeadPointer(), (DataType *)buffer, numItems); + return B_NO_ERROR; + } + else return B_ERROR; + } + + // Flattenable interface + virtual uint32 FlattenedSize() const {return this->_data.GetNumItems() * sizeof(DataType);} + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const = 0; + virtual void ConvertFromNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const = 0; + + virtual const char * GetFormatString() const = 0; + + virtual void AddToString(String & s, uint32, int indent) const + { + uint32 numItems = this->GetNumItems(); + for (uint32 i=0; iItemAt(i)); + char temp2[150]; sprintf(temp2, " " UINT32_FORMAT_SPEC ". [%s]\n", i, temp1); s += temp2; + } + } +}; + +class PointDataArray : public FixedSizeFlatObjectArray +{ +public: + PointDataArray() {/* empty */} + virtual ~PointDataArray() {/* empty */} + virtual RefCountableRef Clone() const; + virtual void AddItemToString(String & s, const Point & p) const + { + char buf[128]; + sprintf(buf, "Point: %f %f\n", p.x(), p.y()); + s += buf; + } + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*_data[i].CalculateChecksum()); + return ret; + } +}; +DECLAREFIELDTYPE(PointDataArray); + +class RectDataArray : public FixedSizeFlatObjectArray +{ +public: + RectDataArray() {/* empty */} + virtual ~RectDataArray() {/* empty */} + virtual RefCountableRef Clone() const; + virtual void AddItemToString(String & s, const Rect & r) const + { + char buf[256]; + sprintf(buf, "Rect: leftTop=(%f,%f) rightBottom=(%f,%f)\n", r.left(), r.top(), r.right(), r.bottom()); + s += buf; + } + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*_data[i].CalculateChecksum()); + return ret; + } +}; +DECLAREFIELDTYPE(RectDataArray); + +class Int8DataArray : public PrimitiveTypeDataArray +{ +public: + Int8DataArray() {/* empty */} + virtual ~Int8DataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_INT8_TYPE;} + + virtual const char * GetFormatString() const {return "%i";} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*((uint32)_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + memcpy(writeToHere, readFromHere, numItems); // no translation required, really + } + + virtual void ConvertFromNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + memcpy(writeToHere, readFromHere, numItems); // no translation required, really + } +}; +DECLAREFIELDTYPE(Int8DataArray); + +class BoolDataArray : public PrimitiveTypeDataArray +{ +public: + BoolDataArray() {/* empty */} + virtual ~BoolDataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_BOOL_TYPE;} + + virtual const char * GetFormatString() const {return "%i";} + + virtual RefCountableRef Clone() const; + + virtual void Flatten(uint8 * buffer) const + { + uint8 * dBuf = (uint8 *) buffer; + uint32 numItems = _data.GetNumItems(); + for (uint32 i=0; i=0; i--) ret += ((i+1)*(_data[i] ? 1 : 0)); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void *, const void *, uint32) const + { + MCRASH("BoolDataArray::ConvertToNetworkByteOrder should never be called"); + } + + virtual void ConvertFromNetworkByteOrder(void *, const void *, uint32) const + { + MCRASH("BoolDataArray::ConvertFromNetworkByteOrder should never be called"); + } +}; +DECLAREFIELDTYPE(BoolDataArray); + +class Int16DataArray : public PrimitiveTypeDataArray +{ +public: + Int16DataArray() {/* empty */} + virtual ~Int16DataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_INT16_TYPE;} + + virtual const char * GetFormatString() const {return "%i";} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*((uint32)_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + int16 * writeToHere16 = (int16 *) writeToHere; + const int16 * readFromHere16 = (const int16 *) readFromHere; + for (uint32 i=0; i +{ +public: + Int32DataArray() {/* empty */} + virtual ~Int32DataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_INT32_TYPE;} + + virtual const char * GetFormatString() const {return INT32_FORMAT_SPEC;} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*((uint32)_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + int32 * writeToHere32 = (int32 *) writeToHere; + const int32 * readFromHere32 = (const int32 *) readFromHere; + for (uint32 i=0; i +{ +public: + Int64DataArray() {/* empty */} + virtual ~Int64DataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_INT64_TYPE;} + + virtual const char * GetFormatString() const {return INT64_FORMAT_SPEC;} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*CalculateChecksumForUint64(_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + int64 * writeToHere64 = (int64 *) writeToHere; + const int64 * readFromHere64 = (const int64 *) readFromHere; + for (uint32 i=0; i +{ +public: + FloatDataArray() {/* empty */} + virtual ~FloatDataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_FLOAT_TYPE;} + + virtual const char * GetFormatString() const {return "%f";} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*CalculateChecksumForFloat(_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + int32 * writeToHere32 = (int32 *) writeToHere; // yeah, they're really floats, but no need to worry about that here + const int32 * readFromHere32 = (const int32 *) readFromHere; + for (uint32 i=0; i +{ +public: + DoubleDataArray() {/* empty */} + virtual ~DoubleDataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_DOUBLE_TYPE;} + + virtual const char * GetFormatString() const {return "%lf";} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) ret += ((i+1)*CalculateChecksumForDouble(_data[i])); + return ret; + } + +protected: + virtual void ConvertToNetworkByteOrder(void * writeToHere, const void * readFromHere, uint32 numItems) const + { + int64 * writeToHere64 = (int64 *) writeToHere; // yeah, they're really doubles, but no need to worry about that here + const int64 * readFromHere64 = (const int64 *) readFromHere; + for (uint32 i=0; i +{ +public: + PointerDataArray() {/* empty */} + virtual ~PointerDataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_POINTER_TYPE;} + + virtual const char * GetFormatString() const {return "%p";} + + virtual bool IsFlattenable() const {return false;} + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + return TypeCode() + GetNumItems(); // Best we can do, since pointer equivalence is not well defined + } + +protected: + virtual void ConvertToNetworkByteOrder(void *, const void *, uint32) const + { + MCRASH("PointerDataArray::ConvertToNetworkByteOrder should never be called"); + } + + virtual void ConvertFromNetworkByteOrder(void *, const void *, uint32) const + { + MCRASH("PointerDataArray::ConvertFromNetworkByteOrder should never be called"); + } +}; +DECLAREFIELDTYPE(PointerDataArray); + +// An abstract array of FlatCountableRefs. +template class FlatCountableRefDataArray : public FixedSizeDataArray +{ +public: + FlatCountableRefDataArray() {/* empty */} + virtual ~FlatCountableRefDataArray() {/* empty */} + + virtual uint32 GetItemSize(uint32 index) const + { + const FlatCountable * msg = this->ItemAt(index)(); + return msg ? msg->FlattenedSize() : 0; + } + + virtual uint32 TypeCode() const {return ItemTypeCode;} + + virtual bool ElementsAreFixedSize() const {return false;} + + /** Whether or not we should write the number-of-items element when we flatten this array. + * Older versions of muscle didn't do this for MessageDataArray objects, so we need to + * maintain that behaviour so that we don't break compatibility. (bleah) + */ + virtual bool ShouldWriteNumItems() const {return true;} + + virtual void Flatten(uint8 * buffer) const + { + uint32 writeOffset = 0; + uint32 numItems = this->_data.GetNumItems(); + + // Conditional to allow maintaining backwards compatibility with old versions of muscle's MessageDataArrays (sigh) + if (ShouldWriteNumItems()) + { + uint32 writeNumElements = B_HOST_TO_LENDIAN_INT32(numItems); + this->WriteData(buffer, &writeOffset, &writeNumElements, sizeof(writeNumElements)); + } + + for (uint32 i=0; iItemAt(i)(); + if (next) + { + uint32 fs = next->FlattenedSize(); + uint32 writeFs = B_HOST_TO_LENDIAN_INT32(fs); + this->WriteData(buffer, &writeOffset, &writeFs, sizeof(writeFs)); + next->Flatten(&buffer[writeOffset]); + writeOffset += fs; + } + } + } + + // Flattenable interface + virtual uint32 FlattenedSize() const + { + uint32 numItems = this->GetNumItems(); + uint32 count = (numItems+(ShouldWriteNumItems()?1:0))*sizeof(uint32); + for (uint32 i=0; i +{ +public: + ByteBufferDataArray() : _typeCode(B_RAW_TYPE) {/* empty */} + virtual ~ByteBufferDataArray() {/* empty */} + + /** Sets our type code. Typically called after using the default ctor. */ + void SetTypeCode(uint32 tc) {_typeCode = tc;} + + virtual uint32 TypeCode() const {return _typeCode;} + + virtual status_t Unflatten(const uint8 * buffer, uint32 numBytes) + { + Clear(false); + + uint32 readOffset = 0; + + uint32 numItems; + if (ReadData(buffer, numBytes, &readOffset, &numItems, sizeof(numItems)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "ByteBufferDataArray %p: Error reading numItems (numBytes=" UINT32_FORMAT_SPEC ")\n", this, numBytes); + return B_ERROR; + } + numItems = B_LENDIAN_TO_HOST_INT32(numItems); + + for (uint32 i=0; i numBytes) + { + LogTime(MUSCLE_LOG_DEBUG, "ByteBufferDataArray %p: Item size too large (i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ", readOffset=" UINT32_FORMAT_SPEC ", numBytes=" UINT32_FORMAT_SPEC ", readFs=" UINT32_FORMAT_SPEC ")\n", this, i, numItems, readOffset, numBytes, readFs); + return B_ERROR; // message size too large for our buffer... corruption? + } + FlatCountableRef fcRef(GetByteBufferFromPool(readFs, &buffer[readOffset]).GetRefCountableRef(), true); + if ((fcRef())&&(AddDataItem(&fcRef, sizeof(fcRef)) == B_NO_ERROR)) readOffset += readFs; + else return B_ERROR; + } + return B_NO_ERROR; + } + + virtual void AddToString(String & s, uint32, int indent) const + { + uint32 numItems = GetNumItems(); + for (uint32 i=0; i(fc); + if ((bb == NULL)&&(fc)) + { + temp.SetNumBytes(fc->FlattenedSize(), false); + if (temp()) + { + fc->Flatten((uint8*)temp()); + bb = &temp; + } + } + + if (bb) + { + sprintf(buf, "[flattenedSize=" UINT32_FORMAT_SPEC "] ", bb->GetNumBytes()); + s += buf; + uint32 printBytes = muscleMin(bb->GetNumBytes(), (uint32)10); + if (printBytes > 0) + { + s += '['; + for (uint32 j=0; jGetBuffer())[j], (j 10) s += " ..."; + s += ']'; + } + } + else s += "[NULL]"; + + s += '\n'; + } + } + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool /*countNonFlattenableFields*/) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) + { + const ByteBuffer * buf = dynamic_cast(_data[i]()); // TODO: possibly make this a static cast? + if (buf) ret += ((i+1)*buf->CalculateChecksum()); + } + return ret; + } + +protected: + /** Overridden to compare objects instead of merely the pointers to them */ + virtual bool AreContentsEqual(const AbstractDataArray * rhs) const + { + const ByteBufferDataArray * trhs = dynamic_cast(rhs); + if (trhs == NULL) return false; + + for (int32 i=GetNumItems()-1; i>=0; i--) + { + const ByteBuffer * myBuf = (const ByteBuffer *) this->ItemAt(i)(); + const ByteBuffer * hisBuf = (const ByteBuffer *) trhs->ItemAt(i)(); + if (((myBuf != NULL)!=(hisBuf != NULL))||((myBuf)&&(*myBuf != *hisBuf))) return false; + } + return true; + } + +private: + uint32 _typeCode; +}; +DECLAREFIELDTYPE(ByteBufferDataArray); + +class MessageDataArray : public FlatCountableRefDataArray +{ +public: + MessageDataArray() {/* empty */} + virtual ~MessageDataArray() {/* empty */} + + /** For backwards compatibility with older muscle streams */ + virtual bool ShouldWriteNumItems() const {return false;} + + virtual status_t Unflatten(const uint8 * buffer, uint32 numBytes) + { + Clear(false); + + uint32 readOffset = 0; + while(readOffset < numBytes) + { + uint32 readFs; + if (ReadData(buffer, numBytes, &readOffset, &readFs, sizeof(readFs)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "MessageDataArray %p: Read of sub-message size failed (readOffset=" UINT32_FORMAT_SPEC ", numBytes=" UINT32_FORMAT_SPEC ")\n", this, readOffset, numBytes); + return B_ERROR; + } + + readFs = B_LENDIAN_TO_HOST_INT32(readFs); + if (readOffset + readFs > numBytes) + { + LogTime(MUSCLE_LOG_DEBUG, "MessageDataArray %p: Sub-message size too large (readOffset=" UINT32_FORMAT_SPEC ", numBytes=" UINT32_FORMAT_SPEC ", readFs=" UINT32_FORMAT_SPEC ")\n", this, readOffset, numBytes, readFs); + return B_ERROR; // message size too large for our buffer... corruption? + } + MessageRef nextMsg = GetMessageFromPool(); + if (nextMsg()) + { + if (nextMsg()->Unflatten(&buffer[readOffset], readFs) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "MessageDataArray %p: Sub-message unflatten failed (readOffset=" UINT32_FORMAT_SPEC ", numBytes=" UINT32_FORMAT_SPEC ", readFs=" UINT32_FORMAT_SPEC ")\n", this, readOffset, numBytes, readFs); + return B_ERROR; + } + if (AddDataItem(&nextMsg, sizeof(nextMsg)) != B_NO_ERROR) return B_ERROR; + readOffset += readFs; + } + else return B_ERROR; + } + return B_NO_ERROR; + } + + virtual void AddToString(String & s, uint32 maxRecurseLevel, int indent) const + { + uint32 numItems = GetNumItems(); + for (uint32 i=0; iGetItemPointer(); + if (msg) + { + char tcbuf[5]; MakePrettyTypeCodeString(msg->what, tcbuf); + sprintf(buf, "[what='%s' (" INT32_FORMAT_SPEC "/0x" XINT32_FORMAT_SPEC "), flattenedSize=" UINT32_FORMAT_SPEC ", numFields=" UINT32_FORMAT_SPEC "]\n", tcbuf, msg->what, msg->what, itemSize, msg->GetNumNames()); + s += buf; + + if (maxRecurseLevel > 0) msg->AddToString(s, maxRecurseLevel-1, indent+3); + } + else s += "[NULL]\n"; + } + else s += "[]\n"; + } + } + + virtual RefCountableRef Clone() const; + + virtual uint32 CalculateChecksum(bool countNonFlattenableFields) const + { + uint32 ret = TypeCode() + GetNumItems(); + for (int32 i=GetNumItems()-1; i>=0; i--) + { + const MessageRef & msg = _data[i]; + if (msg()) ret += ((i+1)*(msg()->CalculateChecksum(countNonFlattenableFields))); + } + return ret; + } + +protected: + /** Overridden to compare objects instead of merely the pointers to them */ + virtual bool AreContentsEqual(const AbstractDataArray * rhs) const + { + const MessageDataArray * trhs = dynamic_cast(rhs); + if (trhs == NULL) return false; + + for (int32 i=GetNumItems()-1; i>=0; i--) + { + const Message * myMsg = (const Message *) this->ItemAt(i)(); + const Message * hisMsg = (const Message *) trhs->ItemAt(i)(); + if (((myMsg != NULL)!=(hisMsg != NULL))||((myMsg)&&(*myMsg != *hisMsg))) return false; + } + return true; + } +}; +DECLAREFIELDTYPE(MessageDataArray); + +// An array of Flattenable objects which are *not* guaranteed to all have the same flattened size. +template class VariableSizeFlatObjectArray : public FixedSizeDataArray +{ +public: + VariableSizeFlatObjectArray() {/* empty */} + virtual ~VariableSizeFlatObjectArray() {/* empty */} + + virtual uint32 GetItemSize(uint32 index) const {return this->ItemAt(index).FlattenedSize();} + virtual bool ElementsAreFixedSize() const {return false;} + + virtual void Flatten(uint8 * buffer) const + { + // Format: 0. number of entries (4 bytes) + // 1. entry size in bytes (4 bytes) + // 2. entry data (n bytes) + // (repeat 1. and 2. as necessary) + uint32 numElements = this->GetNumItems(); + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(numElements); + uint32 writeOffset = 0; + + this->WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + for (uint32 i=0; iItemAt(i); + uint32 nextElementBytes = s.FlattenedSize(); + networkByteOrder = B_HOST_TO_LENDIAN_INT32(nextElementBytes); + this->WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // write element data + s.Flatten(&buffer[writeOffset]); + writeOffset += nextElementBytes; + } + } + + virtual uint32 FlattenedSize() const + { + uint32 num = this->GetNumItems(); + uint32 sum = (num+1)*sizeof(uint32); // 1 uint32 for the count, plus 1 per entry for entry-size + for (uint32 i=0; iItemAt(i).FlattenedSize(); + return sum; + } + + virtual status_t Unflatten(const uint8 * buffer, uint32 inputBufferBytes) + { + this->Clear(false); + + uint32 networkByteOrder; + uint32 readOffset = 0; + + if (this->ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "VariableSizeFlatObjectArray %p: Read of numElements failed (inputBufferBytes=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes); + return B_ERROR; + } + + uint32 numElements = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if (this->_data.EnsureSize(numElements) != B_NO_ERROR) return B_ERROR; + for (uint32 i=0; iReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "VariableSizeFlatObjectArray %p: Read of element size failed (inputBufferBytes=" UINT32_FORMAT_SPEC ", i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, i, numElements); + return B_ERROR; + } + uint32 elementSize = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if (elementSize == 0) + { + LogTime(MUSCLE_LOG_DEBUG, "VariableSizeFlatObjectArray %p: Element size was zero! (inputBufferBytes=" UINT32_FORMAT_SPEC ", i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, i, numElements); + return B_ERROR; // it should always have at least the trailing NUL byte! + } + + // read element data + if ((readOffset + elementSize > inputBufferBytes)||(this->_data.AddTail() != B_NO_ERROR)||(this->_data.TailPointer()->Unflatten(&buffer[readOffset], elementSize) != B_NO_ERROR)) + { + LogTime(MUSCLE_LOG_DEBUG, "VariableSizeFlatObjectArray %p: Element size was too large! (inputBufferBytes=" UINT32_FORMAT_SPEC ", i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ", readOffset=" UINT32_FORMAT_SPEC ", elementSize=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, i, numElements, readOffset, elementSize); + return B_ERROR; + } + readOffset += elementSize; + } + return B_NO_ERROR; + } +}; + +class StringDataArray : public VariableSizeFlatObjectArray +{ +public: + StringDataArray() {/* empty */} + virtual ~StringDataArray() {/* empty */} + + virtual uint32 TypeCode() const {return B_STRING_TYPE;} + + virtual RefCountableRef Clone() const; + + virtual void AddToString(String & s, uint32, int indent) const + { + uint32 numItems = GetNumItems(); + for (uint32 i=0; i=0; i--) ret += ((i+1)*(_data[i].CalculateChecksum())); + return ret; + } +}; +DECLAREFIELDTYPE(StringDataArray); + +void MessageFieldNameIterator :: SkipNonMatchingFieldNames() +{ + // Gotta move ahead until we find the first matching value! + while((_iter.HasData())&&(static_cast(_iter.GetValue()())->TypeCode() != _typeCode)) _iter++; +} + +Message & Message :: operator=(const Message & rhs) +{ + TCHECKPOINT; + + if (this != &rhs) + { + Clear(); + what = rhs.what; + for (HashtableIterator it(rhs._entries, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + RefCountableRef clone = static_cast(it.GetValue()())->Clone(); + if (clone()) (void) _entries.Put(it.GetKey(), clone); + } + } + return *this; +} + +status_t Message :: GetInfo(const String & fieldName, uint32 * type, uint32 * c, bool * fixedSize) const +{ + const AbstractDataArray * array = GetArray(fieldName, B_ANY_TYPE); + if (array == NULL) return B_ERROR; + if (type) *type = array->TypeCode(); + if (c) *c = array->GetNumItems(); + if (fixedSize) *fixedSize = array->ElementsAreFixedSize(); + return B_NO_ERROR; +} + +uint32 Message :: GetNumNames(uint32 type) const +{ + if (type == B_ANY_TYPE) return _entries.GetNumItems(); + + // oops, gotta count just the entries of the given type + uint32 total = 0; + for (HashtableIterator it(_entries, HTIT_FLAG_NOREGISTER); it.HasData(); it++) if ((static_cast(it.GetValue()()))->TypeCode() == type) total++; + return total; +} + +void Message :: PrintToStream(FILE * optFile, uint32 maxRecurseLevel, int indent) const +{ + String s; AddToString(s, maxRecurseLevel, indent); + fprintf(optFile?optFile:stdout, "%s", s()); +} + +String Message :: ToString(uint32 maxRecurseLevel, int indent) const +{ + String s; + AddToString(s, maxRecurseLevel, indent); + return s; +} + +void Message :: AddToString(String & s, uint32 maxRecurseLevel, int indent) const +{ + TCHECKPOINT; + + String ret; + + char prettyTypeCodeBuf[5]; + MakePrettyTypeCodeString(what, prettyTypeCodeBuf); + + char buf[128]; + DoIndents(indent,s); + sprintf(buf, "Message: what='%s' (" INT32_FORMAT_SPEC "/0x" XINT32_FORMAT_SPEC "), entryCount=" INT32_FORMAT_SPEC ", flatSize=" UINT32_FORMAT_SPEC " checksum=" UINT32_FORMAT_SPEC "\n", prettyTypeCodeBuf, what, what, GetNumNames(B_ANY_TYPE), FlattenedSize(), CalculateChecksum()); + s += buf; + + for (HashtableIterator iter(_entries, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + const AbstractDataArray * nextValue = static_cast(iter.GetValue()()); + uint32 tc = nextValue->TypeCode(); + MakePrettyTypeCodeString(tc, prettyTypeCodeBuf); + DoIndents(indent,s); + s += " Entry: Name=["; + s += iter.GetKey(); + sprintf(buf, "], GetNumItems()=" INT32_FORMAT_SPEC ", TypeCode()='%s' (" INT32_FORMAT_SPEC ") flatSize=" UINT32_FORMAT_SPEC " checksum=" UINT32_FORMAT_SPEC "\n", nextValue->GetNumItems(), prettyTypeCodeBuf, tc, nextValue->FlattenedSize(), nextValue->CalculateChecksum(false)); + s += buf; + nextValue->AddToString(s, maxRecurseLevel, indent); + } +} + +// Returns an pointer to a held array of the given type, if it exists. If (tc) is B_ANY_TYPE, then any type array is acceptable. +AbstractDataArray * Message :: GetArray(const String & arrayName, uint32 tc) +{ + RefCountableRef * array; + return (((array = _entries.Get(arrayName)) != NULL)&&((tc == B_ANY_TYPE)||(tc == (static_cast(array->GetItemPointer()))->TypeCode()))) ? (static_cast(array->GetItemPointer())) : NULL; +} + +// Returns a read-only pointer to a held array of the given type, if it exists. If (tc) is B_ANY_TYPE, then any type array is acceptable. +const AbstractDataArray * Message :: GetArray(const String & arrayName, uint32 tc) const +{ + const RefCountableRef * array; + return (((array = _entries.Get(arrayName)) != NULL)&&((tc == B_ANY_TYPE)||(tc == (static_cast(array->GetItemPointer()))->TypeCode()))) ? (static_cast(array->GetItemPointer())) : NULL; +} + + +// Called by FindFlat(), which (due to its templated nature) can't access this info directly +const AbstractDataArray * Message :: GetArrayAndTypeCode(const String & arrayName, uint32 index, uint32 * retTC) const +{ + const RefCountableRef * aRef = _entries.Get(arrayName); + if (aRef) + { + const AbstractDataArray * ada = static_cast(aRef->GetItemPointer()); + if (index < ada->GetNumItems()) + { + *retTC = ada->TypeCode(); + return ada; + } + } + return NULL; +} + +// Returns an pointer to a held array of the given type, if it exists. If (tc) is B_ANY_TYPE, then any type array is acceptable. +RefCountableRef Message :: GetArrayRef(const String & arrayName, uint32 tc) const +{ + RefCountableRef array; + if ((_entries.Get(arrayName, array) == B_NO_ERROR)&&(tc != B_ANY_TYPE)&&(tc != ((const AbstractDataArray *)array())->TypeCode())) array.Reset(); + return array; +} + +AbstractDataArray * Message :: GetOrCreateArray(const String & arrayName, uint32 tc) +{ + TCHECKPOINT; + + { + AbstractDataArray * nextEntry = GetArray(arrayName, tc); + if (nextEntry) return nextEntry; + } + + // Make sure the problem isn't that there already exists an array, but of the wrong type... + // If that's the case, we can't create a like-names array of a different type, so fail. + if (_entries.ContainsKey(arrayName)) return NULL; + + // Oops! This array doesn't exist; better create it! + RefCountableRef newEntry; + switch(tc) + { + case B_BOOL_TYPE: newEntry.SetRef(NEWFIELD(BoolDataArray)); break; + case B_DOUBLE_TYPE: newEntry.SetRef(NEWFIELD(DoubleDataArray)); break; + case B_POINTER_TYPE: newEntry.SetRef(NEWFIELD(PointerDataArray)); break; + case B_POINT_TYPE: newEntry.SetRef(NEWFIELD(PointDataArray)); break; + case B_RECT_TYPE: newEntry.SetRef(NEWFIELD(RectDataArray)); break; + case B_FLOAT_TYPE: newEntry.SetRef(NEWFIELD(FloatDataArray)); break; + case B_INT64_TYPE: newEntry.SetRef(NEWFIELD(Int64DataArray)); break; + case B_INT32_TYPE: newEntry.SetRef(NEWFIELD(Int32DataArray)); break; + case B_INT16_TYPE: newEntry.SetRef(NEWFIELD(Int16DataArray)); break; + case B_INT8_TYPE: newEntry.SetRef(NEWFIELD(Int8DataArray)); break; + case B_MESSAGE_TYPE: newEntry.SetRef(NEWFIELD(MessageDataArray)); break; + case B_STRING_TYPE: newEntry.SetRef(NEWFIELD(StringDataArray)); break; + case B_TAG_TYPE: newEntry.SetRef(NEWFIELD(TagDataArray)); break; + default: + newEntry.SetRef(NEWFIELD(ByteBufferDataArray)); + if (newEntry()) (static_cast(newEntry()))->SetTypeCode(tc); + break; + } + return ((newEntry())&&(_entries.Put(arrayName, newEntry) == B_NO_ERROR)) ? (AbstractDataArray*)newEntry() : NULL; +} + +status_t Message :: Rename(const String & oldFieldName, const String & newFieldName) +{ + RefCountableRef oldArray = GetArrayRef(oldFieldName, B_ANY_TYPE); + if (oldArray()) + { + (void) RemoveName(newFieldName); // destructive rename... remove anybody in our way + (void) _entries.Remove(oldFieldName); // remove from under old name + return _entries.Put(newFieldName, oldArray); // add to under new name + } + return B_ERROR; +} + +uint32 Message :: FlattenedSize() const +{ + uint32 sum = 3 * sizeof(uint32); // For the message header: 4 bytes for the protocol revision #, 4 bytes for the number-of-entries field, 4 bytes for what code + + // For each flattenable field: 4 bytes for the name length, name data, 4 bytes for entry type code, 4 bytes for entry data length, entry data + for (HashtableIterator it(_entries, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + const AbstractDataArray * nextValue = static_cast(it.GetValue()()); + if (nextValue->IsFlattenable()) sum += sizeof(uint32) + it.GetKey().FlattenedSize() + sizeof(uint32) + sizeof(uint32) + nextValue->FlattenedSize(); + } + return sum; +} + +uint32 Message :: CalculateChecksum(bool countNonFlattenableFields) const +{ + uint32 ret = what; + + // Calculate the number of flattenable entries (may be less than the total number of entries!) + for (HashtableIterator it(_entries, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + // Note that I'm deliberately NOT considering the ordering of the fields when computing the checksum! + const AbstractDataArray * a = static_cast(it.GetValue()()); + if ((countNonFlattenableFields)||(a->IsFlattenable())) + { + uint32 fnChk = it.GetKey().CalculateChecksum(); + ret += fnChk; + if (fnChk == 0) ret++; // almost-paranoia + ret += (fnChk*a->CalculateChecksum(countNonFlattenableFields)); // multiplying by fnChck helps catch when two fields were swapped + } + } + return ret; +} + +void Message :: Flatten(uint8 * buffer) const +{ + TCHECKPOINT; + + // Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) + // 1. 'what' code (4 bytes) + // 2. Number of entries (4 bytes) + // 3. Entry name length (4 bytes) + // 4. Entry name string (flattened String) + // 5. Entry type code (4 bytes) + // 6. Entry data length (4 bytes) + // 7. Entry data (n bytes) + // 8. loop to 3 as necessary + + // Write current protocol version + uint32 writeOffset = 0; + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(CURRENT_PROTOCOL_VERSION); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // Write 'what' code + networkByteOrder = B_HOST_TO_LENDIAN_INT32(what); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // Remember where to write the number-of-entries value (we'll actually write it at the end of this method) + uint8 * entryCountPtr = &buffer[writeOffset]; + writeOffset += sizeof(uint32); + + // Write entries + uint32 numFlattenedEntries = 0; + for (HashtableIterator it(_entries, HTIT_FLAG_NOREGISTER); it.HasData(); it++) + { + const AbstractDataArray * nextValue = static_cast(it.GetValue()()); + if (nextValue->IsFlattenable()) + { + numFlattenedEntries++; + + // Write entry name length + uint32 keyNameSize = it.GetKey().FlattenedSize(); + networkByteOrder = B_HOST_TO_LENDIAN_INT32(keyNameSize); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // Write entry name + it.GetKey().Flatten(&buffer[writeOffset]); + writeOffset += keyNameSize; + + // Write entry type code + networkByteOrder = B_HOST_TO_LENDIAN_INT32(nextValue->TypeCode()); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // Write entry data length + uint32 dataSize = nextValue->FlattenedSize(); + networkByteOrder = B_HOST_TO_LENDIAN_INT32(dataSize); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + // Write entry data + nextValue->Flatten(&buffer[writeOffset]); + writeOffset += dataSize; + } + } + + // Write number-of-entries field (now that we know its final value) + networkByteOrder = B_HOST_TO_LENDIAN_INT32(numFlattenedEntries); + memcpy(entryCountPtr, &networkByteOrder, sizeof(uint32)); +} + +status_t Message :: Unflatten(const uint8 * buffer, uint32 inputBufferBytes) +{ + TCHECKPOINT; + + Clear(); + + uint32 readOffset = 0; + + // Read and check protocol version number + uint32 networkByteOrder; + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Couldn't read message protocol version! (inputBufferBytes=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes); + return B_ERROR; + } + + uint32 messageProtocolVersion = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if ((messageProtocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)||(messageProtocolVersion > CURRENT_PROTOCOL_VERSION)) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unexpected message protocol version " UINT32_FORMAT_SPEC " (inputBufferBytes=" UINT32_FORMAT_SPEC ")\n", this, messageProtocolVersion, inputBufferBytes); + return B_ERROR; + } + + // Read 'what' code + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Couldn't read what-code! (inputBufferBytes=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes); + return B_ERROR; + } + what = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + + // Read number of entries + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Couldn't read number-of-entries! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, what); + return B_ERROR; + } + uint32 numEntries = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + + // Read entries + for (uint32 i=0; i inputBufferBytes-readOffset) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Entry name length too long! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " nameLength=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, what, i, numEntries, nameLength, (uint32)(inputBufferBytes-readOffset)); + return B_ERROR; + } + + // Read entry name + String entryName; + if (entryName.Unflatten(&buffer[readOffset], nameLength) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unable to unflatten entry name! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " nameLength=" UINT32_FORMAT_SPEC ")\n", this, inputBufferBytes, what, i, numEntries, nameLength); + return B_ERROR; + } + readOffset += nameLength; + + // Read entry type code + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unable to read entry type code! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " entryName=[%s])\n", this, inputBufferBytes, what, i, numEntries, entryName()); + return B_ERROR; + } + uint32 tc = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + + // Read entry data length + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unable to read data length! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " tc=" UINT32_FORMAT_SPEC " entryName=[%s])\n", this, inputBufferBytes, what, i, numEntries, tc, entryName()); + return B_ERROR; + } + uint32 eLength = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if (eLength > inputBufferBytes-readOffset) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Data length is too long! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " tc=" UINT32_FORMAT_SPEC " eLength=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " entryName=[%s])\n", this, inputBufferBytes, what, i, numEntries, tc, eLength, (uint32)(inputBufferBytes-readOffset), entryName()); + return B_ERROR; + } + + AbstractDataArray * nextEntry = GetOrCreateArray(entryName, tc); + if (nextEntry == NULL) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unable to create data array object! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " tc=" UINT32_FORMAT_SPEC " entryName=[%s])\n", this, inputBufferBytes, what, i, numEntries, tc, entryName()); + return B_ERROR; + } + + if (nextEntry->Unflatten(&buffer[readOffset], eLength) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "Message %p: Unable to unflatten data array object! (inputBufferBytes=" UINT32_FORMAT_SPEC ", what=" UINT32_FORMAT_SPEC " i=" UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " tc=" UINT32_FORMAT_SPEC " entryName=[%s])\n", this, inputBufferBytes, what, i, numEntries, tc, entryName()); + Clear(); // fix for occasional crash bug; we were deleting nextEntry here, *and* in the destructor! + return B_ERROR; + } + readOffset += eLength; + } + return B_NO_ERROR; +} + +status_t Message :: AddFlatAux(const String & fieldName, const FlatCountableRef & ref, uint32 tc, bool prepend) +{ + AbstractDataArray * array = ref() ? GetOrCreateArray(fieldName, tc) : NULL; + return array ? (prepend ? array->PrependDataItem(&ref, sizeof(ref)) : array->AddDataItem(&ref, sizeof(ref))) : B_ERROR; +} + +status_t Message :: AddString(const String & fieldName, const String & val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_STRING_TYPE); + status_t ret = array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; + return ret; +} + +status_t Message :: AddInt8(const String & fieldName, int8 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT8_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddInt16(const String & fieldName, int16 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT16_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddInt32(const String & fieldName, int32 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT32_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddInt64(const String & fieldName, int64 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT64_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddBool(const String & fieldName, bool val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_BOOL_TYPE); + status_t ret = array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; + return ret; +} + +status_t Message :: AddFloat(const String & fieldName, float val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_FLOAT_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddDouble(const String & fieldName, double val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_DOUBLE_TYPE); + return array ? array->AddDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: AddPointer(const String & fieldName, const void * ptr) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_POINTER_TYPE); + return array ? array->AddDataItem(&ptr, sizeof(ptr)) : B_ERROR; +} + +status_t Message :: AddPoint(const String & fieldName, const Point & point) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_POINT_TYPE); + return array ? array->AddDataItem(&point, sizeof(point)) : B_ERROR; +} + +status_t Message :: AddRect(const String & fieldName, const Rect & rect) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_RECT_TYPE); + return array ? array->AddDataItem(&rect, sizeof(rect)) : B_ERROR; +} + +status_t Message :: AddTag(const String & fieldName, const RefCountableRef & tag) +{ + if (tag() == NULL) return B_ERROR; + AbstractDataArray * array = GetOrCreateArray(fieldName, B_TAG_TYPE); + return array ? array->AddDataItem(&tag, sizeof(tag)) : B_ERROR; +} + +status_t Message :: AddMessage(const String & fieldName, const MessageRef & ref) +{ + if (ref() == NULL) return B_ERROR; + AbstractDataArray * array = GetOrCreateArray(fieldName, B_MESSAGE_TYPE); + return (array) ? array->AddDataItem(&ref, sizeof(ref)) : B_ERROR; +} + +status_t Message :: AddFlat(const String & fieldName, const FlatCountableRef & ref) +{ + FlatCountable * fc = ref(); + if (fc) + { + uint32 tc = fc->TypeCode(); + switch(tc) + { + case B_STRING_TYPE: return B_ERROR; // sorry, can't do that (Strings aren't FlatCountables) + case B_POINT_TYPE: return B_ERROR; // sorry, can't do that (Points aren't FlatCountables) + case B_RECT_TYPE: return B_ERROR; // sorry, can't do that (Rects aren't FlatCountables) + case B_MESSAGE_TYPE: return AddMessage(fieldName, MessageRef(ref.GetRefCountableRef(), true)); + default: return AddFlatAux(fieldName, ref, tc, false); + } + } + return B_ERROR; +} + +uint32 Message :: GetElementSize(uint32 type) const +{ + switch(type) + { + case B_BOOL_TYPE: return sizeof(bool); + case B_DOUBLE_TYPE: return sizeof(double); + case B_POINTER_TYPE: return sizeof(void *); + case B_POINT_TYPE: return sizeof(Point); + case B_RECT_TYPE: return sizeof(Rect); + case B_FLOAT_TYPE: return sizeof(float); + case B_INT64_TYPE: return sizeof(int64); + case B_INT32_TYPE: return sizeof(int32); + case B_INT16_TYPE: return sizeof(int16); + case B_INT8_TYPE: return sizeof(int8); + case B_MESSAGE_TYPE: return sizeof(MessageRef); + case B_STRING_TYPE: return sizeof(String); + default: return 0; + } +} + +status_t Message :: AddDataAux(const String & fieldName, const void * data, uint32 numBytes, uint32 tc, bool prepend) +{ + if (numBytes == 0) return B_ERROR; // can't add 0 bytes, that's silly + if (tc == B_STRING_TYPE) + { + String temp((const char *)data); // kept separate to avoid BeOS gcc optimizer bug (otherwise -O3 crashes here) + return prepend ? PrependString(fieldName, temp) : AddString(fieldName, temp); + } + + // for primitive types, we do this: + bool isVariableSize = false; + uint32 elementSize = GetElementSize(tc); + if (elementSize == 0) + { + // zero indicates a variable-sized data item; we will use a ByteBuffer to hold it. + isVariableSize = true; + elementSize = numBytes; + if (elementSize == 0) return B_ERROR; + } + if (numBytes % elementSize) return B_ERROR; // Can't add half an element, silly! + + AbstractDataArray * array = GetOrCreateArray(fieldName, tc); + if (array == NULL) return B_ERROR; + + uint32 numElements = numBytes/elementSize; + const uint8 * dataBuf = (const uint8 *) data; + for (uint32 i=0; iPrependDataItem(dataToAdd, addSize) : array->AddDataItem(dataToAdd, addSize)) != B_NO_ERROR) return B_ERROR; + } + return B_NO_ERROR; +} + +void * Message :: GetPointerToNormalizedFieldData(const String & fieldName, uint32 * retNumItems, uint32 typeCode) +{ + RefCountableRef * e = _entries.Get(fieldName); + if (e) + { + AbstractDataArray * a = static_cast(e->GetItemPointer()); + if ((typeCode == B_ANY_TYPE)||(typeCode == a->TypeCode())) + { + a->Normalize(); + + const void * ptr; + if (a->FindDataItem(0, &ptr) == B_NO_ERROR) // must be called AFTER a->Normalize() + { + if (retNumItems) *retNumItems = a->GetNumItems(); + return const_cast(ptr); + } + } + } + return NULL; +} + +status_t Message :: EnsureFieldIsPrivate(const String & fieldName) +{ + RefCountableRef * e = _entries.Get(fieldName); + return e ? (e->IsRefPrivate() ? B_NO_ERROR : CopyName(fieldName, *this)) : B_ERROR; // Copying the field to ourself replaces the field with an unshared clone +} + +status_t Message :: RemoveName(const String & fieldName) +{ + return _entries.Remove(fieldName); +} + +status_t Message :: RemoveData(const String & fieldName, uint32 index) +{ + AbstractDataArray * array = GetArray(fieldName, B_ANY_TYPE); + if (array) + { + status_t ret = array->RemoveDataItem(index); + return (array->IsEmpty()) ? RemoveName(fieldName) : ret; + } + else return B_ERROR; +} + +status_t Message :: FindString(const String & fieldName, uint32 index, const char * & setMe) const +{ + const StringDataArray * ada = (const StringDataArray *)GetArray(fieldName, B_STRING_TYPE); + if ((ada)&&(index < ada->GetNumItems())) + { + setMe = ada->ItemAt(index)(); + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t Message :: FindString(const String & fieldName, uint32 index, const String ** setMe) const +{ + const StringDataArray * ada = (const StringDataArray *)GetArray(fieldName, B_STRING_TYPE); + if ((ada)&&(index < ada->GetNumItems())) + { + *setMe = &ada->ItemAt(index); + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t Message :: FindString(const String & fieldName, uint32 index, String & str) const +{ + const StringDataArray * ada = (const StringDataArray *)GetArray(fieldName, B_STRING_TYPE); + if ((ada)&&(index < ada->GetNumItems())) + { + str = ada->ItemAt(index); + return B_NO_ERROR; + } + else return B_ERROR; +} + +const uint8 * Message :: FindFlatAux(const AbstractDataArray * ada, uint32 index, uint32 & retNumBytes, const FlatCountable ** optRetFCPtr) const +{ + const ByteBufferDataArray * bbda = dynamic_cast(ada); + if (bbda) + { + const ByteBuffer * buf = dynamic_cast(bbda->ItemAt(index)()); + if (buf) + { + retNumBytes = buf->GetNumBytes(); + return buf->GetBuffer(); + } + else + { + if (optRetFCPtr) *optRetFCPtr = bbda->ItemAt(index)(); + return NULL; + } + } + else + { + const void * data; + status_t ret = ada->FindDataItem(index, &data); + if (ret == B_NO_ERROR) + { + retNumBytes = ada->GetItemSize(index); + return (const uint8 *)data; + } + } + if (optRetFCPtr) *optRetFCPtr = NULL; + return NULL; +} + +status_t Message :: FindFlat(const String & fieldName, uint32 index, FlatCountableRef & ref) const +{ + TCHECKPOINT; + + const AbstractDataArray * array = GetArray(fieldName, B_ANY_TYPE); + if ((array)&&(index < array->GetNumItems())) + { + const ByteBufferDataArray * bbda = dynamic_cast(array); + if (bbda) + { + ref = bbda->ItemAt(index); + return B_NO_ERROR; + } + const MessageDataArray * mda = dynamic_cast(array); + if (mda) return ref.SetFromRefCountableRef(mda->ItemAt(index).GetRefCountableRef()); + } + return B_ERROR; +} + +status_t Message :: FindData(const String & fieldName, uint32 tc, uint32 index, const void ** data, uint32 * setSize) const +{ + TCHECKPOINT; + + const AbstractDataArray * array = GetArray(fieldName, tc); + if ((array)&&(array->FindDataItem(index, data) == B_NO_ERROR)) + { + switch(tc) + { + case B_STRING_TYPE: + { + const String * str = (const String *) (*data); + *data = str->Cstr(); + if (setSize) *setSize = str->FlattenedSize(); + } + break; + + default: + { + uint32 es = GetElementSize(tc); + if (es > 0) + { + if (setSize) *setSize = es; + } + else + { + // But for user-generated types, we need to get a pointer to the actual data, not just the ref + const FlatCountableRef * fcRef = (const FlatCountableRef *)(*data); + const ByteBuffer * buf = dynamic_cast(fcRef->GetItemPointer()); + if (buf) + { + const void * b = buf->GetBuffer(); + if (b) + { + *data = b; + if (setSize) *setSize = buf->GetNumBytes(); + return B_NO_ERROR; + } + } + return B_ERROR; + } + } + break; + } + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t Message :: FindDataItemAux(const String & fieldName, uint32 index, uint32 tc, void * setValue, uint32 valueSize) const +{ + const AbstractDataArray * array = GetArray(fieldName, tc); + if (array == NULL) return B_ERROR; + const void * addressOfValue; + status_t ret = array->FindDataItem(index, &addressOfValue); + if (ret != B_NO_ERROR) return ret; + memcpy(setValue, addressOfValue, valueSize); + return B_NO_ERROR; +} + +status_t Message :: FindPoint(const String & fieldName, uint32 index, Point & point) const +{ + const PointDataArray * array = (const PointDataArray *)GetArray(fieldName, B_POINT_TYPE); + if ((array == NULL)||(index >= array->GetNumItems())) return B_ERROR; + point = array->ItemAt(index); + return B_NO_ERROR; +} + +status_t Message :: FindRect(const String & fieldName, uint32 index, Rect & rect) const +{ + const RectDataArray * array = (const RectDataArray *)GetArray(fieldName, B_RECT_TYPE); + if ((array == NULL)||(index >= array->GetNumItems())) return B_ERROR; + rect = array->ItemAt(index); + return B_NO_ERROR; +} + +status_t Message :: FindTag(const String & fieldName, uint32 index, RefCountableRef & tag) const +{ + const TagDataArray * array = (const TagDataArray*)GetArray(fieldName, B_TAG_TYPE); + if ((array == NULL)||(index >= array->GetNumItems())) return B_ERROR; + tag = array->ItemAt(index); + return B_NO_ERROR; +} + +status_t Message :: FindMessage(const String & fieldName, uint32 index, Message & msg) const +{ + MessageRef msgRef; + if (FindMessage(fieldName, index, msgRef) == B_NO_ERROR) + { + const Message * m = msgRef(); + if (m) + { + msg = *m; + return B_NO_ERROR; + } + } + return B_ERROR; +} + +status_t Message :: FindMessage(const String & fieldName, uint32 index, MessageRef & ref) const +{ + const MessageDataArray * array = (const MessageDataArray *) GetArray(fieldName, B_MESSAGE_TYPE); + if ((array == NULL)||(index >= array->GetNumItems())) return B_ERROR; + ref = array->ItemAt(index); + return B_NO_ERROR; +} + +status_t Message :: FindDataPointer(const String & fieldName, uint32 tc, uint32 index, void ** data, uint32 * setSize) const +{ + const void * dataLoc; + status_t ret = FindData(fieldName, tc, index, &dataLoc, setSize); + if (ret == B_NO_ERROR) + { + *data = (void *) dataLoc; // breaks const correctness, but oh well + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t Message :: ReplaceString(bool okayToAdd, const String & fieldName, uint32 index, const String & string) +{ + AbstractDataArray * array = GetArray(fieldName, B_STRING_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddString(fieldName, string); + return array ? array->ReplaceDataItem(index, &string, sizeof(string)) : B_ERROR; +} + +status_t Message :: ReplaceFlatAux(bool okayToAdd, const String & fieldName, uint32 index, const ByteBufferRef & bufRef, uint32 tc) +{ + FlatCountableRef fcRef; fcRef.SetFromRefCountableRefUnchecked(bufRef.GetRefCountableRef()); + return ReplaceDataAux(okayToAdd, fieldName, index, &fcRef, sizeof(fcRef), tc); +} + +status_t Message :: ReplaceDataAux(bool okayToAdd, const String & fieldName, uint32 index, void * dataBuf, uint32 bufSize, uint32 tc) +{ + AbstractDataArray * array = GetArray(fieldName, tc); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddDataAux(fieldName, dataBuf, bufSize, tc, false); + return array ? array->ReplaceDataItem(index, dataBuf, bufSize) : B_ERROR; +} + +status_t Message :: ReplaceInt8(bool okayToAdd, const String & fieldName, uint32 index, int8 val) +{ + AbstractDataArray * array = GetArray(fieldName, B_INT8_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddInt8(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceInt16(bool okayToAdd, const String & fieldName, uint32 index, int16 val) +{ + AbstractDataArray * array = GetArray(fieldName, B_INT16_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddInt16(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceInt32(bool okayToAdd, const String & fieldName, uint32 index, int32 val) +{ + AbstractDataArray * array = GetArray(fieldName, B_INT32_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddInt32(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceInt64(bool okayToAdd, const String & fieldName, uint32 index, int64 val) +{ + AbstractDataArray * array = GetArray(fieldName, B_INT64_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddInt64(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceBool(bool okayToAdd, const String & fieldName, uint32 index, bool val) +{ + AbstractDataArray * array = GetArray(fieldName, B_BOOL_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddBool(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceFloat(bool okayToAdd, const String & fieldName, uint32 index, float val) +{ + AbstractDataArray * array = GetArray(fieldName, B_FLOAT_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddFloat(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplaceDouble(bool okayToAdd, const String & fieldName, uint32 index, double val) +{ + AbstractDataArray * array = GetArray(fieldName, B_DOUBLE_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddDouble(fieldName, val); + return array ? array->ReplaceDataItem(index, &val, sizeof(val)) : B_ERROR; +} + +status_t Message :: ReplacePointer(bool okayToAdd, const String & fieldName, uint32 index, const void * ptr) +{ + AbstractDataArray * array = GetArray(fieldName, B_POINTER_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddPointer(fieldName, ptr); + return array ? array->ReplaceDataItem(index, &ptr, sizeof(ptr)) : B_ERROR; +} + +status_t Message :: ReplacePoint(bool okayToAdd, const String & fieldName, uint32 index, const Point &point) +{ + AbstractDataArray * array = GetArray(fieldName, B_POINT_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddPoint(fieldName, point); + return array ? array->ReplaceDataItem(index, &point, sizeof(point)) : B_ERROR; +} + +status_t Message :: ReplaceRect(bool okayToAdd, const String & fieldName, uint32 index, const Rect &rect) +{ + AbstractDataArray * array = GetArray(fieldName, B_RECT_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddRect(fieldName, rect); + return array ? array->ReplaceDataItem(index, &rect, sizeof(rect)) : B_ERROR; +} + +status_t Message :: ReplaceTag(bool okayToAdd, const String & fieldName, uint32 index, const RefCountableRef & tag) +{ + if (tag() == NULL) return B_ERROR; + AbstractDataArray * array = GetArray(fieldName, B_TAG_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddTag(fieldName, tag); + return array ? array->ReplaceDataItem(index, &tag, sizeof(tag)) : B_ERROR; +} + +status_t Message :: ReplaceMessage(bool okayToAdd, const String & fieldName, uint32 index, const MessageRef & msgRef) +{ + if (msgRef() == NULL) return B_ERROR; + AbstractDataArray * array = GetArray(fieldName, B_MESSAGE_TYPE); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddMessage(fieldName, msgRef); + if (array) return array->ReplaceDataItem(index, &msgRef, sizeof(msgRef)); + return B_ERROR; +} + +status_t Message :: ReplaceFlat(bool okayToAdd, const String & fieldName, uint32 index, const FlatCountableRef & ref) +{ + const FlatCountable * fc = ref(); + if (fc) + { + uint32 tc = fc->TypeCode(); + AbstractDataArray * array = GetArray(fieldName, tc); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddFlat(fieldName, ref); + if (array) + { + switch(tc) + { + case B_MESSAGE_TYPE: + return (dynamic_cast(fc)) ? ReplaceMessage(okayToAdd, fieldName, index, MessageRef(ref.GetRefCountableRef(), true)) : B_ERROR; + + default: + if (GetElementSize(tc) == 0) + { + ByteBufferDataArray * bbda = dynamic_cast(array); + if (bbda) return bbda->ReplaceDataItem(index, &ref, sizeof(ref)); + } + break; + } + } + } + return B_ERROR; +} + +status_t Message :: ReplaceData(bool okayToAdd, const String & fieldName, uint32 type, uint32 index, const void * data, uint32 numBytes) +{ + TCHECKPOINT; + + if (type == B_STRING_TYPE) + { + String temp((const char *)data); // temp to avoid gcc optimizer bug + return ReplaceString(okayToAdd, fieldName, index, temp); + } + AbstractDataArray * array = GetArray(fieldName, type); + if ((okayToAdd)&&((array == NULL)||(index >= array->GetNumItems()))) return AddDataAux(fieldName, data, numBytes, type, false); + if (array == NULL) return B_ERROR; + + // for primitive types, we do this: + bool isVariableSize = false; + uint32 elementSize = GetElementSize(type); + if (elementSize == 0) + { + // zero indicates a variable-sized data item + isVariableSize = true; + elementSize = numBytes; + if (elementSize == 0) return B_ERROR; + } + + if (numBytes % elementSize) return B_ERROR; // Can't add half an element, silly! + uint32 numElements = numBytes / elementSize; + const uint8 * dataBuf = (const uint8 *) data; + for (uint32 i=index; iReplaceDataItem(i, dataToAdd, addSize) != B_NO_ERROR) return B_ERROR; + } + return B_NO_ERROR; +} + +uint32 Message :: GetNumValuesInName(const String & fieldName, uint32 type) const +{ + const AbstractDataArray * array = GetArray(fieldName, type); + return array ? array->GetNumItems() : 0; +} + +status_t Message :: CopyName(const String & oldFieldName, Message & copyTo, const String & newFieldName) const +{ + const AbstractDataArray * array = GetArray(oldFieldName, B_ANY_TYPE); + if (array) + { + RefCountableRef clone = array->Clone(); + if ((clone())&&(copyTo._entries.Put(newFieldName, clone) == B_NO_ERROR)) return B_NO_ERROR; + } + return B_ERROR; +} + +status_t Message :: ShareName(const String & oldFieldName, Message & shareTo, const String & newFieldName) const +{ + const RefCountableRef * gRef = _entries.Get(oldFieldName); + return gRef ? shareTo._entries.Put(newFieldName, *gRef) : B_ERROR; +} + +status_t Message :: MoveName(const String & oldFieldName, Message & moveTo, const String & newFieldName) +{ + if (ShareName(oldFieldName, moveTo) == B_NO_ERROR) + { + (void) _entries.Remove(newFieldName); + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t Message :: PrependString(const String & fieldName, const String & val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_STRING_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependInt8(const String & fieldName, int8 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT8_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependInt16(const String & fieldName, int16 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT16_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependInt32(const String & fieldName, int32 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT32_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependInt64(const String & fieldName, int64 val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_INT64_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependBool(const String & fieldName, bool val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_BOOL_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependFloat(const String & fieldName, float val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_FLOAT_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependDouble(const String & fieldName, double val) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_DOUBLE_TYPE); + return array ? array->PrependDataItem(&val, sizeof(val)) : B_ERROR; +} + +status_t Message :: PrependPointer(const String & fieldName, const void * ptr) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_POINTER_TYPE); + return array ? array->PrependDataItem(&ptr, sizeof(ptr)) : B_ERROR; +} + +status_t Message :: PrependPoint(const String & fieldName, const Point & point) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_POINT_TYPE); + return array ? array->PrependDataItem(&point, sizeof(point)) : B_ERROR; +} + +status_t Message :: PrependRect(const String & fieldName, const Rect & rect) +{ + AbstractDataArray * array = GetOrCreateArray(fieldName, B_RECT_TYPE); + return array ? array->PrependDataItem(&rect, sizeof(rect)) : B_ERROR; +} + +status_t Message :: PrependTag(const String & fieldName, const RefCountableRef & tag) +{ + if (tag() == NULL) return B_ERROR; + AbstractDataArray * array = GetOrCreateArray(fieldName, B_TAG_TYPE); + return array ? array->PrependDataItem(&tag, sizeof(tag)) : B_ERROR; +} + +status_t Message :: PrependMessage(const String & fieldName, const MessageRef & ref) +{ + if (ref() == NULL) return B_ERROR; + AbstractDataArray * array = GetOrCreateArray(fieldName, B_MESSAGE_TYPE); + return (array) ? array->PrependDataItem(&ref, sizeof(ref)) : B_ERROR; +} + +status_t Message :: PrependFlat(const String & fieldName, const FlatCountableRef & ref) +{ + FlatCountable * fc = ref(); + if (fc) + { + uint32 tc = fc->TypeCode(); + switch(tc) + { + case B_STRING_TYPE: return B_ERROR; // sorry, can't do that (Strings aren't FlatCountables) + case B_POINT_TYPE: return B_ERROR; // sorry, can't do that (Strings aren't FlatCountables) + case B_RECT_TYPE: return B_ERROR; // sorry, can't do that (Strings aren't FlatCountables) + case B_MESSAGE_TYPE: return PrependMessage(fieldName, MessageRef(ref.GetRefCountableRef(), true)); + default: return AddFlatAux(fieldName, ref, tc, true); + } + } + return B_ERROR; +} + +status_t Message :: CopyFromImplementation(const Flattenable & copyFrom) +{ + const Message * cMsg = dynamic_cast(©From); + if (cMsg) + { + *this = *cMsg; + return B_NO_ERROR; + } + else return FlatCountable::CopyFromImplementation(copyFrom); +} + +bool Message :: operator == (const Message & rhs) const +{ + return ((this == &rhs)||((what == rhs.what)&&(GetNumNames() == rhs.GetNumNames())&&(FieldsAreSubsetOf(rhs, true)))); +} + +bool Message :: FieldsAreSubsetOf(const Message & rhs, bool compareContents) const +{ + TCHECKPOINT; + + // Returns true iff every one of our fields has a like-named, liked-typed, equal-length field in (rhs). + for (HashtableIterator iter(_entries, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + const RefCountableRef * hisNextValue = rhs._entries.Get(iter.GetKey()); + if ((hisNextValue == NULL)||((static_cast(iter.GetValue()()))->IsEqualTo((const AbstractDataArray*)(hisNextValue->GetItemPointer()), compareContents) == false)) return false; + } + return true; +} + +void Message :: SwapContents(Message & swapWith) +{ + muscleSwap(what, swapWith.what); + _entries.SwapContents(swapWith._entries); +} + +}; // end namespace muscle diff --git a/message/Message.h b/message/Message.h new file mode 100644 index 00000000..8aaffc37 --- /dev/null +++ b/message/Message.h @@ -0,0 +1,1631 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/****************************************************************************** +/ +/ File: Message.h +/ +/ Description: OS-independent version of Be's BMessage class. +/ Doesn't contain all of BMessage's functionality, +/ but keeps enough of it to be useful as a portable +/ data object (dictionary) in cross-platform apps. +/ Also adds some functionality that BMessage doesn't have, +/ such as iterators and optional direct (non-copying) access to internal +/ data (for efficiency) +/ +*******************************************************************************/ + +#ifndef MuscleMessage_h +#define MuscleMessage_h + +#include "support/Point.h" +#include "support/Rect.h" +#include "util/ByteBuffer.h" +#include "util/String.h" +#include "util/Hashtable.h" + +namespace muscle { + +class Message; +DECLARE_REFTYPES(Message); + +/** Returns a reference to an empty/default Message object. + * Useful in cases where you need to refer to an empty Message but don't wish to be + * constantly constructing/destroying temporary Message objects. + */ +inline const Message & GetEmptyMessage() {return GetDefaultObjectForType();} + +/** Same as GetEmptyMessage(), except it returns a ConstMessageRef instead of a Message. */ +const ConstMessageRef & GetEmptyMessageRef(); + +/** This function returns a pointer to a singleton ObjectPool that can be + * used to minimize the number of Message allocations and deletions by + * recycling the Message objects. + */ +MessageRef::ItemPool * GetMessagePool(); + +/** Convenience method: Gets a Message from the Message pool and returns a reference to it. + * @param what The 'what' code to set in the returned Message. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetMessageFromPool(uint32 what = 0L); + +/** As above, except that the Message is obtained from the specified pool instead of from the default Message pool. + * @param pool the ObjectPool to allocate the Message from. + * @param what The 'what' code to set in the returned Message. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetMessageFromPool(ObjectPool & pool, uint32 what = 0L); + +/** Convenience method: Gets a Message from the message pool, makes it equal to (copyMe), and returns a reference to it. + * @param copyMe A Message to clone. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetMessageFromPool(const Message & copyMe); + +/** As above, except that the Message is obtained from the specified pool instead of from the default Message pool. + * @param pool the ObjectPool to allocate the Message from. + * @param copyMe A Message to clone. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetMessageFromPool(ObjectPool & pool, const Message & copyMe); + +/** Convenience method: Gets a Message from the message pool, populates it using the flattened Message + * bytes at (flatBytes), and returns it. + * @param flatBytes The flattened Message bytes (as previously produced by Message::Flatten()) to unflatten from + * @param numBytes The number of bytes that (flatBytes) points to. + * @return Reference to a Message object, or a NULL ref on failure (out of memory or unflattening error) + */ +MessageRef GetMessageFromPool(const uint8 * flatBytes, uint32 numBytes); + +/** As above, except that the Message is obtained from the specified pool instead of from the default Message pool. + * @param pool the ObjectPool to allocate the Message from. + * @param flatBytes The flattened Message bytes (as previously produced by Message::Flatten()) to unflatten from + * @param numBytes The number of bytes that (flatBytes) points to. + * @return Reference to a Message object, or a NULL ref on failure (out of memory or unflattening error) + */ +MessageRef GetMessageFromPool(ObjectPool & pool, const uint8 * flatBytes, uint32 numBytes); + +/** Convenience method: Gets a Message from the message pool, makes it a lightweight copy of (copyMe), + * and returns a reference to it. See Message::MakeLightweightCopyOf() for details. + * @param copyMe A Message to make a lightweight copy of. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetLightweightCopyOfMessageFromPool(const Message & copyMe); + +/** As above, except that the Message is obtained from the specified pool instead of from the default Message pool. + * @param pool the ObjectPool to allocate the Message from. + * @param copyMe A Message to make a lightweight copy of. + * @return Reference to a Message object, or a NULL ref on failure (out of memory). + */ +MessageRef GetLightweightCopyOfMessageFromPool(ObjectPool & pool, const Message & copyMe); + +// this declaration is for internal use only +class AbstractDataArray; + +/** This is an iterator that allows you to efficiently iterate over the field names in a Message. */ +class MessageFieldNameIterator +{ +public: + /** Default constructor. + * Creates an "empty" iterator; Use the assignment operator to turn the iterator into something useful. + */ + MessageFieldNameIterator() : _typeCode(B_ANY_TYPE) {/* empty */} + + /** This form of the constructor creates an iterator that will iterate over field names in the given Message. + * @param msg the Message whose field names we want to iterate over + * @param type Type of fields you wish to iterate over, or B_ANY_TYPE to iterate over all fields. + * @param flags Bit-chord of HTIT_FLAG_* flags you want to use to affect the iteration behaviour. + * This bit-chord will get passed on to the underlying HashtableIterator. Defaults + * to zero, which provides the default behaviour. + */ + MessageFieldNameIterator(const Message & msg, uint32 type = B_ANY_TYPE, uint32 flags = 0); + + /** Destructor. */ + ~MessageFieldNameIterator() {/* empty */} + + /** Moves this iterator forward by one in the field-names list. */ + void operator++(int) {_iter++; if (_typeCode != B_ANY_TYPE) SkipNonMatchingFieldNames();} + + /** Moves this iterator backward by one in the field-names list. */ + void operator--(int) {bool b = _iter.IsBackwards(); _iter.SetBackwards(!b); (*this)++; _iter.SetBackwards(b);} + + /** Returns true iff this iterator is pointing at valid field name data. + * It's only safe to call GetFieldName() if this method returns true. + */ + bool HasData() const {return _iter.HasData();} + + /** Returns a reference to the current field name. Note that it is only safe + * to call this method if HasData() returned true! The value returned by + * HasData() may change if the Message we are iterating over is modified. + */ + const String & GetFieldName() const {return _iter.GetKey();} + +private: + friend class Message; + MessageFieldNameIterator(const HashtableIterator & iter, uint32 tc) : _typeCode(tc), _iter(iter) {if (_typeCode != B_ANY_TYPE) SkipNonMatchingFieldNames();} + void SkipNonMatchingFieldNames(); + + uint32 _typeCode; + HashtableIterator _iter; +}; + +// Version number of the Message serialization protocol. +// Will be the first four bytes of each serialized Message buffer. +// Only buffers beginning with version numbers between these two +// constants (inclusive) will be Unflattened as Messages. +#define OLDEST_SUPPORTED_PROTOCOL_VERSION 1347235888 // 'PM00' +#define CURRENT_PROTOCOL_VERSION 1347235888 // 'PM00' + +/** + * The Message class implements a serializable container for named, typed data. + * + * Messages are the foundation of the MUSCLE data-exchange protocol, and + * they are used to send data between computers, processes, and threads + * in a standardized, type-safe, expressive, endian-neutral, and + * language-neutral manner. A Message object can be serialized into + * a series of bytes on one machine by calling Flatten() on it, and + * after those bytes have been sent across the network, the same Message + * can be recreated on the destination machine by calling Unflatten(). + * + * This enables efficient and reliable transmission of arbitrarily complex structured data. + * A flattened Message can be as small as 12 bytes, or as large as 4 gigabytes. + * + * Each Message object has a single 32-bit integer 'what code', which + * can be set to any 32-bit value (often a PR_COMMAND_* or PR_RESULT_* + * value, as enumerated in StorageReflectConstants.h) + * + * Each Message can also hold a number of data fields. Each data field + * in a Message is assigned a name string (which also can be arbitrary, + * although various strings may have particular meanings in various contexts), + * and has a specified data type. The field names are used as keys to lookup + * their associated data, so each field in the Message must have a name that is + * unique relative to the other fields in the same Message. (Note: to make + * MUSCLE code more readable and less susceptible to typos, the field name strings + * that MUSCLE's PR_COMMAND_* Messages recognize are encoded as PR_NAME_* defines + * in StorageReflectConstants.h) + * + * Each data field in a Message holds an array of one or more data items of the + * field's specified type, stored (internally) as a double-ended Queue. + * + * The Message class has built-in support for fields containing the following data types: + * - Boolean: via AddBool(), FindBool(), GetBool(), etc. + * - Double-precision IEEE754 floating point: via AddDouble(), FindDouble(), GetDouble(), etc. + * - Single-precision IEEE754 floating point: via AddFloat(), FindFloat(), GetFloat(), etc. + * - 64-bit signed integer: via AddInt64(), FindInt64(), GetInt64(), etc. + * - 32-bit signed integer: via AddInt32(), FindInt32(), GetInt32(), etc. + * - 16-bit signed integer: via AddInt16(), FindInt16(), GetInt16(), etc. + * - 8-bit signed integer: via AddInt8(), FindInt8(), GetInt8(), etc. + * - String: (String objects are NUL-terminated character strings): via AddString(), FindString(), GetString(), etc. + * - Message/MessageRef: via AddMessage(), FindMessage(), GetMessage(), etc. + * - Raw Byte Buffer: (Variable-length sequences of unstructured uint8 bytes): via AddData(), FindData(), etc. + * - Point: (x and y float values): via AddPoint(), FindPoint(), GetPoint(), etc. + * - Rectangle: (left, top, right, and bottom float values): via AddRect(), FindRect(), GetRect(), etc. + * - Pointer: (Note: Pointer fields do not get serialized): via AddPointer(), FindPointer(), GetPointer(), etc. + * + * Note that the support fields of type Message means that Messages can be nested hierarchically; + * A single Message object can contain a field of type Message, each of whose elements is another + * Message, each of which can contain its own Message fields, and so on. This allows for very + * complex data structures: for example, Meyer Sound's CueStation software encodes an entire + * saved project file in the form of a single serialized Message object. + * + * In addition to the built-in types listed above, it is possible to add fields + * containing user-defined objects of arbitary type. In order to be added to a Message, + * a non-built-in object must derive from Flattenable (if you wish to add it to the Message + * in its flattened-bytes form, and later on restore it via FindFlat()), or alternatively + * the object can be derived from FlatCountable, in which case you can add a reference to + * your object (again via AddFlat()) to the Message, and the Message will hold on to your + * object directly rather than to a buffer of its flattened bytes. This can be more efficient, + * since when done this way the object need not be serialized and later reconstructed, unless/until + * the Message itself needs to be serialized and reconstructed (e.g. in order to + * send it across the network to another machine). + * + * Lastly, it is possible to store inside a Message references to objects of any class that derives + * from RefCountable, via the AddTag() method (etc). Objects added via AddTag() will not be serialized + * when the Message is serialized, however, so this method is useful mainly for intra-process communication + * or in-RAM data storage. + * + * Note that for quick debugging purposes, it is possible to dump a Message's contents to stdout + * at any time by calling PrintToStream() on the Message. + */ +class Message : public FlatCountable, public Cloneable, private CountedObject +{ +public: + /** 32 bit what code, for quick identification of message types. Set this however you like. */ + uint32 what; + + /** Default Constructor. */ + Message() : what(0) {/* empty */} + + /** Constructor. + * @param what The 'what' member variable will be set to the value you specify here. + */ + explicit Message(uint32 what) : what(what) {/* empty */} + + /** Copy Constructor. */ + Message(const Message & copyMe) : FlatCountable(), Cloneable(), CountedObject() {*this = copyMe;} + + /** Destructor. */ + virtual ~Message() {/* empty */} + + /** Assignment operator. */ + Message & operator=(const Message & msg); + + /** Comparison operator. Two Message are considered equal iff their what codes are equal, + * and their sets of fields are equal. Field ordering is not considered. Note that this + * operation can be expensive for large Messages! + */ + bool operator ==(const Message & rhs) const; + + /** Returns the opposite of the == operator. */ + bool operator !=(const Message & rhs) const {return !(*this == rhs);} + + /** Retrieve information about the given field in this message. + * @param fieldName Name of the field to look for. + * @param type If non-NULL, On sucess the type code of the found field is copied into this value. + * @param c If non-NULL, On success, the number of elements in the found field is copied into this value. + * @param fixed_size If non-NULL, On success, (*fixed_size) is set to reflect whether the returned field's objects are all the same size, or not. + * @return B_NO_ERROR on success, or B_ERROR if the requested field couldn't be found. + */ + status_t GetInfo(const String & fieldName, uint32 *type, uint32 *c = NULL, bool *fixed_size = NULL) const; + + /** Returns the number of fields of the given type that are present in the Message. + * @param type The type of field to count, or B_ANY_TYPE to count all field types. + * @return The number of matching fields + */ + uint32 GetNumNames(uint32 type = B_ANY_TYPE) const; + + /** Returns true if there are any fields of the given type in the Message. + * @param type The type of field to check for, or B_ANY_TYPE to check for any field type. + * @return True iff any fields of the given type exist. + */ + bool HasNames(uint32 type = B_ANY_TYPE) const {return (GetNumNames(type) > 0);} + + /** @return true iff there are no fields in this Message. */ + bool IsEmpty() const {return (_entries.IsEmpty());} + + /** Prints debug info describing the contents of this Message to stdout. + * @param optFile If non-NULL, the text will be printed to this file. If left as NULL, stdout will be used as a default. + * @param maxRecurseLevel The maximum level of nested sub-Messages that we will print. Defaults to MUSCLE_NO_LIMIT. + * @param indentLevel Number of spaces to indent each printed line. Used while recursing to format nested messages text nicely + */ + void PrintToStream(FILE * optFile = NULL, uint32 maxRecurseLevel = MUSCLE_NO_LIMIT, int indentLevel = 0) const; + + /** Same as PrintToStream(), only the state of the Message is returned + * as a String instead of being printed to stdout. + */ + String ToString(uint32 maxRecurseLevel = MUSCLE_NO_LIMIT, int indentLevel = 0) const; + + /** Same as ToString(), except the text is added to the given String instead + * of being returned as a new String. + */ + void AddToString(String & s, uint32 maxRecurseLevel = MUSCLE_NO_LIMIT, int indentLevel = 0) const; + + /** Renames a field. + * @param old_entry Field name to rename from. + * @param new_entry Field name to rename to. If a field with this name already exists, + * it will be replaced. + * @return B_NO_ERROR on success, or B_ERROR if a field named (old_entry) couldn't be found. + */ + status_t Rename(const String & old_entry, const String & new_entry); + + /** Returns false (Messages can be of various sizes, depending on how many fields they have, etc.) */ + virtual bool IsFixedSize() const {return false;} + + /** Returns B_MESSAGE_TYPE */ + virtual uint32 TypeCode() const {return B_MESSAGE_TYPE;} + + /** Returns The number of bytes it would take to flatten this Message into a byte buffer. */ + virtual uint32 FlattenedSize() const; + + /** + * Converts this Message into a flattened buffer of bytes that can be saved to disk + * or sent over a network, and later converted back into an identical Message object. + * @param buffer The byte buffer to store the Message into. It must be FlattenedSize() bytes long. + */ + virtual void Flatten(uint8 *buffer) const; + + /** + * Convert the given byte buffer back into a Message. Any previous contents of + * this Message will be erased, and replaced with the data specified in the byte buffer. + * @param buf Pointer to a byte buffer containing a flattened Message to restore. + * @param size The number of bytes in the flattened byte buffer. + * @return B_NO_ERROR if the buffer was successfully Unflattened, or B_ERROR if there + * was an error (usually meaning the buffer was corrupt, or out-of-memory) + */ + virtual status_t Unflatten(const uint8 *buf, uint32 size); + + /** Adds a new string to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The string to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddString(const String & fieldName, const String & val); + + /** Adds a new int8 to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The int8 to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddInt8(const String & fieldName, int8 val); + + /** Adds a new int16 to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The int16 to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddInt16(const String & fieldName, int16 val); + + /** Adds a new int32 to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The int32 to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddInt32(const String & fieldName, int32 val); + + /** Adds a new int64 to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The int64 to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddInt64(const String & fieldName, int64 val); + + /** Adds a new boolean to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The boolean to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddBool(const String & fieldName, bool val); + + /** Adds a new float to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The float to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddFloat(const String & fieldName, float val); + + /** Adds a new double to the Message. + * @param fieldName Name of the field to add (or add to) + * @param val The double to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddDouble(const String & fieldName, double val); + + /** Adds a new Message to the Message. + * Note that this method is less efficient than the + * AddMessage(MessageRef) method, as this method + * necessitates copying all the data in (msg). + * @param fieldName Name of the field to add (or add to) + * @param msg The Message to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddMessage(const String & fieldName, const Message & msg) {return AddMessage(fieldName, GetMessageFromPool(msg));} + + /** Adds a new Message to the Message. + * Note that this method is more efficient than the + * AddMessage(const Message &) method, as this method + * need not make a copy of the referenced Message. + * @param fieldName Name of the field to add (or add to) + * @param msgRef Reference to the Message to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddMessage(const String & fieldName, const MessageRef & msgRef); + + /** Calls SaveToArchive() on the specified object to save its + * state into a Message, then adds the resulting Message into this Message. + * @param fieldName Name of the field to add (or add to) + * @param obj The object to archive. May be any type with a SaveToArchive() method. + * @return B_NO_ERROR on success, B_ERROR if out of memory or SaveToArchive() failed. + */ + template inline status_t AddArchiveMessage(const String & fieldName, const T & obj); + + /** Adds a new pointer value to the Message. + * @param fieldName Name of the field to add (or add to) + * @param ptr The pointer value to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddPointer(const String & fieldName, const void * ptr); + + /** Adds a new point to the Message. + * @param fieldName Name of the field to add (or add to) + * @param point The point to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddPoint(const String & fieldName, const Point & point); + + /** Adds a new rect to the Message. + * @param fieldName Name of the field to add (or add to) + * @param rect The rect to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddRect(const String & fieldName, const Rect & rect); + + /** Flattens a Flattenable object and adds the resulting bytes into this Message. + * @param fieldName Name of the field to add (or add to) + * @param obj The Flattenable object (or at least an object with TypeCode(), Flatten(), + * and FlattenedSize() methods). Flatten() is called on this object, and the + * resulting bytes are appended to the given field in this Message. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + template status_t AddFlat(const String & fieldName, const T & obj) {return AddFlatAux(fieldName, GetFlattenedByteBufferFromPool(obj), obj.TypeCode(), false);} + + /** Adds a reference to a FlatCountable object to the Message. + * @param fieldName Name of the field to add (or add to) + * @param ref The FlatCountable reference to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddFlat(const String & fieldName, const FlatCountableRef & ref); + + /** Adds a reference to a ByteBuffer object to the Message. + * @param fieldName Name of the field to add (or add to) + * @param ref The ByteBuffer reference to add + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddFlat(const String & fieldName, const ByteBufferRef & ref) + { + FlatCountableRef fcRef; fcRef.SetFromRefCountableRefUnchecked(ref.GetRefCountableRef()); + return AddFlat(fieldName, fcRef); + } + + /** Adds a new ephemeral-tag-item to this Message. Tags are references to arbitrary + * ref-countable C++ objects; They can be added to a Message as a matter of convenience + * to the local program, but note that they are not persistent--they won't get + * Flatten()'d with the rest of the contents of the Message! + * @param fieldName Name of the field to add (or add to) + * @param tagRef Reference to the tag object to add. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t AddTag(const String & fieldName, const RefCountableRef & tagRef); + + /** Generic method, capable of adding any type of data to the Message. + * @param fieldName Name of the field to add (or add to) + * @param type The B_*_TYPE code indicating the type of data that will be added. + * @param data A pointer to the bytes of data to add. The data is copied out of + * this byte buffer, and exactly how it is interpreted depends on (type). + * (data) may be NULL, in which case default objects (or uninitialized byte space) is added. + * Note: If you are adding B_MESSAGE_TYPE, then (data) must point to a MessageRef + * object, and NOT a Message object, or flattened-Message-buffer! + * (This is inconsistent with BMessage::AddData, which expects a flattened BMessage buffer. Sorry!) + * @param numBytes The number of bytes that are pointed to by (data). If (type) + * is a known type such as B_INT32_TYPE or B_RECT_TYPE, (numBytes) + * may be a multiple of the datatype's size, allowing you to add + * multiple entries with a single call. + * @return B_NO_ERROR on success, B_ERROR if out of memory or (numBytes) isn't a multiple (type)'s + * known size, or a type conflict occurred. + */ + status_t AddData(const String & fieldName, uint32 type, const void *data, uint32 numBytes) {return AddDataAux(fieldName, data, numBytes, type, false);} + + /** Prepends a new string to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The string to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependString(const String & fieldName, const String & val); + + /** Prepends a new int8 to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The int8 to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependInt8(const String & fieldName, int8 val); + + /** Prepends a new int16 to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The int16 to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependInt16(const String & fieldName, int16 val); + + /** Prepends a new int32 to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The int32 to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependInt32(const String & fieldName, int32 val); + + /** Prepends a new int64 to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The int64 to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependInt64(const String & fieldName, int64 val); + + /** Prepends a new boolean to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The boolean to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependBool(const String & fieldName, bool val); + + /** Prepends a new float to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The float to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependFloat(const String & fieldName, float val); + + /** Prepends a new double to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param val The double to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependDouble(const String & fieldName, double val); + + /** Prepends a new Message to the beginning of a field array in the Message. + * Note that this method is less efficient than the AddMessage(MessageRef) method, + * as this method necessitates making a copy of (msg) and all the data it contains. + * @param fieldName Name of the field to add (or prepend to) + * @param msg The Message to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependMessage(const String & fieldName, const Message & msg) {return PrependMessage(fieldName, GetMessageFromPool(msg));} + + /** Prepends a new Message to the beginning of a field array in the Message. + * Note that this method is more efficient than the + * PrependMessage(const Message &) method, as this method + * need not make a copy of the referenced Message. + * @param fieldName Name of the field to add (or prepend to) + * @param msgRef Reference to the Message to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependMessage(const String & fieldName, const MessageRef & msgRef); + + /** Calls SaveToArchive() on the specified object to save its + * state into a Message, then prepends the resulting Message into this Message. + * @param fieldName Name of the field to add (or add to) + * @param obj The object to archive. May be any type with a SaveToArchive() method. + * @return B_NO_ERROR on success, B_ERROR if out of memory or SaveToArchive() failed. + */ + template inline status_t PrependArchiveMessage(const String & fieldName, const T & obj); + + /** Prepends a new pointer value to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param ptr The pointer value to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependPointer(const String & fieldName, const void * ptr); + + /** Prepends a new point to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param point The point to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependPoint(const String & fieldName, const Point & point); + + /** Prepends a new rectangle to the beginning of a field array in the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param rect The rectangle to add or prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependRect(const String & fieldName, const Rect & rect); + + /** Flattens a Flattenable object and adds the resulting bytes into this Message. + * @param fieldName Name of the field to add (or prepend to) + * @param obj The Flattenable object (or at least an object with TypeCode(), Flatten(), + * and FlattenedSize() methods). Flatten() is called on this object, and the + * resulting bytes are appended to the given field in this Message. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + template status_t PrependFlat(const String & fieldName, const T & obj) {return AddFlatAux(fieldName, GetFlattenedByteBufferFromPool(obj), obj.TypeCode(), true);} + + /** Prepends a reference to a FlatCountable object to the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param flatRef FlatCountable reference to prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependFlat(const String & fieldName, const FlatCountableRef & flatRef); + + /** Prepends a reference to a ByteBuffer object to the Message. + * @param fieldName Name of the field to add (or prepend to) + * @param ref The ByteBuffer reference to prepend + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependFlat(const String & fieldName, const ByteBufferRef & ref) + { + FlatCountableRef fcRef; fcRef.SetFromRefCountableRefUnchecked(ref.GetRefCountableRef()); + return PrependFlat(fieldName, fcRef); + } + + /** Prepends a new ephemeral-tag-item to this Message. Tags are references to arbitrary + * ref-countable C++ objects; They can be added to a Message as a matter of convenience + * to the local program, but note that they are not persistent--they won't get + * Flatten()'d with the rest of the contents of the Message! + * @param fieldName Name of the field to add (or prepend to) + * @param tagRef Reference to the tag object to add or prepend. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t PrependTag(const String & fieldName, const RefCountableRef & tagRef); + + /** Generic method, capable of prepending any type of data to the Message. + * @param fieldName Name of the field to add (or add to) + * @param type The B_*_TYPE code indicating the type of data that will be added. + * @param data A pointer to the bytes of data to add. The data is copied out of + * this byte buffer, and exactly how it is interpreted depends on (type). + * (data) may be NULL, in which case default objects (or uninitialized byte space) is added. + * Note: If you are adding B_MESSAGE_TYPE, then (data) must point to a MessageRef + * object, and NOT a Message object, or flattened-Message-buffer! + * (This is inconsistent with BMessage::AddData, which expects a flattened BMessage buffer. Sorry!) + * @param numBytes The number of bytes that are pointed to by (data). If (type) + * is a known type such as B_INT32_TYPE or B_RECT_TYPE, (numBytes) + * may be a multiple of the datatype's size, allowing you to add + * multiple entries with a single call. + * @return B_NO_ERROR on success, B_ERROR if out of memory or (numBytes) isn't a multiple (type)'s + * known size, or a type conflict occurred. + */ + status_t PrependData(const String & fieldName, uint32 type, const void *data, uint32 numBytes) {return AddDataAux(fieldName, data, numBytes, type, true);} + + /** Removes the (index)'th item from the given field entry. If the field entry becomes + * empty, the field itself is removed also. + * @param fieldName Name of the field to remove an item from. + * @param index Index of the item to remove. + * @return B_NO_ERROR on success, B_ERROR if the field name wasn't found, or the specified index was invalid. + */ + status_t RemoveData(const String & fieldName, uint32 index = 0); + + /** Removes the given field name and its contents from the Message. + * @param fieldName Name of the field to remove. + * @return B_NO_ERROR on success, B_ERROR if the field name wasn't found. + */ + status_t RemoveName(const String & fieldName); + + /** Clears all fields from the Message. + * @param releaseCachedBuffers If set true, any cached buffers we are holding will be immediately freed. + * Otherwise, they will be kept around for future re-use. + */ + void Clear(bool releaseCachedBuffers = false) {_entries.Clear(releaseCachedBuffers);} + + /** Retrieve a string value from the Message. + * @param fieldName The field name to look for the string value under. + * @param index The index of the string item in its field entry. + * @param setMe On success, this pointer will be pointing to a String containing the result. + * @return B_NO_ERROR if the string value was found, or B_ERROR if it wasn't. + */ + status_t FindString(const String & fieldName, uint32 index, const String ** setMe) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindString(const String & fieldName, const String ** setMe) const {return FindString(fieldName, 0, setMe);} + + /** Retrieve a string value from the Message. + * @param fieldName The field name to look for the string value under. + * @param index The index of the string item in its field entry. + * @param setMe On success, a pointer to the string value is written into this object. + * @return B_NO_ERROR if the string value was found, or B_ERROR if it wasn't. + */ + status_t FindString(const String & fieldName, uint32 index, const char * & setMe) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindString(const String & fieldName, const char * & setMe) const {return FindString(fieldName, 0, setMe);} + + /** Retrieve a string value from the Message. + * @param fieldName The field name to look for the string value under. + * @param index The index of the string item in its field entry. + * @param setMe On success, the value of the string is written into this object. + * @return B_NO_ERROR if the string value was found, or B_ERROR if it wasn't. + */ + status_t FindString(const String & fieldName, uint32 index, String & setMe) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindString(const String & fieldName, String & setMe) const {return FindString(fieldName, 0, setMe);} + + /** Retrieve an int8 value from the Message. + * @param fieldName The field name to look for the int8 value under. + * @param index The index of the int8 item in its field entry. + * @param val On success, the value of the int8 is written into this object. + * @return B_NO_ERROR if the int8 value was found, or B_ERROR if it wasn't. + */ + status_t FindInt8(const String & fieldName, uint32 index, int8 & val) const {return FindDataItemAux(fieldName, index, B_INT8_TYPE, &val, sizeof(int8));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindInt8(const String & fieldName, int8 & value) const {return FindInt8(fieldName, 0, value);} + + /** Retrieve an int16 value from the Message. + * @param fieldName The field name to look for the int16 value under. + * @param index The index of the int16 item in its field entry. + * @param val On success, the value of the int16 is written into this object. + * @return B_NO_ERROR if the int16 value was found, or B_ERROR if it wasn't. + */ + status_t FindInt16(const String & fieldName, uint32 index, int16 & val) const {return FindDataItemAux(fieldName, index, B_INT16_TYPE, &val, sizeof(int16));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindInt16(const String & fieldName, int16 & value) const {return FindInt16(fieldName, 0, value);} + + /** Retrieve an int32 value from the Message. + * @param fieldName The field name to look for the int32 value under. + * @param index The index of the int32 item in its field entry. + * @param val On success, the value of the int32 is written into this object. + * @return B_NO_ERROR if the int32 value was found, or B_ERROR if it wasn't. + */ + status_t FindInt32(const String & fieldName, uint32 index, int32 & val) const {return FindDataItemAux(fieldName, index, B_INT32_TYPE, &val, sizeof(int32));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindInt32(const String & fieldName, int32 & value) const {return FindInt32(fieldName, 0, value);} + + /** Retrieve an int64 value from the Message. + * @param fieldName The field name to look for the int64 value under. + * @param index The index of the int64 item in its field entry. + * @param val On success, the value of the int64 is written into this object. + * @return B_NO_ERROR if the int64 value was found, or B_ERROR if it wasn't. + */ + status_t FindInt64(const String & fieldName, uint32 index, int64 & val) const {return FindDataItemAux(fieldName, index, B_INT64_TYPE, &val, sizeof(int64));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindInt64(const String & fieldName, int64 & value) const {return FindInt64(fieldName, 0, value);} + + /** Retrieve a boolean value from the Message. + * @param fieldName The field name to look for the boolean value under. + * @param index The index of the boolean item in its field entry. + * @param val On success, the value of the boolean is written into this object. + * @return B_NO_ERROR if the boolean value was found, or B_ERROR if it wasn't. + */ + status_t FindBool(const String & fieldName, uint32 index, bool & val) const {return FindDataItemAux(fieldName, index, B_BOOL_TYPE, &val, sizeof(bool));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindBool(const String & fieldName, bool & value) const {return FindBool(fieldName, 0, value);} + + /** Retrieve a float value from the Message. + * @param fieldName The field name to look for the float value under. + * @param index The index of the float item in its field entry. + * @param val On success, the value of the float is written into this object. + * @return B_NO_ERROR if the float value was found, or B_ERROR if it wasn't. + */ + status_t FindFloat(const String & fieldName, uint32 index, float & val) const {return FindDataItemAux(fieldName, index, B_FLOAT_TYPE, &val, sizeof(float));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindFloat(const String & fieldName, float & f) const {return FindFloat(fieldName, 0, f);} + + /** Retrieve a double value from the Message. + * @param fieldName The field name to look for the double value under. + * @param index The index of the double item in its field entry. + * @param val On success, the value of the double is written into this object. + * @return B_NO_ERROR if the double value was found, or B_ERROR if it wasn't. + */ + status_t FindDouble(const String & fieldName, uint32 index, double & val) const {return FindDataItemAux(fieldName, index, B_DOUBLE_TYPE, &val, sizeof(double));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindDouble(const String & fieldName, double & d) const {return FindDouble(fieldName, 0, d);} + + /** Retrieve a Message value from the Message. + * Note that this method is less efficient than the FindMessage(MessageRef) + * method, because it forces the held Message's data to be copied into (msg) + * @param fieldName The field name to look for the Message value under. + * @param index The index of the Message item in its field entry. + * @param msg On success, the value of the Message is written into this object. + * @return B_NO_ERROR if the Message value was found, or B_ERROR if it wasn't. + */ + status_t FindMessage(const String & fieldName, uint32 index, Message & msg) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindMessage(const String & fieldName, Message & msg) const {return FindMessage(fieldName, 0, msg);} + + /** Retrieve a Message value from the Message. + * Note that this method is more efficient than the FindMessage(MessageRef) + * method, because it the retrieved Message need not be copied. + * @param fieldName The field name to look for the Message value under. + * @param index The index of the Message item in its field entry. + * @param msgRef On success, the value of the Message is written into this object. + * @return B_NO_ERROR if the Message value was found, or B_ERROR if it wasn't. + */ + status_t FindMessage(const String & fieldName, uint32 index, MessageRef & msgRef) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindMessage(const String & fieldName, MessageRef & msgRef) const {return FindMessage(fieldName, 0, msgRef);} + + /** Convenience method: Retrieves a Message value from this Message, + * and if successful, calls SetFromArchive(msg) on the passed-in object. + * @param fieldName The field name to look for the Message value under. + * @param index The index of the Message item in its field entry. + * @param obj The object to call SetFromArchive(msg) on. Since this method is templated, + * this object may be of any type that has a SetFromArchive(const Message &) method. + * @return B_NO_ERROR if the Message value was found and SetFromArchive() returned B_NO_ERROR, or B_ERROR otherwise. + */ + template inline status_t FindArchiveMessage(const String & fieldName, uint32 index, T & obj) const + { + MessageRef msg; + return (FindMessage(fieldName, index, msg) == B_NO_ERROR) ? obj.SetFromArchive(*msg()) : B_ERROR; + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + template inline status_t FindArchiveMessage(const String & fieldName, T & obj) const {return FindArchiveMessage(fieldName, 0, obj);} + + /** Convenience method: Retrieves a Message value from this Message, + * and if successful, calls SetFromArchive(foundMsg) on the passed-in object. + * If the specified sub-Message is not found, this method calls SetFromArchive(defaultMsg) on the object instead. + * @param fieldName The field name to look for the Message value under. + * @param index The index of the Message item in its field entry. + * @param obj The object to call SetFromArchive(msg) on. Since this method is templated, + * this object may be of any type that has a SetFromArchive(const Message &) method. + * @param defaultMsg The Message to pass to SetFromArchive() if no sub-Message is found. Defaults to GetEmptyMessage(). + * @return The value returned by obj.SetFromArchive(). + */ + template inline status_t FindArchiveMessageWithDefault(const String & fieldName, uint32 index, T & obj, const Message & defaultMsg = GetEmptyMessage()) const + { + MessageRef msg; + return (FindMessage(fieldName, index, msg) == B_NO_ERROR) ? obj.SetFromArchive(*msg()) : obj.SetFromArchive(defaultMsg); + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + template inline status_t FindArchiveMessageWithDefault(const String & fieldName, T & obj, const Message & defaultMsg = GetEmptyMessage()) const {return FindArchiveMessageWithDefault(fieldName, 0, obj, defaultMsg);} + + /** Retrieve a pointer value from the Message. + * @param fieldName The field name to look for the pointer value under. + * @param index The index of the pointer item in its field entry. + * @param val On success, the value of the pointer is written into this object. + * @return B_NO_ERROR if the pointer value was found, or B_ERROR if it wasn't. + */ + status_t FindPointer(const String & fieldName, uint32 index, void * & val) const {return FindDataItemAux(fieldName, index, B_POINTER_TYPE, &val, sizeof(void *));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindPointer(const String & fieldName, void * & ptr) const {return FindPointer(fieldName, 0, ptr);} + + /** Retrieve a point value from the Message. + * @param fieldName The field name to look for the point value under. + * @param index The index of the point item in its field entry. + * @param point On success, the value of the point is written into this object. + * @return B_NO_ERROR if the point value was found, or B_ERROR if it wasn't. + */ + status_t FindPoint(const String & fieldName, uint32 index, Point & point) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindPoint(const String & fieldName, Point & point) const {return FindPoint(fieldName, 0, point);} + + /** Retrieve a rectangle value from the Message. + * @param fieldName The field name to look for the rectangle value under. + * @param index The index of the rectangle item in its field entry. + * @param rect On success, the value of the rectangle is written into this object. + * @return B_NO_ERROR if the rectangle value was found, or B_ERROR if it wasn't. + */ + status_t FindRect(const String & fieldName, uint32 index, Rect & rect) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindRect(const String & fieldName, Rect & rect) const {return FindRect(fieldName, 0, rect);} + + /** Retrieve a flattened object from the Message. + * @param fieldName The field name to look for the flattened object value under. + * @param index The index of the flattened object item in its field entry. + * @param obj On success, the flattened object is copied into this object. + * @return B_NO_ERROR if the flattened object was found, or B_ERROR if it wasn't. + */ + template status_t FindFlat(const String & fieldName, uint32 index, T & obj) const + { + uint32 arrayTypeCode; + const AbstractDataArray * ada = GetArrayAndTypeCode(fieldName, index, &arrayTypeCode); + if ((ada)&&(obj.AllowsTypeCode(arrayTypeCode))) + { + uint32 numBytes; + const FlatCountable * fcPtr; + const uint8 * ptr = FindFlatAux(ada, index, numBytes, &fcPtr); + if (ptr) return obj.Unflatten(ptr, numBytes); + else if (fcPtr) return obj.CopyFrom(*fcPtr); + } + return B_ERROR; + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + template status_t FindFlat(const String & fieldName, T & obj) const {return FindFlat(fieldName, 0, obj);} + + /** Retrieve a FlatCountable reference from the Message. + * @param fieldName The field name to look for the FlatCountable reference under. + * @param index The index of the flattened object item in its field entry. + * @param ref On success, this reference will refer to the FlatCountable object. + * @return B_NO_ERROR if the flattened object was found, or B_ERROR if it wasn't. + */ + status_t FindFlat(const String & fieldName, uint32 index, FlatCountableRef & ref) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindFlat(const String & fieldName, FlatCountableRef & ref) const {return FindFlat(fieldName, 0, ref);} + + /** Convenience method: As above, only the result is placed into the given ByteBufferRef object. + * This saves you having to do the necessary FlatCountableRef->ByteBufferRef casting yourself. + */ + status_t FindFlat(const String & fieldName, uint32 index, ByteBufferRef & ref) const + { + FlatCountableRef fcRef; + return (FindFlat(fieldName, index, fcRef) == B_NO_ERROR) ? ref.SetFromRefCountableRef(fcRef.GetRefCountableRef()) : B_ERROR; + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindFlat(const String & fieldName, ByteBufferRef & ref) const {return FindFlat(fieldName, 0, ref);} + + /** Retrieve an ephemeral-tag-item from the Message. + * @param fieldName Name of the field to look for the tag under. + * @param index The index of the tag item in its field entry. + * @param tagRef On success, this object becomes a reference to the found tag object. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + status_t FindTag(const String & fieldName, uint32 index, RefCountableRef & tagRef) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindTag(const String & fieldName, RefCountableRef & tagRef) const {return FindTag(fieldName, 0, tagRef);} + + /** Retrieve a pointer to the raw data bytes of a stored message field of any type. + * @param fieldName The field name to retrieve the pointer to + * @param type The type code of the field you are interested, or B_ANY_TYPE if any type is acceptable. + * @param index Index of the data item to look for (within the field) + * @param data On success, a pointer to the data bytes will be written into this object. + * Note: If you are retrieving B_MESSAGE_TYPE, then (data) will be set to point to a MessageRef + * object, and NOT a Message object, or flattened-Message-buffer! + * (This is inconsistent with BMessage::FindData, which returns a flattened BMessage buffer. Sorry!) + * @param numBytes On success, the number of bytes in the returned data array will be written into this object. (May be NULL) + * @return B_NO_ERROR if the data pointer was retrieved successfully, or B_ERROR if it wasn't. + */ + status_t FindData(const String & fieldName, uint32 type, uint32 index, const void **data, uint32 *numBytes) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindData(const String & fieldName, uint32 type, const void **data, uint32 *numBytes) const {return FindData(fieldName, type, 0, data, numBytes);} + + /** As above, only this returns a pointer to an array that you can write directly into, for efficiency. + * You should be very careful with this method, though, as you will be writing into the guts of this Message. + * The returned pointer is not guaranteed to be valid after the Message is modified by any method! + * @see FindData(...) + */ + status_t FindDataPointer(const String & fieldName, uint32 type, uint32 index, void **data, uint32 *numBytes) const; + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t FindDataPointer(const String & fieldName, uint32 type, void **data, uint32 *numBytes) const {return FindDataPointer(fieldName, type, 0, data, numBytes);} + + /** Replace a string value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param newString The new string value to put overwrite the old string with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceString(bool okayToAdd, const String & fieldName, uint32 index, const String & newString); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceString(bool okayToAdd, const String & fieldName, const String & newString) {return ReplaceString(okayToAdd, fieldName, 0, newString);} + + /** Replace an int8 value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new int8 value to put overwrite the old int8 with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceInt8(bool okayToAdd, const String & fieldName, uint32 index, int8 val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceInt8(bool okayToAdd, const String & fieldName, int8 val) {return ReplaceInt8(okayToAdd, fieldName, 0, val);} + + /** Replace an int16 value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new int16 value to put overwrite the old int16 with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceInt16(bool okayToAdd, const String & fieldName, uint32 index, int16 val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceInt16(bool okayToAdd, const String & fieldName, int16 val) {return ReplaceInt16(okayToAdd, fieldName, 0, val);} + + /** Replace an int32 value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new int32 value to put overwrite the old int32 with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceInt32(bool okayToAdd, const String & fieldName, uint32 index, int32 val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceInt32(bool okayToAdd, const String & fieldName, int32 val) {return ReplaceInt32(okayToAdd, fieldName, 0, val);} + + /** Replace an int64 value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new int64 value to put overwrite the old int8 with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceInt64(bool okayToAdd, const String & fieldName, uint32 index, int64 val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceInt64(bool okayToAdd, const String & fieldName, int64 val) {return ReplaceInt64(okayToAdd, fieldName, 0, val);} + + /** Replace a boolean value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new boolean value to put overwrite the old boolean with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceBool(bool okayToAdd, const String & fieldName, uint32 index, bool val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceBool(bool okayToAdd, const String & fieldName, bool val) {return ReplaceBool(okayToAdd, fieldName, 0, val);} + + /** Replace a float value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new float value to put overwrite the old float with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceFloat(bool okayToAdd, const String & fieldName, uint32 index, float val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceFloat(bool okayToAdd, const String & fieldName, float val) {return ReplaceFloat(okayToAdd, fieldName, 0, val);} + + /** Replace a double value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param val The new double value to put overwrite the old double with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceDouble(bool okayToAdd, const String & fieldName, uint32 index, double val); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceDouble(bool okayToAdd, const String & fieldName, double val) {return ReplaceDouble(okayToAdd, fieldName, 0, val);} + + /** Replace a pointer value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param ptr The new pointer value to put overwrite the old pointer with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplacePointer(bool okayToAdd, const String & fieldName, uint32 index, const void * ptr); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplacePointer(bool okayToAdd, const String & fieldName, const void * ptr) {return ReplacePointer(okayToAdd, fieldName, 0, ptr);} + + /** Replace a point value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param point The new point value to put overwrite the old point with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplacePoint(bool okayToAdd, const String & fieldName, uint32 index, const Point & point); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplacePoint(bool okayToAdd, const String & fieldName, const Point & point) {return ReplacePoint(okayToAdd, fieldName, 0, point);} + + /** Replace a rectangle value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param rect The new rectangle value to put overwrite the old rectangle with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceRect(bool okayToAdd, const String & fieldName, uint32 index, const Rect & rect); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceRect(bool okayToAdd, const String & fieldName, const Rect & rect) {return ReplaceRect(okayToAdd, fieldName, 0, rect);} + + /** Replace a Message value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param msg The new Message value to put overwrite the old Message with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceMessage(bool okayToAdd, const String & fieldName, uint32 index, const Message & msg) {return ReplaceMessage(okayToAdd, fieldName, index, GetMessageFromPool(msg));} + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceMessage(bool okayToAdd, const String & fieldName, const Message & msg) {return ReplaceMessage(okayToAdd, fieldName, 0, msg);} + + /** Replace a Message value in an existing Message field with a new value. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param msgRef The new Message value to put overwrite the old Message with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceMessage(bool okayToAdd, const String & fieldName, uint32 index, const MessageRef & msgRef); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceMessage(bool okayToAdd, const String & fieldName, const MessageRef & msgRef) {return ReplaceMessage(okayToAdd, fieldName, 0, msgRef);} + + /** Replace a Message value in an existing Message field with a new value + * that was generated by calling SaveToArchive() on the passed in object. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param obj The object to call SaveToArchive() on. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, if SaveToArchive() failed, or if (index) wasn't a valid index, or out of memory. + */ + template inline status_t ReplaceArchiveMessage(bool okayToAdd, const String & fieldName, uint32 index, const T & obj) + { + return ReplaceMessage(okayToAdd, fieldName, index, GetArchiveMessageFromPool(obj)); + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + template inline status_t ReplaceArchiveMessage(bool okayToAdd, const String & fieldName, const T & obj) {return ReplaceArchiveMessage(okayToAdd, fieldName, 0, obj);} + + /** Flattens a Flattenable object and adds the resulting bytes into this Message. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName Name of the field to add (or add to) + * @param index The index of the entry within the field name to modify + * @param obj The Flattenable object (or at least an object with TypeCode(), Flatten(), + * and FlattenedSize() methods). Flatten() is called on this object, and the + * resulting bytes are appended to the given field in this Message. + * @return B_NO_ERROR on success, B_ERROR if out of memory or a type conflict occurred + */ + template status_t ReplaceFlat(bool okayToAdd, const String & fieldName, uint32 index, const T & obj) {return ReplaceFlatAux(okayToAdd, fieldName, index, GetFlattenedByteBufferFromPool(obj), obj.TypeCode());} + + /** As above, except that the index argument is implicitly zero. */ + template status_t ReplaceFlat(bool okayToAdd, const String & fieldName, const T & obj) {return ReplaceFlat(okayToAdd, fieldName, 0, obj);} + + /** Replace a FlatCountable reference in an existing Message field with a new reference. + * @param okayToAdd If set true, attempting to replace an reference that doesn't exist will cause the new reference to be added to the end of the field array, instead. If false, attempting to replace a non-existent reference will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param ref The new FlatCountableRef put overwrite the old reference with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceFlat(bool okayToAdd, const String & fieldName, uint32 index, const FlatCountableRef & ref); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceFlat(bool okayToAdd, const String & fieldName, FlatCountableRef & ref) {return ReplaceFlat(okayToAdd, fieldName, 0, ref);} + + /** As above, only (ref) is specified as a ByteBufferRef, to save you having to do the necessary casting to FlatCountableRef yourself */ + status_t ReplaceFlat(bool okayToAdd, const String & fieldName, uint32 index, const ByteBufferRef & ref) + { + FlatCountableRef fcRef; fcRef.SetFromRefCountableRefUnchecked(ref.GetRefCountableRef()); + return ReplaceFlat(okayToAdd, fieldName, index, fcRef); + } + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceFlat(bool okayToAdd, const String & fieldName, ByteBufferRef & ref) {return ReplaceFlat(okayToAdd, fieldName, 0, ref);} + + /** Replace a tag object in an existing Message field with a new tag object. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name of an existing field to modify + * @param index The index of the entry within the field name to modify + * @param tag The new tag reference to overwrite the old tag reference with. + * @return B_NO_ERROR on success, or B_ERROR if the field wasn't found, or if (index) wasn't a valid index, or out of memory. + */ + status_t ReplaceTag(bool okayToAdd, const String & fieldName, uint32 index, const RefCountableRef & tag); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceTag(bool okayToAdd, const String & fieldName, const RefCountableRef & tag) {return ReplaceTag(okayToAdd, fieldName, 0, tag);} + + /** Replace one entry in a field of any type. + * @param okayToAdd If set true, attempting to replace an item that doesn't exist will cause the new item to be added to the end of the field array, instead. If false, attempting to replace a non-existent item will cause B_ERROR to be returned with no side effects. + * @param fieldName The field name to replace an entry in. + * @param type The uint32 of the field you are interested, or B_ANY_TYPE if any type is acceptable. + * @param index Index in the field to replace the data at. + * @param data The bytes representing the item you wish to replace an old item in the field with + * @param numBytes The number of bytes in your data array. + * Note: If you are replacing B_MESSAGE_TYPE, then (data) must point to a MessageRef + * object, and NOT a Message object, or flattened-Message-buffer! + * (This is inconsistent with BMessage::ReplaceData, which expects a flattened BMessage buffer. Sorry!) + * @return B_NO_ERROR if the data item(s) were replaced successfully, or B_ERROR they weren't. + */ + status_t ReplaceData(bool okayToAdd, const String & fieldName, uint32 type, uint32 index, const void *data, uint32 numBytes); + + /** As above, only (index) isn't specified. It is assumed to be zero. */ + status_t ReplaceData(bool okayToAdd, const String & fieldName, uint32 type, const void *data, uint32 numBytes) {return ReplaceData(okayToAdd, fieldName, type, 0, data, numBytes);} + + /** Convenience method: Returns the first field name of the given type, or NULL if none is found. + * @param optTypeCode If specified, only field names with the specified type will be considered. + * If left as the default (B_ANY_TYPE), then the first field name of any type will be returned. + * @returns a pointer to the returned field's name String on success, or NULL on failure. Note that this pointer + * is not guaranteed to remain valid if the Message is modified. + */ + const String * GetFirstFieldNameString(uint32 optTypeCode = B_ANY_TYPE) const {return GetExtremeFieldNameStringAux(optTypeCode, false);} + + /** Convenience method: Returns the last field name of the given type, or NULL if none is found. + * @param optTypeCode If specified, only field names with the specified type will be considered. + * If left as the default (B_ANY_TYPE), then the last field name of any type will be returned. + * @returns a pointer to the returned field's name String on success, or NULL on failure. Note that this pointer + * is not guaranteed to remain valid if the Message is modified. + */ + const String * GetLastFieldNameString(uint32 optTypeCode = B_ANY_TYPE) const {return GetExtremeFieldNameStringAux(optTypeCode, true);} + + /** Returns true iff there is a field with the given name and type present in the Message. + * @param fieldName the field name to look for. + * @param type the type to look for, or B_ANY_TYPE if type isn't important to you. + * @return true if such a field exists, else false. + */ + bool HasName(const String & fieldName, uint32 type = B_ANY_TYPE) const {return (GetArray(fieldName, type) != NULL);} + + /** Returns the number of values in the field with the given name and type in the Message. + * @param fieldName the field name to look for. + * @param type the type to look for, or B_ANY_TYPE if type isn't important to you. + * @return The number of values stored under (fieldName) if a field of the right type exists, + * or zero if the field doesn't exist or isn't of a matching type. + */ + uint32 GetNumValuesInName(const String & fieldName, uint32 type = B_ANY_TYPE) const; + + /** Takes the field named (fieldName) in this message, and moves the field into (moveTo). + * Any data that was already in (moveTo) under (fieldName) will be replaced. + * @param fieldName Name of an existing field (in this Message) to be moved. + * @param moveTo A Message to move the specified field into. + * @result B_NO_ERROR on success, or B_ERROR if there is an error moving the field. + */ + status_t MoveName(const String & fieldName, Message & moveTo) {return MoveName(fieldName, moveTo, fieldName);} + + /** Take the data under (oldFieldName) in this message, and moves it into (moveTo), + * where it will appear under the field name (newFieldName). + * Any data that was already in (moveTo) under (newFieldName) will be replaced. + * @param oldFieldName Name of an existing field (in this Message) to be moved. + * @param moveTo A Message to move the field into. + * @param newFieldName The name the field should have in the new Message. + * @result B_NO_ERROR on success, or B_ERROR if there is an error moving the field. + */ + status_t MoveName(const String & oldFieldName, Message & moveTo, const String & newFieldName); + + /** Take the data under (fieldName) in this message, and copies it into (copyTo). + * Any data that was already in (copyTo) under (fieldName) will be replaced. + * @param fieldName Name of an existing field (in this Message) to be copied. + * @param copyTo A Message to copy the field into. + * @result B_NO_ERROR on success, or B_ERROR if there is an error copying the field. + */ + status_t CopyName(const String & fieldName, Message & copyTo) const {return CopyName(fieldName, copyTo, fieldName);} + + /** Take the data under (oldFieldName) in this message, and copies it into (copyTo), + * where it will appear under the field name (newFieldName). + * Any data that was already in (copyTo) under (newFieldName) will be replaced. + * @param oldFieldName Name of an existing field (in this Message) to be copied. + * @param copyTo A Message to copy the field into. + * @param newFieldName The name the field should have in the new Message. + * @result B_NO_ERROR on success, or B_ERROR if there is an error copying the field. + */ + status_t CopyName(const String & oldFieldName, Message & copyTo, const String & newFieldName) const; + + /** Take the data under (fieldName) in this message, and shares it into (shareTo). + * Any data that was under (fieldName) in (shareTo) will be replaced. + * This operation is similar to CopyName(), except that no copy of the field + * data is made: instead, the field becomes shared between the two Messages, + * and changes to the field in one Message will be seen in the other. + * This method is more efficient than CopyName(), but you need to be careful + * when using it or you may get unexpected results... + * @param fieldName Name of an existing field to be shared. + * @param shareTo A Message to share the field into. + * @result B_NO_ERROR on success, or B_ERROR if there is an error sharing the field. + */ + status_t ShareName(const String & fieldName, Message & shareTo) const {return ShareName(fieldName, shareTo, fieldName);} + + /** Take the data under (oldFieldName) in this message, and shares it into (shareTo), + * where it will appear under the field name (newFieldName). + * Any data that was under (newFieldName) in (shareTo) will be replaced. + * This operation is similar to CopyName(), except that no copy of the field + * data is made: instead, the field becomes shared between the two Messages, + * and changes to the field in one Message will be seen in the other. + * This method is more efficient than CopyName(), but you need to be careful + * when using it or you may get unexpected results... + * @param oldFieldName Name of an existing field to be shared. + * @param shareTo A Message to share the field into. + * @param newFieldName The name the field should have in the new Message. + * @result B_NO_ERROR on success, or B_ERROR if there is an error sharing the field. + */ + status_t ShareName(const String & oldFieldName, Message & shareTo, const String & newFieldName) const; + + /** Moves the field with the specified name to the beginning of the field-names-iteration-list. + * @param fieldNameToMove Name of the field to move to the beginning of the iteration list. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found?) + */ + status_t MoveNameToFront(const String & fieldNameToMove) {return _entries.MoveToFront(fieldNameToMove);} + + /** Moves the field with the specified name to the end of the field-names-iteration-list. + * @param fieldNameToMove Name of the field to move to the end of the iteration list. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found?) + */ + status_t MoveNameToBack(const String & fieldNameToMove) {return _entries.MoveToBack(fieldNameToMove);} + + /** Moves the field with the specified name to just before the second specified field name. + * @param fieldNameToMove Name of the field to move + * @param toBeforeMe Name of the field that (fieldNameToMove) should appear just before. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found?) + */ + status_t MoveNameToBefore(const String & fieldNameToMove, const String & toBeforeMe) {return _entries.MoveToBefore(fieldNameToMove, toBeforeMe);} + + /** Moves the field with the specified name to just after the second specified field name. + * @param fieldNameToMove Name of the field to move + * @param toBehindMe Name of the field that (fieldNameToMove) should appear just after. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found?) + */ + status_t MoveNameToBehind(const String & fieldNameToMove, const String & toBehindMe) {return _entries.MoveToBehind(fieldNameToMove, toBehindMe);} + + /** Moves the field with the specified name to the nth position in the field-names-iteration-list. + * @param fieldNameToMove Name of the field to move + * @param toPosition The position to move it to (0==first, 1=second, and so on) + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found?) + */ + status_t MoveNameToPosition(const String & fieldNameToMove, uint32 toPosition) {return _entries.MoveToPosition(fieldNameToMove, toPosition);} + + /** Examines the specified field to see if it is referenced more than once (e.g. by + * another Message object). If it is referenced more than once, makes a copy of the + * field (including its contents) and replaces the shared field with the copy. + * If the field is not referenced more than once, this method returns B_NO_ERROR + * without doing anything. + * This method is handy when you are about to modify a field in a Message and you want + * to first make sure that the field isn't shared by any other Messages. + * @param fieldName Name of the field to ensure the non-sharedness of. + * @returns B_NO_ERROR on success (i.e. either the copy was made, or no copy was necessary) + * or B_ERROR on failure (field not found, or out-of-memory) + */ + status_t EnsureFieldIsPrivate(const String & fieldName); + + /** Ensures that the data items held in (field) are stored as a contiguous array in memory, + * and then returns a pointer to the beginning of the array. + * Note: In most cases, the contents of the field will already be stored contiguously, and so this + * method will be very efficient (O(1)). However, if the field was previously modified using any of + * our Prepend*() methods, then the field data may need to be normalized into a contiguous + * array before a pointer can be returned... in that case, this method will call Queue::Normalize() + * to do that, which is an O(N) operation with respect to the number of data items stored in the field. + * @param fieldName The name of the field we wish to have a pointer to the contents of. + * @param retItemCount If non-NULL, on successful return this will hold the number of items pointed to by our return value. + * @param type Type of field you are looking for, or B_ANY_TYPE if any type will do. Defaults to B_ANY_TYPE. + * @returns A pointer to the field's data on success, or NULL if a field with the specified name and type wasn't found. + */ + void * GetPointerToNormalizedFieldData(const String & fieldName, uint32 * retItemCount, uint32 type = B_ANY_TYPE); + + /** + * Swaps the contents and 'what' code of this Message with the specified Message. + * This is a very efficient, O(1) operation. + * @param swapWith Message whose contents should be swapped with this one. + */ + void SwapContents(Message & swapWith); + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Constructor */ + Message(Message && rhs) : what(0) {SwapContents(rhs);} + + /** C++11 Move Assignment Operator */ + Message & operator =(Message && rhs) {SwapContents(rhs); return *this;} +#endif + + /** Sorts the iteration-order of this Message's field names into case-sensitive alphabetical order. */ + void SortFieldNames() {_entries.SortByKey();} + + /** Returns true iff every one of our fields has a like-named, liked-typed, equal-length field in (rhs). + * @param rhs The Message to check to see if it has a superset of our fields. + * @param compareData If true, then the data in the fields will be compared also, and true will not be returned unless all the data items are equal. + * @returns true If and only if our fields are a subset of (rhs)'s fields. + */ + bool FieldsAreSubsetOf(const Message & rhs, bool compareData) const; + + /** + * Iterates over the contents of this Message to compute a checksum. + * Note that this method can be CPU-intensive, since it has to scan + * everything in the Message. Don't call it often if you want good + * performance! + * @param countNonFlattenableFields If true, non-flattenable fields (e.g. + * those added with AddTag() will be included in the + * checksum. If false (the default), they will be + * ignored. + */ + uint32 CalculateChecksum(bool countNonFlattenableFields = false) const; + + /** + * Returns an iterator that iterates over the names of the fields in this + * message. If (type) is B_ANY_TYPE, then all field names will be included + * in the traversal. Otherwise, only names of the given type will be included. + * @param type Type of fields you wish to iterate over, or B_ANY_TYPE to iterate over all fields. + * @param flags Bit-chord of HTIT_FLAG_* flags you want to use to affect the iteration behaviour. + * This bit-chord will get passed on to the underlying HashtableIterator. Defaults + * to zero, which provides the default behaviour. + */ + MessageFieldNameIterator GetFieldNameIterator(uint32 type = B_ANY_TYPE, uint32 flags = 0) const {return MessageFieldNameIterator(_entries.GetIterator(flags), type);} + + /** + * As above, only starts the iteration at the given field name, instead of at the beginning + * or end of the list of field names. + * @param startFieldName the field name to start with. If (startFieldName) isn't present, the iteration will be empty. + * @param type Type of fields you wish to iterate over, or B_ANY_TYPE to iterate over all fields. + * @param flags Bit-chord of HTIT_FLAG_* flags you want to use to affect the iteration behaviour. + * This bit-chord will get passed on to the underlying HashtableIterator. Defaults + * to zero, which provides the default behaviour. + */ + MessageFieldNameIterator GetFieldNameIteratorAt(const String & startFieldName, uint32 type = B_ANY_TYPE, uint32 flags = 0) const {return MessageFieldNameIterator(_entries.GetIteratorAt(startFieldName, flags), type);} + + /** Makes this Message into a "light-weight" copy of (rhs). + * When this method returns, this object will look like a copy of (rhs), except that + * it will share the data in (rhs)'s fields such that modifying the data in (rhs)'s + * fields will modify the data in this Message, and vice versa. Making a light-weight + * copy can be significantly cheaper than doing a full copy (e.g. with the assignment operator), + * but you need to be aware of the potential side effects if you then go on to modify + * the contents of either Message's fields. Use with caution! + * @param rhs The Message to make this Message into a light-weight copy of. + */ + void BecomeLightweightCopyOf(const Message & rhs) {what = rhs.what; _entries = rhs._entries;} + + /** Convenience method for retrieving a C-string pointer to a string data item inside a Message. + * @param fn The field name to look for the string under. + * @param defVal The pointer to return if no data item can be found. Defaults to NULL. + * @param idx The index to look under inside the field. Defaults to zero. + * @returns a Pointer to the String data item's Nul-terminated C string array on success, or (defVal) if the item could not be found. + */ + inline const char * GetCstr(const String & fn, const char * defVal = NULL, uint32 idx = 0) const {const char * r; return (FindString( fn, idx, &r) == B_NO_ERROR) ? r : defVal;} + + /** Convenience method for retrieving a pointer to a pointer data item inside a Message. + * @param fn The field name to look for the pointer under. + * @param defVal The pointer to return if no data item can be found. Defaults to NULL. + * @param idx The index to look under inside the field. Defaults to zero. + * @returns The requested pointer on success, or (defVal) on failure. + */ + inline void * GetPointer(const String & fn, void * defVal = NULL, uint32 idx = 0) const {void * r; return (FindPointer(fn, idx, r) == B_NO_ERROR) ? r : defVal;} + + /** Convenience method for retrieving a pointer to A String data item inside a Message. + * @param fn The field name to look for the String under. + * @param defVal The pointer to return if no data item can be found. Defaults to NULL. + * @param idx The index to look under inside the field. Defaults to zero. + * @returns a Pointer to the String data item on success, or (defVal) if the item could not be found. + */ + inline const String * GetStringPointer(const String & fn, const String * defVal=NULL, uint32 idx = 0) const {const String * r; return (FindString(fn, idx, &r) == B_NO_ERROR) ? r : defVal;} + +#define DECLARE_MUSCLE_UNSIGNED_INTEGER_FIND_METHODS(bw) \ + inline status_t FindInt##bw (const String & fieldName, uint32 index, uint##bw & val) const {return FindInt##bw (fieldName, index, (int##bw &)val);} \ + inline status_t FindInt##bw (const String & fieldName, uint##bw & val) const {return FindInt##bw (fieldName, (int##bw &)val);} + DECLARE_MUSCLE_UNSIGNED_INTEGER_FIND_METHODS(8); ///< This macro defined Find methods with unsigned value arguments, so the user doesn't have to do ugly C-style casts to retrieve unsigned values. + DECLARE_MUSCLE_UNSIGNED_INTEGER_FIND_METHODS(16); ///< This macro defined Find methods with unsigned value arguments, so the user doesn't have to do ugly C-style casts to retrieve unsigned values. + DECLARE_MUSCLE_UNSIGNED_INTEGER_FIND_METHODS(32); ///< This macro defined Find methods with unsigned value arguments, so the user doesn't have to do ugly C-style casts to retrieve unsigned values. + DECLARE_MUSCLE_UNSIGNED_INTEGER_FIND_METHODS(64); ///< This macro defined Find methods with unsigned value arguments, so the user doesn't have to do ugly C-style casts to retrieve unsigned values. + +#define DECLARE_MUSCLE_POINTER_FIND_METHODS(name, type) \ + inline status_t Find##name (const String & fieldName, uint32 index, type * val) const {return Find##name (fieldName, index, *val);} \ + inline status_t Find##name (const String & fieldName, type * val) const {return Find##name (fieldName, *val);} + DECLARE_MUSCLE_POINTER_FIND_METHODS(Bool, bool); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Double, double); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Float, float); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int8, int8); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int16, int16); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int32, int32); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int64, int64); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int8, uint8); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int16, uint16); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int32, uint32); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Int64, uint64); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Point, Point); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(Rect, Rect); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + DECLARE_MUSCLE_POINTER_FIND_METHODS(String, const char *); ///< This macro defines old-style Find methods with pointer value arguments, for backwards compatibility. + +#define DECLARE_MUSCLE_CONVENIENCE_METHODS(name, type) \ + inline type Get##name(const String & fieldName, const type & defVal = type(), uint32 idx = 0) const {type r; return (Find##name (fieldName, idx, r) == B_NO_ERROR) ? (const type &)r : defVal;} \ + inline status_t CAdd##name( const String & fieldName, const type & value, const type & defVal = type()) {return (value == defVal) ? B_NO_ERROR : Add##name (fieldName, value);} \ + inline status_t CPrepend##name(const String & fieldName, const type & value, const type & defVal = type()) {return (value == defVal) ? B_NO_ERROR : Prepend##name (fieldName, value);} + DECLARE_MUSCLE_CONVENIENCE_METHODS(Bool, bool); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Double, double); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Float, float); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Int8, int8); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Int16, int16); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Int32, int32); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Int64, int64); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Point, Point); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Rect, Rect); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(String, String); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Message, MessageRef); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Flat, ByteBufferRef); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_MUSCLE_CONVENIENCE_METHODS(Tag, RefCountableRef); ///< This macro defines Get(), CAdd(), and CPrepend() methods for convience in common use cases. + DECLARE_STANDARD_CLONE_METHOD(Message); ///< implements the standard Clone() method to copy a Message object. + +protected: + /** Overridden to copy directly if (copyFrom) is a Message as well. */ + virtual status_t CopyFromImplementation(const Flattenable & copyFrom); + +private: + // Helper functions + status_t AddDataAux(const String & fieldName, uint32 type, const void *data, uint32 numBytes, bool prepend); + + // Given a known uint32, returns the size of an item of that type. + // Returns zero if items of the given type are variable length. + uint32 GetElementSize(uint32 type) const; + + RefCountableRef GetArrayRef(const String & arrayName, uint32 etc) const; + AbstractDataArray * GetArray(const String & arrayName, uint32 etc); + const AbstractDataArray * GetArray(const String & arrayName, uint32 etc) const; + AbstractDataArray * GetOrCreateArray(const String & arrayName, uint32 tc); + const AbstractDataArray * GetArrayAndTypeCode(const String & arrayName, uint32 index, uint32 * retTypeCode) const; + + status_t AddFlatAux(const String & fieldName, const FlatCountableRef & flat, uint32 etc, bool prepend); + status_t AddFlatAux(const String & fieldName, const ByteBufferRef & bufRef, uint32 etc, bool prepend) + { + FlatCountableRef fcRef; fcRef.SetFromRefCountableRefUnchecked(bufRef.GetRefCountableRef()); + return AddFlatAux(fieldName, fcRef, etc, prepend); + } + + status_t AddDataAux(const String & fieldName, const void * data, uint32 size, uint32 etc, bool prepend); + + const uint8 * FindFlatAux(const AbstractDataArray * ada, uint32 index, uint32 & retNumBytes, const FlatCountable ** optRetFCPtr) const; + status_t FindDataItemAux(const String & fieldName, uint32 index, uint32 tc, void * setValue, uint32 valueSize) const; + + status_t ReplaceFlatAux(bool okayToAdd, const String & fieldName, uint32 index, const ByteBufferRef & bufRef, uint32 tc); + status_t ReplaceDataAux(bool okayToAdd, const String & fieldName, uint32 index, void * dataBuf, uint32 bufSize, uint32 tc); + + const String * GetExtremeFieldNameStringAux(uint32 optTypeCode, bool isLast) const + { + if (optTypeCode == B_ANY_TYPE) return isLast ? _entries.GetLastKey() : _entries.GetFirstKey(); + + MessageFieldNameIterator iter(*this, optTypeCode, HTIT_FLAG_NOREGISTER|(isLast?HTIT_FLAG_BACKWARDS:0)); + return iter.HasData() ? &iter.GetFieldName() : NULL; + } + + // Iterator support methods + friend class MessageFieldNameIterator; + Hashtable _entries; +}; + +// Template specializations so that the *Flat() methods do the right thing when called with a String or Message object as the argument +#define DECLARE_MUSCLE_FLAT_SPECIALIZERS(tp) \ +template <> inline status_t Message::FindFlat( const String & fieldName, uint32 index, tp & obj) const {return Find##tp( fieldName, index, obj);} \ +template <> inline status_t Message::FindFlat( const String & fieldName, tp & obj) const {return Find##tp( fieldName, obj);} \ +template <> inline status_t Message::AddFlat( const String & fieldName, const tp & obj) {return Add##tp( fieldName, obj);} \ +template <> inline status_t Message::PrependFlat(const String & fieldName, const tp & obj) {return Prepend##tp(fieldName, obj);} \ +template <> inline status_t Message::ReplaceFlat(bool okayToAdd, const String & fieldName, const tp & obj) {return Replace##tp(okayToAdd, fieldName, obj);} \ +template <> inline status_t Message::ReplaceFlat(bool okayToAdd, const String & fieldName, uint32 index, const tp & obj) {return Replace##tp( okayToAdd, fieldName, index, obj);} + +DECLARE_MUSCLE_FLAT_SPECIALIZERS(String) +DECLARE_MUSCLE_FLAT_SPECIALIZERS(Point) +DECLARE_MUSCLE_FLAT_SPECIALIZERS(Rect) +DECLARE_MUSCLE_FLAT_SPECIALIZERS(Message) + +/** Convenience method: Gets a Message from the message pool, populates it using the flattened Message + * bytes held by (bb), and returns it. + * @param bb a ByteBuffer object to unflatten the Message from. + * @return Reference to a Message object, or a NULL ref on failure (out of memory or unflattening error) + */ +inline MessageRef GetMessageFromPool(const ByteBuffer & bb) {return GetMessageFromPool(bb.GetBuffer(), bb.GetNumBytes());} + +/** Convenience method: Gets a Message from the message pool, and populates it by calling + * SaveToArchive(msg) on the passed in object. Templates so that the passed-in object may be of any type. + * @param pool The Message pool to get the Message object from. + * @param objectToArchive The object to call SaveToArchive() on. + * @returns a non-NULL MessageRef on success, or a NULL MessageRef on failure. + */ +template inline MessageRef GetArchiveMessageFromPool(ObjectPool & pool, const T & objectToArchive) +{ + MessageRef m = GetMessageFromPool(pool); + if ((m())&&(objectToArchive.SaveToArchive(*m()) != B_NO_ERROR)) m.Reset(); + return m; +} + +/** Convenience method: Gets a Message from the message pool, and populates it by calling + * SaveToArchive(msg) on the passed in object. Templates so that the passed-in object may be of any type. + * @param objectToArchive The object to call SaveToArchive() on. + * @returns a non-NULL MessageRef on success, or a NULL MessageRef on failure. + */ +template inline MessageRef GetArchiveMessageFromPool(const T & objectToArchive) +{ + MessageRef m = GetMessageFromPool(); + if ((m())&&(objectToArchive.SaveToArchive(*m()) != B_NO_ERROR)) m.Reset(); + return m; +} + +/** Convenience method: Given a Message that was previously created via GetArchiveMessageFromPool(), + * creates and returns an object of type T, and calls SetFromArchive() on the object so that it + * represents the state that was saved into the Message. + * @param msg The Message to extract the returned object's state-data from + * @returns a reference to the new object on success, or a NULL reference on failure. + */ +template inline Ref CreateObjectFromArchiveMessage(const Message & msg) +{ + Ref newObjRef(newnothrow T); + if (newObjRef()) + { + if (newObjRef()->SetFromArchive(msg) != B_NO_ERROR) newObjRef.Reset(); + } + else WARN_OUT_OF_MEMORY; + return newObjRef; +} + +/** As above, except this version takes a MessageRef instead of a Message. + * @param msgRef MessageRef to extract the returned object's state-data from. + * @returns a reference to the new object on success, or a NULL reference on failure (or if msgRef was a NULL reference). + */ +template inline Ref CreateObjectFromArchiveMessage(const MessageRef & msgRef) +{ + return msgRef() ? CreateObjectFromArchiveMessage(*msgRef()) : Ref(); +} + +inline MessageFieldNameIterator :: MessageFieldNameIterator(const Message & msg, uint32 type, uint32 flags) : _typeCode(type), _iter(msg._entries.GetIterator(flags)) {if (_typeCode != B_ANY_TYPE) SkipNonMatchingFieldNames();} + +// declared down here to make clang++ happy +template status_t Message :: AddArchiveMessage(const String & fieldName, const T & obj) +{ + return AddMessage(fieldName, GetArchiveMessageFromPool(obj)); +} + +// declared down here to make clang++ happy +template status_t Message :: PrependArchiveMessage(const String & fieldName, const T & obj) +{ + return PrependMessage(fieldName, GetArchiveMessageFromPool(obj)); +} + +}; // end namespace muscle + +#endif /* _MUSCLEMESSAGE_H */ + diff --git a/micromessage/MicroMessage.c b/micromessage/MicroMessage.c new file mode 100644 index 00000000..0a1d9ca5 --- /dev/null +++ b/micromessage/MicroMessage.c @@ -0,0 +1,948 @@ +#include +#include "micromessage/MicroMessage.h" + +#ifdef cplusplus +extern "C" { +#endif + +#define OLDEST_SUPPORTED_PROTOCOL_VERSION 1347235888 /* 'PM00' */ +#define CURRENT_PROTOCOL_VERSION 1347235888 /* 'PM00' */ + +static UBool _enforceFieldNameUniqueness = UTrue; +void SetFieldNameUniquenessEnforced(UBool enforce) {_enforceFieldNameUniqueness = enforce;} +UBool IsFieldNameUniquenessEnforced() {return _enforceFieldNameUniqueness;} + +/** Returns the number of bytes that are actually valid (i.e. written to, not garbage bytes) starting at (ptr) */ +static uint32 GetNumValidBytesAt(const UMessage * msg, const uint8 * ptr) +{ + const uint8 * afterLast = msg->_buffer+msg->_numValidBytes; + return ((ptr >= msg->_buffer)&&(ptr < afterLast)) ? (afterLast-ptr) : 0; +} + +/** Returns the number of bytes that are present (i.e. in the buffer) starting at (ptr). Note that the values of these bytes may or may not be well-defined at this time. */ +static uint32 GetNumBufferBytesAt(const UMessage * msg, const uint8 * ptr) +{ + const uint8 * afterLast = msg->_buffer+msg->_bufferSize; + return ((ptr >= msg->_buffer)&&(ptr < afterLast)) ? (afterLast-ptr) : 0; +} + +static inline uint32 GetNumRemainingSpareBufferBytes(const UMessage * msg) {return GetNumBufferBytesAt(msg, msg->_buffer+msg->_numValidBytes);} + +static inline void UMWriteInt32(uint8 * ptr, uint32 val) {*((uint32 *)ptr) = B_HOST_TO_LENDIAN_INT32(val);} +static inline uint32 UMReadInt32(const uint8 * ptr) {return B_LENDIAN_TO_HOST_INT32(*((uint32 *)ptr));} + +static status_t UMWriteInt32AtOffset(UMessage * msg, uint32 offset, uint32 value) +{ + uint8 * ptr = msg->_buffer+offset; + if (GetNumBufferBytesAt(msg, ptr) < sizeof(uint32)) return B_ERROR; + UMWriteInt32(ptr, value); + return B_NO_ERROR; +} + +static uint32 UMReadInt32AtOffset(const UMessage * msg, uint32 offset) +{ + uint8 * ptr = msg->_buffer+offset; + return (GetNumValidBytesAt(msg,ptr) >= sizeof(uint32)) ? UMReadInt32(ptr) : 0; +} + +static const uint32 MESSAGE_HEADER_SIZE = (3*sizeof(uint32)); // a Message with no fields is this long + +static inline status_t UMSetNumFields(UMessage * msg, uint32 numFields) {return UMWriteInt32AtOffset(msg, 2*sizeof(uint32), numFields);} +uint32 UMGetNumFields( const UMessage * msg) {return UMReadInt32AtOffset( msg, 2*sizeof(uint32));} +uint32 UMGetFlattenedSize( const UMessage * msg) {return msg->_numValidBytes;} +uint32 UMGetMaximumSize( const UMessage * msg) {return msg->_bufferSize;} +const uint8 * UMGetFlattenedBuffer(const UMessage * msg) {return msg->_buffer;} +UBool UMIsMessageReadOnly( const UMessage * msg) {return msg->_isReadOnly;} +UBool UMIsMessageValid( const UMessage * msg) {return ((msg->_buffer)&&(msg->_numValidBytes >= MESSAGE_HEADER_SIZE));} + +status_t UMInitializeToEmptyMessage(UMessage * msg, uint8 * buf, uint32 numBytesInBuf, uint32 whatCode) +{ + if (numBytesInBuf < MESSAGE_HEADER_SIZE) return B_ERROR; + + msg->_buffer = buf; + msg->_bufferSize = numBytesInBuf; + msg->_numValidBytes = MESSAGE_HEADER_SIZE; + msg->_currentAddField = NULL; + msg->_isReadOnly = UFalse; + msg->_parentMsg = NULL; + msg->_sizeField = NULL; + msg->_readFieldCache = NULL; + if ((UMWriteInt32AtOffset(msg, 0, CURRENT_PROTOCOL_VERSION) == B_NO_ERROR) && + (UMSetWhatCode(msg, whatCode) == B_NO_ERROR) && + (UMSetNumFields(msg, 0) == B_NO_ERROR)) + { + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t UMInitializeWithExistingData(UMessage * msg, const uint8 * buf, uint32 numBytesInBuf) +{ + msg->_buffer = (uint8 *) buf; + msg->_bufferSize = numBytesInBuf; + msg->_numValidBytes = numBytesInBuf; + msg->_currentAddField = NULL; + msg->_isReadOnly = UTrue; + msg->_parentMsg = NULL; + msg->_sizeField = NULL; + msg->_readFieldCache = NULL; + if ((numBytesInBuf >= MESSAGE_HEADER_SIZE)&&(UMReadInt32AtOffset(msg, 0) == CURRENT_PROTOCOL_VERSION)) + { + return B_NO_ERROR; + } + else return B_ERROR; +} + +void UMInitializeToInvalid(UMessage * msg) +{ + msg->_buffer = NULL; + msg->_bufferSize = 0; + msg->_numValidBytes = 0; + msg->_currentAddField = NULL; + msg->_isReadOnly = UTrue; + msg->_parentMsg = NULL; + msg->_sizeField = NULL; + msg->_readFieldCache = NULL; +} + +uint32 UMGetWhatCode(const UMessage * msg) {return UMReadInt32AtOffset( msg, 1*sizeof(uint32));} +status_t UMSetWhatCode( UMessage * msg, uint32 whatCode) {return UMWriteInt32AtOffset(msg, 1*sizeof(uint32), whatCode);} + +/* Per-field headers are laid out like this: */ +/* 1. Field name length (4 bytes) */ +/* 2. Field name string (flattened String) */ +/* 3. Field type code (4 bytes) */ +/* 4. Field data length (4 bytes) */ +/* 5. Field data (n bytes) */ +static inline uint32 GetFieldNameLength( uint8 * field) {return UMReadInt32(field);} +static inline const char * GetFieldName( uint8 * field) {return ((const char *)field)+sizeof(uint32);} +static inline void * GetFieldTypePointer(uint8 * field) {return (((uint8 *)GetFieldName(field))+GetFieldNameLength(field));} +static inline uint32 GetFieldType( void * ftptr) {return UMReadInt32((uint8 *)ftptr);} +static inline uint32 GetFieldDataLength( void * ftptr) {return UMReadInt32(((uint8 *)ftptr)+sizeof(uint32));} +static inline uint8 * GetFieldData( void * ftptr) {return ((uint8 *)ftptr)+sizeof(uint32)+sizeof(uint32);} +static inline void SetFieldDataLength( void * ftptr, uint32 newVal) {return UMWriteInt32(((uint8 *)ftptr)+sizeof(uint32), newVal);} + +static const uint32 MINIMUM_FIELD_HEADERS_SIZE = (3*sizeof(uint32)); // name_length, type_code, data_length (name_string and data not included!) + +static UBool IsFieldPointerValid(const UMessage * msg, uint8 * ptr) +{ + void * ftptr = (GetNumValidBytesAt(msg, ptr) >= MINIMUM_FIELD_HEADERS_SIZE) ? GetFieldTypePointer(ptr) : NULL; + if ((ftptr)&&(GetNumValidBytesAt(msg, ((uint8*)ftptr)) >= (sizeof(uint32)+sizeof(uint32)))) + { + uint8 * fData = GetFieldData(ftptr); + return (GetNumValidBytesAt(msg, fData) > 0); + } + return UFalse; +} + +static uint8 * GetNextField(UMessage * msg, uint8 * field) +{ + void * ftptr = GetFieldTypePointer(field); + uint8 * afterData = GetFieldData(ftptr)+GetFieldDataLength(ftptr); + return IsFieldPointerValid(msg, afterData) ? afterData : NULL; +} + +static void IncreaseCurrentFieldDataLength(UMessage * msg, uint32 numBytes); /* forward declaration */ + +static void IncreaseParentValidBytesBy(UMessage * msg, uint32 numBytes) +{ + /* If we are being constructed in-place inside a parent UMessage, we need to tell the parent to get larger as we get larger */ + if (msg->_parentMsg) IncreaseCurrentFieldDataLength((UMessage *)msg->_parentMsg, numBytes); + if (msg->_sizeField) UMWriteInt32(msg->_sizeField, UMReadInt32(msg->_sizeField)+numBytes); +} + +/** Note that we assume that _currentAddField is non-NULL in this method. */ +static void IncreaseCurrentFieldDataLength(UMessage * msg, uint32 numBytes) +{ + msg->_numValidBytes += numBytes; + void * ftptr = GetFieldTypePointer(msg->_currentAddField); + SetFieldDataLength(ftptr, GetFieldDataLength(ftptr)+numBytes); + IncreaseParentValidBytesBy(msg, numBytes); +} + +static uint8 * GetFieldByNameAux(const UMessage * msg, const char * fieldName, uint32 fieldNameLength, uint32 desiredTypeCode) +{ + uint8 * ptr = msg->_buffer + MESSAGE_HEADER_SIZE; + while(IsFieldPointerValid(msg, ptr)) + { + void * ftptr = GetFieldTypePointer(ptr); + if (((desiredTypeCode == B_ANY_TYPE)||(desiredTypeCode == GetFieldType(ftptr)))&&(GetFieldNameLength(ptr) == fieldNameLength)&&(strcmp(GetFieldName(ptr), fieldName) == 0)) return ptr; + else + { + ptr = GetFieldData(ftptr)+GetFieldDataLength(ftptr); + } + } + return NULL; +} + +static uint8 * GetOrAddFieldDataPointer(UMessage * msg, const char * fieldName, uint32 fieldType, uint32 numDataBytesNeeded, uint8 ** optRetExtraFieldHeader, uint32 fieldHeaderSizeBytes) +{ + if (msg->_isReadOnly) + { + printf("MicroMessage Error: Can't add data to field [%s], this MicroMessage is read-only!\n", fieldName); + return NULL; + } + + /** First see if this is more data for the field currently being added. */ + uint32 newFieldNameLength = ((uint32) strlen(fieldName))+1; + if (msg->_currentAddField) + { + void * curFieldTypePtr = GetFieldTypePointer(msg->_currentAddField); + if ((GetFieldType(curFieldTypePtr) == fieldType)&&(GetFieldNameLength(msg->_currentAddField) == newFieldNameLength)&&(strcmp(GetFieldName(msg->_currentAddField), fieldName) == 0)) + { + uint8 * fieldData = GetFieldData(curFieldTypePtr); + if (optRetExtraFieldHeader) *optRetExtraFieldHeader = fieldData; + + fieldData += GetFieldDataLength(curFieldTypePtr); + if (GetNumBufferBytesAt(msg, fieldData) >= numDataBytesNeeded) return fieldData; + else + { + printf("MicroMessage Error: Not enough space left in "UINT32_FORMAT_SPEC"-byte buffer to append "UINT32_FORMAT_SPEC" bytes of additional data to field [%s]\n", UMGetMaximumSize(msg), numDataBytesNeeded, fieldName); + return NULL; + } + } + + /** If we got here, the current field name is different from the previous one. Optionally check to make sure such a field name doesn't already exist */ + if ((_enforceFieldNameUniqueness)&&(GetFieldByNameAux(msg, fieldName, newFieldNameLength, B_ANY_TYPE) != NULL)) + { + printf("MicroMessage Error: Attempt to a second field [%s] to UMessage %p, when a field with that name already exists. This isn't supported!\n", fieldName, msg); + return NULL; + } + } + + /** If we got here, we can add the new field if there is space for it. */ + { + uint32 numRemainingBytes = GetNumRemainingSpareBufferBytes(msg); + uint32 numRequiredBytes = sizeof(uint32)+newFieldNameLength+sizeof(uint32)+sizeof(uint32)+fieldHeaderSizeBytes+numDataBytesNeeded; + if (numRemainingBytes < numRequiredBytes) + { + printf("MicroMessage Error: Not enough space left in buffer to add new field [%s] ("UINT32_FORMAT_SPEC" additional bytes required, "UINT32_FORMAT_SPEC" bytes available)\n", fieldName, numRequiredBytes, numRemainingBytes); + return NULL; + } + + { + uint32 oldValidBytes = msg->_numValidBytes; + uint8 * ptr = msg->_currentAddField = msg->_buffer+msg->_numValidBytes; + UMWriteInt32(ptr, newFieldNameLength); ptr += sizeof(uint32); + memcpy(ptr, fieldName, newFieldNameLength); ptr += newFieldNameLength; + UMWriteInt32(ptr, fieldType); ptr += sizeof(uint32); + UMWriteInt32(ptr, fieldHeaderSizeBytes); ptr += sizeof(uint32); /* field-data-bytes not included, they will be added by caller */ + if (optRetExtraFieldHeader) *optRetExtraFieldHeader = ptr; + memset(ptr, 0, fieldHeaderSizeBytes); ptr += fieldHeaderSizeBytes; + msg->_numValidBytes = ptr-msg->_buffer; + IncreaseParentValidBytesBy(msg, msg->_numValidBytes-oldValidBytes); + UMSetNumFields(msg, UMGetNumFields(msg)+1); + return ptr; /* The data bytes themselves will be added by the calling function */ + } + } +} + +status_t UMAddBools(UMessage * msg, const char * fieldName, const UBool * vals, uint32 numVals) +{ + uint8 * dataPtr = GetOrAddFieldDataPointer(msg, fieldName, B_BOOL_TYPE, numVals, NULL, 0); + if (dataPtr == NULL) return B_ERROR; + + { + uint32 i; + for (i=0; i_currentField == NULL)||(iter->_typeCode == B_ANY_TYPE)||(UMReadInt32(GetFieldTypePointer(iter->_currentField)) == iter->_typeCode)); +} + +void UMIteratorInitialize(UMessageFieldNameIterator * iter, const UMessage * msg, uint32 typeCode) +{ + iter->_message = (UMessage *) msg; + iter->_typeCode = typeCode; + if (msg->_numValidBytes > MESSAGE_HEADER_SIZE) + { + iter->_currentField = msg->_buffer+MESSAGE_HEADER_SIZE; + if (UMIteratorCurrentFieldMatches(iter) == false) UMIteratorAdvance(iter); + } + else iter->_currentField = NULL; +} + +static uint32 GetNumItemsInField(const UMessage * msg, void * ftptr) +{ + const uint8 * fdata = GetFieldData(ftptr); + uint32 numBytes = GetFieldDataLength(ftptr); + switch(GetFieldType(ftptr)) + { + case B_BOOL_TYPE: return numBytes; + case B_DOUBLE_TYPE: return numBytes/sizeof(double); + case B_FLOAT_TYPE: return numBytes/sizeof(float); + case B_INT64_TYPE: return numBytes/sizeof(int64); + case B_INT32_TYPE: return numBytes/sizeof(int32); + case B_INT16_TYPE: return numBytes/sizeof(int16); + case B_INT8_TYPE: return numBytes/sizeof(int8); + + case B_MESSAGE_TYPE: + { + /* There's no num-items field for this type (sigh) so we gotta count them one by one */ + uint32 ret = 0; + while(numBytes >= (sizeof(uint32)*2)) + { + uint32 msgSize = UMReadInt32(fdata); fdata += sizeof(uint32); + uint32 msgMagic = UMReadInt32(fdata); + if (msgMagic != CURRENT_PROTOCOL_VERSION) + { + printf("MicroMessage: GetNumItemsInField: sub-Message magic value at offset %i is incorrect ("UINT32_FORMAT_SPEC", should be "UINT32_FORMAT_SPEC"!)\n", (int)(fdata-msg->_buffer), msgMagic, CURRENT_PROTOCOL_VERSION); + break; /* paranoia -- avoid infinite loop */ + } + if (msgSize < MESSAGE_HEADER_SIZE) + { + printf("MicroMessage: GetNumItemsInField: sub-Message size "UINT32_FORMAT_SPEC" was too small!\n", msgSize); + break; /* paranoia -- avoid infinite loop */ + } + fdata += msgSize; + uint32 moveBy = sizeof(uint32)+msgSize; + numBytes = (numBytes>moveBy)?(numBytes-moveBy):0; + if (fdata > (msg->_buffer+msg->_numValidBytes)) numBytes = 0; /* paranoia */ + ret++; + } + return ret; + } + + case B_POINTER_TYPE: return numBytes/sizeof(void *); + case B_POINT_TYPE: return numBytes/sizeof(UPoint); + case B_RECT_TYPE: return numBytes/sizeof(URect); + default: return (numBytes>=sizeof(uint32))?UMReadInt32(fdata):0; + } +} + +const char * UMIteratorGetCurrentFieldName(UMessageFieldNameIterator * iter, uint32 * optRetNumItemsInField, uint32 * optRetFieldType) +{ + if (iter->_currentField == NULL) return NULL; + if ((optRetNumItemsInField)||(optRetFieldType)) + { + void * ftptr = GetFieldTypePointer(iter->_currentField); + if (optRetFieldType) *optRetFieldType = GetFieldType(ftptr); + if (optRetNumItemsInField) *optRetNumItemsInField = GetNumItemsInField(iter->_message, ftptr); + } + return GetFieldName(iter->_currentField); +} + +void UMIteratorAdvance(UMessageFieldNameIterator * iter) +{ + while(iter->_currentField) + { + void * ftptr = GetFieldTypePointer(iter->_currentField); + uint32 fieldDataLen = GetFieldDataLength(ftptr); + iter->_currentField = ftptr+(sizeof(uint32)+sizeof(uint32)+fieldDataLen); + if (iter->_currentField > (iter->_message->_buffer+iter->_message->_numValidBytes)) + { + printf("UMIteratorAdvance: Iteration left the valid data range ("UINT32_FORMAT_SPEC" > "UINT32_FORMAT_SPEC"), aborting iteration!\n", (uint32)(iter->_currentField-iter->_message->_buffer), iter->_message->_numValidBytes); + iter->_currentField = NULL; + } + else + { + uint32 bytesLeft = GetNumValidBytesAt(iter->_message, iter->_currentField); + if (bytesLeft < MINIMUM_FIELD_HEADERS_SIZE) + { + if (bytesLeft > 0) printf("UMIteratorAdvance: Iteration found too-short field-header ("UINT32_FORMAT_SPEC" < "UINT32_FORMAT_SPEC"), aborting iteration!\n", bytesLeft, MINIMUM_FIELD_HEADERS_SIZE); + iter->_currentField = NULL; + } + } + if (UMIteratorCurrentFieldMatches(iter)) return; + } +} + +static uint8 * GetFieldByName(const UMessage * msg, const char * fieldName, uint32 typeCode) +{ + uint32 fieldNameLen = ((uint32)strlen(fieldName))+1; + + /** First see if our cache already is pointing at the requested field. If so, we're golden. */ + uint8 * rfc = msg->_readFieldCache; /* lame attempt at thread-safety */ + if (rfc) + { + void * ftptr = GetFieldTypePointer(rfc); + if ((GetFieldNameLength(rfc) == fieldNameLen)&&(strcmp(fieldName, GetFieldName(rfc)) == 0)) return ((typeCode == B_ANY_TYPE)||(typeCode == GetFieldType(ftptr))) ? msg->_readFieldCache : NULL; + } + + /* If the requested field wasn't cached, find it and cache it for next time before returning it */ + rfc = GetFieldByNameAux(msg, fieldName, fieldNameLen, typeCode); + if (rfc) + { + ((UMessage *)msg)->_readFieldCache = rfc; /* for next time */ + return rfc; + } + else return NULL; +} + +uint32 UMGetNumItemsInField(const UMessage * msg, const char * fieldName, uint32 typeCode) +{ + uint8 * field = GetFieldByName(msg, fieldName, typeCode); + return field ? GetNumItemsInField(msg, GetFieldTypePointer(field)) : 0; +} + +uint32 UMGetFieldTypeCode(const UMessage * msg, const char * fieldName) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_ANY_TYPE); + return field ? GetFieldType(GetFieldTypePointer(field)) : B_ANY_TYPE; +} + +static void DoIndent(int numSpaces) +{ + int i=0; + for (i=0; i 10) ni = 10; /* truncate to avoid too much spam */ + + MakePrettyTypeCodeString(typeCode, pbuf); + DoIndent(indent); fprintf(file, "Field: Name=[%s] NumItemsInField="UINT32_FORMAT_SPEC", TypeCode=%s ("INT32_FORMAT_SPEC")\n", fieldName, numItems, pbuf, typeCode); + for (i=0; i 0) PrintUMessageToStreamAux(&subMsg, file, indent+3); + else fprintf(file, "(Error retrieving sub-message)\n"); + } + break; + + case B_STRING_TYPE: + { + const char * s = UMGetString(msg, fieldName, i); + if (s) fprintf(file, "[%s]\n", s); + else fprintf(file, "(Error retrieving string value)\n"); + } + break; + + default: + { + const void * subBuf; + uint32 subBufLen; + if (UMFindData(msg, fieldName, typeCode, i, &subBuf, &subBufLen) == B_NO_ERROR) + { + if (subBufLen > 0) + { + const uint8 * b = (const uint8 *) subBuf; + int nb = subBufLen; + int j; + + if (nb > 10) + { + fprintf(file, "(%i bytes, starting with", nb); + nb = 10; + } + else fprintf(file, "(%i bytes, equal to",nb); + + for (j=0; j= numBools)) return B_ERROR; + *retBool = b[idx]; + return B_NO_ERROR; +} + +const int8 * UMGetInt8s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt8s) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_INT8_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumInt8s) *optRetNumInt8s = GetNumItemsInField(msg, ftptr); + return (const int8 *) GetFieldData(ftptr); +} + +status_t UMFindInt8(const UMessage * msg, const char * fieldName, uint32 idx, int8 * retInt8) +{ + uint32 numInt8s; + const int8 * b = UMGetInt8s(msg, fieldName, &numInt8s); + if ((b==NULL)||(idx >= numInt8s)) return B_ERROR; + *retInt8 = b[idx]; + return B_NO_ERROR; +} + +const int16 * UMGetInt16s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt16s) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_INT16_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumInt16s) *optRetNumInt16s = GetNumItemsInField(msg, ftptr); + return (const int16 *) GetFieldData(ftptr); +} + +status_t UMFindInt16(const UMessage * msg, const char * fieldName, uint32 idx, int16 * retInt16) +{ + uint32 numInt16s; + const int16 * b = UMGetInt16s(msg, fieldName, &numInt16s); + if ((b==NULL)||(idx >= numInt16s)) return B_ERROR; + *retInt16 = b[idx]; + return B_NO_ERROR; +} + +const int32 * UMGetInt32s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt32s) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_INT32_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumInt32s) *optRetNumInt32s = GetNumItemsInField(msg, ftptr); + return (const int32 *) GetFieldData(ftptr); +} + +status_t UMFindInt32(const UMessage * msg, const char * fieldName, uint32 idx, int32 * retInt32) +{ + uint32 numInt32s; + const int32 * b = UMGetInt32s(msg, fieldName, &numInt32s); + if ((b==NULL)||(idx >= numInt32s)) return B_ERROR; + *retInt32 = b[idx]; + return B_NO_ERROR; +} + +const int64 * UMGetInt64s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt64s) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_INT64_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumInt64s) *optRetNumInt64s = GetNumItemsInField(msg, ftptr); + return (const int64 *) GetFieldData(ftptr); +} + +status_t UMFindInt64(const UMessage * msg, const char * fieldName, uint32 idx, int64 * retInt64) +{ + uint32 numInt64s; + const int64 * b = UMGetInt64s(msg, fieldName, &numInt64s); + if ((b==NULL)||(idx >= numInt64s)) return B_ERROR; + *retInt64 = b[idx]; + return B_NO_ERROR; +} + +const float * UMGetFloats(const UMessage * msg, const char * fieldName, uint32 * optRetNumFloats) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_FLOAT_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumFloats) *optRetNumFloats = GetNumItemsInField(msg, ftptr); + return (const float *) GetFieldData(ftptr); +} + +status_t UMFindFloat(const UMessage * msg, const char * fieldName, uint32 idx, float * retFloat) +{ + uint32 numFloats; + const float * b = UMGetFloats(msg, fieldName, &numFloats); + if ((b==NULL)||(idx >= numFloats)) return B_ERROR; + *retFloat = b[idx]; + return B_NO_ERROR; +} + +const double * UMGetDoubles(const UMessage * msg, const char * fieldName, uint32 * optRetNumDoubles) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_DOUBLE_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumDoubles) *optRetNumDoubles = GetNumItemsInField(msg, ftptr); + return (const double *) GetFieldData(ftptr); +} + +status_t UMFindDouble(const UMessage * msg, const char * fieldName, uint32 idx, double * retDouble) +{ + uint32 numDoubles; + const double * b = UMGetDoubles(msg, fieldName, &numDoubles); + if ((b==NULL)||(idx >= numDoubles)) return B_ERROR; + *retDouble = b[idx]; + return B_NO_ERROR; +} + +const UPoint * UMGetPoints(const UMessage * msg, const char * fieldName, uint32 * optRetNumPoints) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_POINT_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumPoints) *optRetNumPoints = GetNumItemsInField(msg, ftptr); + return (const UPoint *) GetFieldData(ftptr); +} + +status_t UMFindPoint(const UMessage * msg, const char * fieldName, uint32 idx, UPoint * retPoint) +{ + uint32 numPoints; + const UPoint * b = UMGetPoints(msg, fieldName, &numPoints); + if ((b==NULL)||(idx >= numPoints)) return B_ERROR; + *retPoint = b[idx]; + return B_NO_ERROR; +} + +const URect * UMGetRects(const UMessage * msg, const char * fieldName, uint32 * optRetNumRects) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_RECT_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (optRetNumRects) *optRetNumRects = GetNumItemsInField(msg, ftptr); + return (const URect *) GetFieldData(ftptr); +} + +status_t UMFindRect(const UMessage * msg, const char * fieldName, uint32 idx, URect * retRect) +{ + uint32 numRects; + const URect * b = UMGetRects(msg, fieldName, &numRects); + if ((b==NULL)||(idx >= numRects)) return B_ERROR; + *retRect = b[idx]; + return B_NO_ERROR; +} + +const char * UMGetString(const UMessage * msg, const char * fieldName, uint32 idx) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_STRING_TYPE); + if (field == NULL) return NULL; + + void * ftptr = GetFieldTypePointer(field); + if (idx >= GetNumItemsInField(msg, ftptr)) return NULL; + + const uint8 * afterEndOfField = GetFieldData(ftptr)+GetFieldDataLength(ftptr); + const uint8 * pointerToString = ((uint8 *)ftptr)+(4*sizeof(uint32)); /* skip past the field-type, field-size, number-of-items, and first-string-length fields */ + while(idx > 0) + { + uint32 stringSize = UMReadInt32(pointerToString-sizeof(uint32)); + if ((stringSize+sizeof(uint32)) > (afterEndOfField-pointerToString)) return NULL; /* paranoia */ + pointerToString += UMReadInt32(pointerToString-sizeof(uint32))+sizeof(uint32); /* move past the string and the next string's string-length-field */ + idx--; + } + return (const char *) pointerToString; +} + +status_t UMFindData(const UMessage * msg, const char * fieldName, uint32 dataType, uint32 idx, const void ** retDataBytes, uint32 * retNumBytes) +{ + uint8 * field = GetFieldByName(msg, fieldName, dataType); + if (field == NULL) return B_ERROR; + + void * ftptr = GetFieldTypePointer(field); + if (idx >= GetNumItemsInField(msg, ftptr)) return B_ERROR; + + const uint8 * afterEndOfField = GetFieldData(ftptr)+GetFieldDataLength(ftptr); + const uint8 * pointerToBlob = ((uint8 *)ftptr)+(4*sizeof(uint32)); /* skip past the field-type, field-size, num-items, and first-blob-length fields */ + while(idx > 0) + { + uint32 blobSize = UMReadInt32(pointerToBlob-sizeof(uint32)); /* move past the blob and the next blob's string-length-field */ + if ((blobSize+sizeof(uint32)) > (afterEndOfField-pointerToBlob)) return B_ERROR; // paranoia + pointerToBlob += blobSize+sizeof(uint32); /* move past the blob and the next blob's string-length-field */ + idx--; + } + *retDataBytes = pointerToBlob; + *retNumBytes = UMReadInt32(pointerToBlob-sizeof(uint32)); + return B_NO_ERROR; +} + +status_t UMFindMessage(const UMessage * msg, const char * fieldName, uint32 idx, UMessage * retMessage) +{ + uint8 * field = GetFieldByName(msg, fieldName, B_MESSAGE_TYPE); + if (field == NULL) return B_ERROR; + + void * ftptr = GetFieldTypePointer(field); + const uint8 * afterEndOfField = GetFieldData(ftptr)+GetFieldDataLength(ftptr); + const uint8 * pointerToMsg = ((uint8 *)ftptr)+(3*sizeof(uint32)); /* skip past the field-type, field-size, and first-msg-length fields (there is no field-size field) */ + while(idx > 0) + { + uint32 msgSize = UMReadInt32(pointerToMsg-sizeof(uint32)); + if ((msgSize < MESSAGE_HEADER_SIZE)||((msgSize+sizeof(uint32)) > (afterEndOfField-pointerToMsg))) return B_ERROR; /* paranoia */ + pointerToMsg += msgSize+sizeof(uint32); /* move past the msg and the next msg's msg-length-field */ + idx--; + } + return UMInitializeWithExistingData(retMessage, pointerToMsg, UMReadInt32(pointerToMsg-sizeof(uint32))); +} + +#ifdef cplusplus +}; +#endif diff --git a/micromessage/MicroMessage.h b/micromessage/MicroMessage.h new file mode 100644 index 00000000..b24b2ca8 --- /dev/null +++ b/micromessage/MicroMessage.h @@ -0,0 +1,632 @@ +#ifndef MicroMessage_h +#define MicroMessage_h + +#include "support/MuscleSupport.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup micromessage The MicroMessage C function API + * These functions are all defined in MicroMessage(.c,.h), and are stand-alone + * C functions that provide a way for C programs to use MUSCLE Messages. + * This is a very bare-bones implentation that does not do any dynamic memory + * allocation, and supports only linear addition of data to a Message. + * It is appropriate for severely constrained environments where + * even the MiniMessage API is too heavyweight. + * @{ + */ + +/* My own little boolean type, since C doesn't come with one built in. */ +typedef char UBool; +enum {UFalse = 0, UTrue}; /* and boolean values to go in it */ + +/* This file contains a C API for a "super-minimalist" implementation of the MUSCLE */ +/* Message dictionary object. This implementation sacrifices of flexibility */ +/* in exchange for a super-lightweight implementation that does no dynamic memory */ +/* allocation at all. */ + +/** Definition of our Point class -- two floats */ +typedef struct _UPoint { + float x; /**< horizontal axis co-ordinate */ + float y; /**< vertical axis co-ordinate */ +} UPoint; + +/** Definition of our Rect class -- four floats */ +typedef struct _URect { + float left; /**< left edge of the rectangle */ + float top; /**< top edge of the rectangle */ + float right; /**< right edge of the rectangle */ + float bottom; /**< bottom edge of the rectangle */ +} URect; + +/** Definition of our opaque handle to a UMessage object. + * Note that all fields in this struct are private and subject to change -- + * do not access them directly, call the functions declared below instead! + */ +typedef struct _UMessage { + uint8 * _buffer; + uint32 _bufferSize; + uint32 _numValidBytes; + uint8 * _currentAddField; + UBool _isReadOnly; + void * _parentMsg; /* used during inline-child-UMessage construction, to notify parent that child's field is larger now */ + uint8 * _sizeField; /* Pointer to our size-field in the parent UMessage, when we are an inline-child-UMessage being assembled. */ + uint8 * _readFieldCache; /* a one-item LRU cache so we don't have to scan through all the fields all the time */ +} UMessage; + +typedef struct _UMessageFieldNameIterator { + UMessage * _message; + uint8 * _currentField; + uint32 _typeCode; +} UMessageFieldNameIterator; + +/** Initializes the state of the specified UMessageFieldNameIterato to point at the specified UMessage. + * When this function returns, the iterator will be pointing to the first matching field in the UMessage (if there are any). + * @param iter The iterator object to initialize. + * @param msg The UMessage object the iterator is to examine. (This object must remain valid while the iterator is in use) + * @param typeCode Type-code of the fields the iteration should include, or B_ANY_TYPE if all types of field are of interest. + */ +void UMIteratorInitialize(UMessageFieldNameIterator * iter, const UMessage * msg, uint32 typeCode); + +/** Returns the name of the message-field that the given iterator is currently pointing at, and + * optionally some other information about the field as well. + * @param iter The iterator object to query + * @param optRetNumItemsInField If non-NULL, the number of values stored in the current field will be returned here. + * @param optRetFieldType If non-NULL, the type-code of the current field will be returned here. + * @returns A pointer to the C-string name of the current field, or NULL if the iterator isn't currently pointing at a valid field + * (e.g. because the Message has no fields in it, or because the iteration is complete) + */ +const char * UMIteratorGetCurrentFieldName(UMessageFieldNameIterator * iter, uint32 * optRetNumItemsInField, uint32 * optRetFieldType); + +/** Advances the iterator to the next field in its Message, if there are any more. */ +void UMIteratorAdvance(UMessageFieldNameIterator * iter); + +/** + * Initializes a UMessage object and associated it with the specified empty byte buffer. + * This function will write the initial header data into (buf), and keep a pointer to + * (buf) for use in future calls. + * This function should be called on any UMessage object before using it to add new message data. + * @param msg Pointer to the UMessage object to initialize. + * @param buf Pointer to the byte buffer the UMessage object should use for its + * flattened-data storage. This buffer must remain valid for as long + * as the UMessage object is in use. + * @param numBytesInBuf Number of usable bytes at (buf). Must be at least 12. + * @param whatCode The 'what code' that this UMessage should hold. + * @returns B_NO_ERROR on success, or B_ERROR on failure (e.g. buffer is too small) + */ +status_t UMInitializeToEmptyMessage(UMessage * msg, uint8 * buf, uint32 numBytesInBuf, uint32 whatCode); + +/** + * Initializes a UMessage object and associates it with the specified byte buffer that already + * contains flattened message data. The UMessage will be flagged as being read-only. + * This function should be called on any UMessage object before using it to read received message data. + * @param msg Pointer to the UMessage object to initialize. + * @param buf Pointer to the read-only byte buffer the UMessage object should examine + * to read existing flattened-data. This buffer must remain valid for as long + * as the UMessage object is in use. + * @param numBytesInBuf Number of usable bytes at (buf). Must be at least 12. + * @returns B_NO_ERROR on success, or B_ERROR on failure (e.g. buffer is too small, or contains invalid data) + */ +status_t UMInitializeWithExistingData(UMessage * msg, const uint8 * buf, uint32 numBytesInBuf); + +/** + * Initializes the UMessage to a well-defined but invalid state. The UMessage will be read-only + * and contain no data. + */ +void UMInitializeToInvalid(UMessage * msg); + +/** + * @param msg The UMessage to query. + * @returns UTrue iff (msg) is flagged as being read-only (i.e. we are reading a UMessage that was + * received from somewhere else) or UFalse if (msg) is read/write (i.e. we are constructing + * it from scratch) + */ +UBool UMIsMessageReadOnly(const UMessage * msg); + + +/** + * @param msg The UMessage to query. + * @returns UTrue iff (msg) is a valid UMessage, or UFalse if it is not. + */ +UBool UMIsMessageValid(const UMessage * msg); + +/** + * Returns the number of data-fields in (msg). + * @param msg The UMessage to query. + */ +uint32 UMGetNumFields(const UMessage * msg); + +/** + * Returns the number of data-items within a data-field in (msg). + * @param msg The UMessage to inquire into + * @param fieldName the field-name to inquire about + * @param typeCode If specified other than B_ANY_TYPE, than only items in a field that match this type will be counted. + */ +uint32 UMGetNumItemsInField(const UMessage * msg, const char * fieldName, uint32 typeCode); + +/** + * Returns the type-code of the specified field, or B_ANY_TYPE if the field is not present in the UMessage. + * @param msg The UMessage to inquire into + * @param fieldName the field-name to inquire about + */ +uint32 UMGetFieldTypeCode(const UMessage * msg, const char * fieldName); + +/** + * Returns the current size of (msg)'s flattened-data-buffer, in bytes. + * Note that this value includes only valid bytes, not "spare" bytes that are in the buffer but aren't currently being used. + * @param msg The UMessage to query. + */ +uint32 UMGetFlattenedSize(const UMessage * msg); + +/** + * Returns the current the maximum number of bytes (msg)'s buffer can contain. + * Note that this value includes all bytes in the buffer, whether they have had data written to them or not. + * @param msg The UMessage to query. + */ +uint32 UMGetMaximumSize(const UMessage * msg); + +/** + * Returns a pointer to the buffer that (msg) is using. This buffer contains UMGetFlattenedSize(msg) valid bytes of data. + * @param msg The UMessage to query. + */ +const uint8 * UMGetFlattenedBuffer(const UMessage * msg); + +/** Prints the contents of this MMessage's buffer to stdout. Useful for debugging. + * @param msg The MicroMessage to print the contents of to stdout. + * @param optFile If non-NULL, the file to print the output to. If NULL, the output will go to stdout. + */ +void UMPrintToStream(const UMessage * msg, FILE * optFile); + +/** Returns the 'what code' from the data buffer associated with (msg) + * @param msg The UMessage associated with the data buffer. + */ +uint32 UMGetWhatCode(const UMessage * msg); + +/** Sets the 'what code' in the data buffer associated with (msg) + * @param msg The UMessage associated with the data buffer. + * @param whatCode the new what-code to set in the data buffer. + * @returns B_NO_ERROR on success, or B_ERROR on failure (e.g. msg's buffer is too small) + */ +status_t UMSetWhatCode(UMessage * msg, uint32 whatCode); + +/** Adds an array of one or more boolean values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of boolean values to be copied into (msg). + * @param numVals The number of boolean values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddBools(UMessage * msg, const char * fieldName, const UBool * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single boolean value to the UMessage. See UMAddBools() for details. */ +static inline status_t UMAddBool(UMessage * msg, const char * fieldName, UBool val) {return UMAddBools(msg, fieldName, &val, 1);} + +/** Adds an array of one or more int8 values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of int8 values to be copied into (msg). + * @param numVals The number of int8 values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddInt8s(UMessage * msg, const char * fieldName, const int8 * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single int8 value to the UMessage. See UMAddInt8s() for details. */ +static inline status_t UMAddInt8(UMessage * msg, const char * fieldName, int8 val) {return UMAddInt8s(msg, fieldName, &val, 1);} + +/** Adds an array of one or more int16 values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of int16 values to be copied into (msg). + * @param numVals The number of int16 values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddInt16s(UMessage * msg, const char * fieldName, const int16 * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single int16 value to the UMessage. See UMAddInt16s() for details. */ +static inline status_t UMAddInt16(UMessage * msg, const char * fieldName, int16 val) {return UMAddInt16s(msg, fieldName, &val, 1);} + +/** Adds an array of one or more int32 values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of int32 values to be copied into (msg). + * @param numVals The number of int32 values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddInt32s(UMessage * msg, const char * fieldName, const int32 * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single int32 value to the UMessage. See UMAddInt32s() for details. */ +static inline status_t UMAddInt32(UMessage * msg, const char * fieldName, int32 val) {return UMAddInt32s(msg, fieldName, &val, 1);} + +/** Adds an array of one or more int64 values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of int64 values to be copied into (msg). + * @param numVals The number of int64 values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddInt64s(UMessage * msg, const char * fieldName, const int64 * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single int64 value to the UMessage. See UMAddInt64s() for details. */ +static inline status_t UMAddInt64(UMessage * msg, const char * fieldName, int64 val) {return UMAddInt64s(msg, fieldName, &val, 1);} + +/** Adds an array of one or more floating point values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of floating point values to be copied into (msg). + * @param numVals The number of floating point values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddFloats(UMessage * msg, const char * fieldName, const float * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single floating point value to the UMessage. See UMAddFloats() for details. */ +static inline status_t UMAddFloat(UMessage * msg, const char * fieldName, float val) {return UMAddFloats(msg, fieldName, &val, 1);} + +/** Adds an array of one or more double-precision floating point values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param vals Pointer to an array of double-precision floating point values to be copied into (msg). + * @param numVals The number of double-precision floating point values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddDoubles(UMessage * msg, const char * fieldName, const double * vals, uint32 numVals); + +/** Convenience wrapper method for adding a single double-precision floating point value to the UMessage. See UMAddDoubles() for details. */ +static inline status_t UMAddDouble(UMessage * msg, const char * fieldName, double val) {return UMAddDoubles(msg, fieldName, &val, 1);} + +/** Adds an array of one or more child sub-UMessages to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param messageArray Pointer to an array of UMessage values to be copied into (msg). + * @param numVals The number of child sub-UMessages pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + * @note This method of adding child-UMessages requires copying all of the child-UMessages' data over from their + * buffers into the (msg)'s buffer. As such, it may be inefficient, particularly if the child-UMessages are + * large. For a more efficient approach to message-composition, see the UMInlineAddMessage() function. + */ +status_t UMAddMessages(UMessage * msg, const char * fieldName, const UMessage * messageArray, uint32 numVals); + +/** Convenience wrapper method for adding a single UMessage value to the UMessage. See UMAddUMessages() for details. */ +static inline status_t UMAddMessage(UMessage * msg, const char * fieldName, UMessage message) {return UMAddMessages(msg, fieldName, &message, 1);} + +/** Adds an array of one or more UPoint values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param pointArray Pointer to an array of UPoint values to be copied into (msg). + * @param numVals The number of UPoint values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddPoints(UMessage * msg, const char * fieldName, const UPoint * pointArray, uint32 numVals); + +/** Convenience wrapper method for adding a single UPoint value to the UMessage. See UMAddUPoint() for details. */ +static inline status_t UMAddPoint(UMessage * msg, const char * fieldName, UPoint point) {return UMAddPoints(msg, fieldName, &point, 1);} + +/** Adds an array of one or more URect values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param rectArray Pointer to an array of URect values to be copied into (msg). + * @param numVals The number of URect values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddRects(UMessage * msg, const char * fieldName, const URect * rectArray, uint32 numVals); + +/** Convenience wrapper method for adding a single URect value to the UMessage. See UMAddRects() for details. */ +static inline status_t UMAddRect(UMessage * msg, const char * fieldName, URect rect) {return UMAddRects(msg, fieldName, &rect, 1);} + +/** Adds an array of one or more string values to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the data to. + * @param stringArray Pointer to an array of string values to be copied into (msg). + * @param numStrings The number of string values pointed to by (vals). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddStrings(UMessage * msg, const char * fieldName, const char ** stringArray, uint32 numStrings); + +/** Convenience wrapper method for adding a single string value to the UMessage. See UMAddStrings() for details. */ +static inline status_t UMAddString(UMessage * msg, const char * fieldName, const char * string) {return UMAddStrings(msg, fieldName, &string, 1);} + +/** Adds a a "blob" of raw binary data to the UMessage. + * @param msg The UMessage to add data to. + * @param fieldName The field name to add the raw data to. + * @param dataType The type-code to associate with the raw data. (B_RAW_TYPE is often used here, or perhaps some other user-defined type code) + * @param dataBytes Pointer to the bytes of raw data to add + * @param numBytes Number of bytes pointed to by (dataBytes) + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of space, or field-name semantics would be violated?) + */ +status_t UMAddData(UMessage * msg, const char * fieldName, uint32 dataType, const void * dataBytes, uint32 numBytes); + +/** This function can be used to create a sub-Message directly within its parent UMessage. + * This can be more efficient than the usual UMAddMessage()/UMAddMessages() route, as it avoids + * having to make a copy of the child UMessage after the child UMessage is complete. + * @param parentMsg The UMessage that the new child UMessage will be a child of. + * @param fieldName The field name that the child UMessage should appear udner, inside the parent. + * @param whatCode The what-code to assign to the child UMessage. + * @returns a UMessage object whose data-buffer resides inside the data-buffer of the parent UMessage. + * Be sure to make any additions to the child UMessage before making any further additions to the + * parent UMessage, and once you have made a related addition to the parent UMessage, do not make + * any further additions to the child UMessage. Breaking these rules will result in data corruption. + */ +UMessage UMInlineAddMessage(UMessage * parentMsg, const char * fieldName, uint32 whatCode); + +/** Returns a pointer to the array of boolean values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumBools If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const UBool * UMGetBools(const UMessage * msg, const char * fieldName, uint32 * optRetNumBools); + +/** Queries the UMessage for a particular boolean value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the boolean item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retBool on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindBool(const UMessage * msg, const char * fieldName, uint32 idx, UBool * retBool); + +/** Returns a pointer to the requested boolean value, or a default value (UFalse) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the boolean to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline UBool UMGetBool(const UMessage * msg, const char * fieldName, uint32 idx) {UBool r; return (UMFindBool(msg, fieldName, idx, &r)==B_NO_ERROR)?r:((UBool)UFalse);} + +/** Returns a pointer to the array of int8 values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumInt8s If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const int8 * UMGetInt8s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt8s); + +/** Queries the UMessage for a particular int8 value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the int8 item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retInt8 on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindInt8(const UMessage * msg, const char * fieldName, uint32 idx, int8 * retInt8); + +/** Returns a pointer to the requested int8 value, or a default value (0) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the int8 to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline int8 UMGetInt8(const UMessage * msg, const char * fieldName, uint32 idx) {int8 r; return (UMFindInt8(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0;} + +/** Returns a pointer to the array of int16 values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumInt16s If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const int16 * UMGetInt16s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt16s); + +/** Queries the UMessage for a particular int16 value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the int16 item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retInt16 on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindInt16(const UMessage * msg, const char * fieldName, uint32 idx, int16 * retInt16); + +/** Returns a pointer to the requested int16 value, or a default value (0) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the int16 to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline int16 UMGetInt16(const UMessage * msg, const char * fieldName, uint32 idx) {int16 r; return (UMFindInt16(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0;} + +/** Returns a pointer to the array of int32 values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumInt32s If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const int32 * UMGetInt32s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt32s); + +/** Queries the UMessage for a particular int32 value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the int32 item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retInt32 on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindInt32(const UMessage * msg, const char * fieldName, uint32 idx, int32 * retInt32); + +/** Returns a pointer to the requested int32 value, or a default value (0) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the int32 to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline int32 UMGetInt32(const UMessage * msg, const char * fieldName, uint32 idx) {int32 r; return (UMFindInt32(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0;} + +/** Returns a pointer to the array of int64 values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumInt64s If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const int64 * UMGetInt64s(const UMessage * msg, const char * fieldName, uint32 * optRetNumInt64s); + +/** Queries the UMessage for a particular int64 value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the int64 item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retInt64 on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindInt64(const UMessage * msg, const char * fieldName, uint32 idx, int64 * retInt64); + +/** Returns a pointer to the requested int64 value, or a default value (0) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the int64 to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline int64 UMGetInt64(const UMessage * msg, const char * fieldName, uint32 idx) {int64 r; return (UMFindInt64(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0;} + +/** Returns a pointer to the array of floating point values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumFloats If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const float * UMGetFloats(const UMessage * msg, const char * fieldName, uint32 * optRetNumFloats); + +/** Queries the UMessage for a particular floating point value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the floating point item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retFloat on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindFloat(const UMessage * msg, const char * fieldName, uint32 idx, float * retFloat); + +/** Returns a pointer to the requested floating point value, or a default value (0.0f) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the floating point value to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline float UMGetFloat(const UMessage * msg, const char * fieldName, uint32 idx) {float r; return (UMFindFloat(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0.0f;} + +/** Returns a pointer to the array of double-precision floating point values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumDoubles If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const double * UMGetDoubles(const UMessage * msg, const char * fieldName, uint32 * optRetNumDoubles); + +/** Queries the UMessage for a particular double-precision floating point value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the double-precision floating point item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retDouble on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindDouble(const UMessage * msg, const char * fieldName, uint32 idx, double * retDouble); + +/** Returns a pointer to the requested double-precision floating point value, or a default value (0.0) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the double-precision floating point to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline double UMGetDouble(const UMessage * msg, const char * fieldName, uint32 idx) {double r; return (UMFindDouble(msg, fieldName, idx, &r)==B_NO_ERROR)?r:0.0f;} + +/** Returns a pointer to the array of UPoint values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumPoints If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const UPoint * UMGetPoints(const UMessage * msg, const char * fieldName, uint32 * optRetNumPoints); + +/** Queries the UMessage for a particular UPoint value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the UPoint item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retPoint on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindPoint(const UMessage * msg, const char * fieldName, uint32 idx, UPoint * retPoint); + +/** Returns a pointer to the requested UPoint value, or a default value (0.0f,0.0f) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the UPoint to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline UPoint UMGetPoint(const UMessage * msg, const char * fieldName, uint32 idx) {UPoint r; if (UMFindPoint(msg, fieldName, idx, &r)==B_NO_ERROR) return r; else {UPoint x = {0.0f,0.0f}; return x;}} + +/** Returns a pointer to the array of URect values stored under the given field name, or NULL. + * @param msg The UMessage to retrieve the array from. + * @param fieldName The field name of the desired data. + * @param optRetNumRects If non-NULL, the length of the returned array will be written here on success. + * @returns a Pointer to the requested array, or NULL on failure (i.e. field not present, or is of a different type) + */ +const URect * UMGetRects(const UMessage * msg, const char * fieldName, uint32 * optRetNumRects); + +/** Queries the UMessage for a particular URect value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the URect item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retRect on success, the requested value is written into this location + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindRect(const UMessage * msg, const char * fieldName, uint32 idx, URect * retRect); + +/** Returns a pointer to the requested URect value, or a default value (0.0f,0.0f,0.0f,0.0f) on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the URect to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +static inline URect UMGetRect(const UMessage * msg, const char * fieldName, uint32 idx) {URect r; if (UMFindRect(msg, fieldName, idx, &r)==B_NO_ERROR) return r; else {URect x = {0.0f,0.0f,0.0f,0.0f}; return x;}} + +/** Returns a pointer to the requested string value, or NULL on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the string to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + */ +const char * UMGetString(const UMessage * msg, const char * fieldName, uint32 idx); + +/** Queries the UMessage for a particular string value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the string item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retSTring on success, a pointer to the requested string is written to this location. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +static inline status_t UMFindString(const UMessage * msg, const char * fieldName, uint32 idx, const char ** retStringPointer) {*retStringPointer = UMGetString(msg, fieldName, idx); return (*retStringPointer) ? B_NO_ERROR : B_ERROR;} + +/** Queries the UMessage for a particular raw-data-blob. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param dataType The type-code to require. If B_ANY_TYPE is passed, then the field's type code will be ignored; otherwise, this call will only + * succeed if the field's type code is equal to this. + * @param idx The index of the raw-data-blob to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retDataBytes on success, a pointer to the requested data bytes is written to this location. + * @param retNumBytes on the number of data bytes pointed to by (retDataBytes) is written to this location. + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindData(const UMessage * msg, const char * fieldName, uint32 dataType, uint32 idx, const void ** retDataBytes, uint32 * retNumBytes); + +/** Queries the UMessage for a particular boolean value. + * @param msg The UMessage to query. + * @param fieldName The field to name to look inside + * @param idx The index of the boolean item to look for (e.g. 0 is the first in the array, 1 is the second, and so on) + * @param retMessage on success, the requested UMessage value is written to this location. (Note that this is a lightweight operation, because + * the returned UMessage object merely points to data within (msg)'s own data buffer; no actual message-data is copied) + * @returns B_NO_ERROR on success, or B_ERROR on failure (field name not found, was of the wrong type, or the array was shorter than (idx+1) items) + */ +status_t UMFindMessage(const UMessage * msg, const char * fieldName, uint32 idx, UMessage * retMessage); + +/** Returns a pointer to the requested UMessage value, or an invalid UMessage value on failure. + * @param msg The UMessage to query. + * @param fieldName The field name to look inside. + * @param idx The index of the UMessage to look for (e.g. 0 is the first in the field's array, 1 is the second, and so on) + * @note You can tell if the returned UMessage is invalid by calling UMGetFlattenedSize() on it -- if the result is zero, it's an invalid UMessage. + */ +static inline UMessage UMGetMessage(const UMessage * msg, const char * fieldName, uint32 idx) {UMessage r; if (UMFindMessage(msg, fieldName, idx, &r) == B_NO_ERROR) return r; else {UMessage x; memset(&x,0,sizeof(x)); return x;}} + +/** By default, the MicroMessage API will check (when adding a new field to a Message) to make sure that no other fields with the + * same name already exist in the message. It does this check because Message field names are required to be unique within the + * Message they are directly a part of, and other Message implementations do not support a Message that contains multiple different + * fields with the same name. However, this check can be inefficient in Messages with many fields, as doing this check is an + * O(N) operation, so you can call this method to disable the check. Note that you are still responsible for making sure that + * no duplicate fields exist, this only disables the check to verify that. + * @param enforce New value for the global enforce-field-name-uniqueness flag. Default value of this flag is true. + */ +void SetFieldNameUniquenessEnforced(UBool enforce); + +/** Returns true iff field-name-uniqueness is being enforced. Default value of this flag is true. */ +UBool IsFieldNameUniquenessEnforced(); + +/** @} */ // end of micromessage doxygen group + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/micromessage/MicroMessageGateway.c b/micromessage/MicroMessageGateway.c new file mode 100644 index 00000000..74d0acac --- /dev/null +++ b/micromessage/MicroMessageGateway.c @@ -0,0 +1,167 @@ +#include // for memmove() +#include "micromessage/MicroMessageGateway.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const uint32 _MUSCLE_MESSAGE_ENCODING_DEFAULT = 1164862256; /* 'Enc0' -- vanilla encoding */ +static const uint32 GATEWAY_HEADER_SIZE = 2*sizeof(uint32); +static const uint32 MESSAGE_HEADER_SIZE = 3*sizeof(uint32); + +void UGGatewayInitialize(UMessageGateway * gw, uint8 * inputBuffer, uint32 numInputBufferBytes, uint8 * outputBuffer, uint32 numOutputBufferBytes) +{ + gw->_inputBuffer = inputBuffer; + gw->_inputBufferSize = numInputBufferBytes; + gw->_numValidInputBytes = 0; + gw->_numInputBytesToRead = GATEWAY_HEADER_SIZE; + gw->_outputBuffer = outputBuffer; + gw->_outputBufferSize = numOutputBufferBytes; + gw->_firstValidOutputByte = gw->_outputBuffer; + gw->_numValidOutputBytes = 0; + gw->_preparingOutgoingMessage = UFalse; +} + +static uint32 UGGetAvailableBytesCount(const UMessageGateway * gw) +{ + return (gw->_outputBuffer+gw->_outputBufferSize)-(gw->_firstValidOutputByte+gw->_numValidOutputBytes); +} + +UMessage UGGetOutgoingMessage(UMessageGateway * gw, uint32 whatCode) +{ + UMessage ret; + if (gw->_preparingOutgoingMessage) + { + printf("UGGetOutgoingMessage: Error, can't return a second UGGetOutgoingMessage() when a first one is still in use! Did you forget to call UMOutgoingMessagePrepared() on the previous UMessage?\n"); + (void) UMInitializeToInvalid(&ret); + return ret; + } + + /* First, see if it's worth moving the outgoing buffer data to the front, in order to gain space */ + uint32 bytesAvail = UGGetAvailableBytesCount(gw); + if (bytesAvail < (gw->_outputBufferSize/4)) + { + /** Move the already-buffered outgoing-data up to the top of the buffer, to free up more space for our new UMessage */ + memmove(gw->_outputBuffer, gw->_firstValidOutputByte, gw->_numValidOutputBytes); + gw->_firstValidOutputByte = gw->_outputBuffer; + bytesAvail = UGGetAvailableBytesCount(gw); + } + + uint8 * nextAvailByte = gw->_firstValidOutputByte+gw->_numValidOutputBytes; + if (bytesAvail >= (GATEWAY_HEADER_SIZE+MESSAGE_HEADER_SIZE)) + { + (void) UMInitializeToEmptyMessage(&ret, nextAvailByte+GATEWAY_HEADER_SIZE, bytesAvail-GATEWAY_HEADER_SIZE, whatCode); + gw->_preparingOutgoingMessage = UTrue; + } + else (void) UMInitializeToInvalid(&ret); + + return ret; +} + +static inline void UMWriteInt32(uint8 * ptr, uint32 val) {*((uint32 *)ptr) = B_HOST_TO_LENDIAN_INT32(val);} +static inline uint32 UMReadInt32(const uint8 * ptr) {return B_LENDIAN_TO_HOST_INT32(*((uint32 *)ptr));} + +void UGOutgoingMessagePrepared(UMessageGateway * gw, const UMessage * msg) +{ + if (gw->_preparingOutgoingMessage) + { + uint8 * nextAvailByte = gw->_firstValidOutputByte+gw->_numValidOutputBytes; + uint8 * expected = msg->_buffer-(2*sizeof(uint32)); + if (nextAvailByte == expected) + { + uint32 msgSize = UMGetFlattenedSize(msg); + + UMWriteInt32(nextAvailByte, msgSize); + nextAvailByte += sizeof(uint32); + + UMWriteInt32(nextAvailByte, _MUSCLE_MESSAGE_ENCODING_DEFAULT); +#ifdef DISABLED_TO_KEEP_CLANG_STATIC_ANALYZER_HAPPY + nextAvailByte += sizeof(uint32); +#endif + + /* The UMessage data is already in the right place, as the UMessage wrote it there directly */ + gw->_numValidOutputBytes += (GATEWAY_HEADER_SIZE+msgSize); + gw->_preparingOutgoingMessage = UFalse; + } + else printf("UGOutgoingMessagePrepared(): The UMessage passed in is not the expected one! %p vs %p\n", nextAvailByte, expected); + } + else printf("UGOutgoingMessagePrepared(): Error, called when there was not any UMessage being prepared!?\n"); +} + +void UGOutgoingMessageCancelled(UMessageGateway * gw, const UMessage * msg) +{ + gw->_preparingOutgoingMessage = UFalse; + (void) msg; /* avoid compiler warning */ +} + +UBool UGHasBytesToOutput(const UMessageGateway * gw) +{ + return (gw->_numValidOutputBytes > 0); +} + +int32 UGDoOutput(UMessageGateway * gw, uint32 maxBytes, UGSendFunc sendFunc, void * arg) +{ + int32 totalSent = 0; + while((gw->_numValidOutputBytes > 0)&&((uint32)totalSent < maxBytes)) + { + int32 bytesToSend = gw->_numValidOutputBytes; + if (bytesToSend > maxBytes) bytesToSend = maxBytes; + + int32 bytesSent = sendFunc(gw->_firstValidOutputByte, bytesToSend, arg); + if (bytesSent < 0) return -1; /* error! */ + else + { + totalSent += bytesSent; + gw->_numValidOutputBytes -= bytesSent; + gw->_firstValidOutputByte = (gw->_numValidOutputBytes == 0) ? gw->_outputBuffer : (gw->_firstValidOutputByte+bytesSent); + } + if (bytesSent < bytesToSend) break; /* short write indicates that the output buffer is full for now */ + } + return totalSent; +} + +int32 UGDoInput(UMessageGateway * gw, uint32 maxBytes, UGReceiveFunc recvFunc, void * arg, UMessage * optRetMsg) +{ + if (optRetMsg) UMInitializeToInvalid(optRetMsg); + + int32 totalRecvd = 0; + while(true) + { + int32 bytesReceived; + uint32 bytesToRecv = (gw->_numInputBytesToRead-gw->_numValidInputBytes); + if (bytesToRecv > maxBytes) bytesToRecv = maxBytes; + + bytesReceived = recvFunc(gw->_inputBuffer+gw->_numValidInputBytes, bytesToRecv, arg); + if (bytesReceived < 0) return -1; /* error */ + else + { + totalRecvd += bytesReceived; + gw->_numValidInputBytes += bytesReceived; + if (gw->_numValidInputBytes == gw->_numInputBytesToRead) + { + if (gw->_numValidInputBytes == GATEWAY_HEADER_SIZE) /* guaranteed never to be a real UMessage, since the gateway header size is less than the legal minimum UMessage size */ + { + /* We have our fixed-size headers, now prepare to receive the actual Message body! */ + uint32 bodySize = UMReadInt32(gw->_inputBuffer); + uint32 magic = UMReadInt32(gw->_inputBuffer+sizeof(uint32)); + if ((bodySize < MESSAGE_HEADER_SIZE)||(bodySize > gw->_inputBufferSize)||(magic != _MUSCLE_MESSAGE_ENCODING_DEFAULT)) return -1; + gw->_numValidInputBytes = 0; + gw->_numInputBytesToRead = bodySize; + } + else + { + if (optRetMsg) (void) UMInitializeWithExistingData(optRetMsg, gw->_inputBuffer, gw->_numValidInputBytes); + gw->_numValidInputBytes = 0; + gw->_numInputBytesToRead = GATEWAY_HEADER_SIZE; /* get ready for the next header */ + break; /* Otherwise the caller would never see his UMessage */ + } + } + } + if (bytesReceived < bytesToRecv) break; /* short read indicates that the input buffer is empty for now */ + } + return totalRecvd; +} + +#ifdef __cplusplus +}; +#endif diff --git a/micromessage/MicroMessageGateway.h b/micromessage/MicroMessageGateway.h new file mode 100644 index 00000000..c2ad8f77 --- /dev/null +++ b/micromessage/MicroMessageGateway.h @@ -0,0 +1,119 @@ +#ifndef MicroMessageGateway_h +#define MicroMessageGateway_h + +#include "micromessage/MicroMessage.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup micromessagegateway The MicroMessageGateway C function API + * These functions are all defined in MicroMessageGateway(.c,.h), and are stand-alone + * C functions that provide functionality similar to that of the C++ + * MessageIOGateway class. + * @{ + */ + +typedef struct _UMessageGateway { + uint8 * _inputBuffer; + uint32 _inputBufferSize; + uint32 _numValidInputBytes; + uint32 _numInputBytesToRead; + uint8 * _outputBuffer; + uint32 _outputBufferSize; + uint8 * _firstValidOutputByte; + uint32 _numValidOutputBytes; + UBool _preparingOutgoingMessage; +} UMessageGateway; + +/** Initializes the specified UMessageGateway struct to point to the specified memory buffers. + * @param gateway the UMessageGateway to initialize. + * @param inputBuffer An array of bytes to use for receiving incoming UMessage data. This buffer should be at least 8 bytes larger than the + * largest UMessage you expect to receive -- if a UMessage is received that is too large, the stream will be broken. + * This array needs to remain valid and accessible for the lifetime of the UMessageGateway. + * @param numInputBufferBytes The number of bytes pointed to by (inputBuffer). + * @param outputBuffer An array of bytes to use for storing outgoing UMessage data. This buffer should be at least 8 bytes larger than the + * largest UMessage you expect to send, and larger still if you expect to queue up multiple outgoing UMessages at once. + * The maximum size of outgoing UMessages will be limited to the amount of contiguous space available in this buffer. + * This array needs to remain valid and accessible for the lifetime of the UMessageGateway. + * @param numOutputBufferBytes The number of bytes pointed to by (outputBuffer). + */ +void UGGatewayInitialize(UMessageGateway * gateway, uint8 * inputBuffer, uint32 numInputBufferBytes, uint8 * outputBuffer, uint32 numOutputBufferBytes); + +/** Typedef for a callback function that knows how to read data from a buffer and send it + * out to (a file, the network, a serial line, wherever). + * @param buf The buffer to read bytes from. + * @param numBytes The number of bytes available for reading at (buf) + * @param arg This is a user-specified value; it will be the same as the value passed in to UMDoOutput(). + * @returns The number of bytes actually read from (buf), or a negative value if there was a critical error (e.g. disconnected socket). + */ +typedef int32 (*UGSendFunc)(const uint8 * buf, uint32 numBytes, void * arg); + +/** Typedef for a callback function that knows how to read data from + * (a file, the network, a serial line, wherever) and write it into a supplied buffer. + * @param buf The buffer to write bytes to. + * @param numBytes The number of bytes available for writing at (buf) + * @param arg This is a user-specified value; it will be the same as the value passed in to UMDoInput(). + * @returns The number of bytes actually written into (buf), or a negative value if there was a critical error (e.g. disconnected socket). + */ +typedef int32 (*UGReceiveFunc)(uint8 * buf, uint32 numBytes, void * arg); + +/** When you want to send a UMessage out to the network via your UMessageGatweay, you can request a UMessage to send + * via this function. + * @param gateway the UMessageGateway to request a UMessage from. + * @param whatCode the uint32 'what code' you what the UMessage to have + * @returns On success a valid UMessage object is returned that you can add data to. When you are done adding data to the + * UMessage, be sure to call UGOutgoingMessagePrepared() to let the gateway know that it is now okay to start sending + * the UMessage bytes. It's an error to call UGGetOutgoingMessage() again before calling UGOutgoingMessagePrepared(). + */ +UMessage UGGetOutgoingMessage(UMessageGateway * gateway, uint32 whatCode); + +/** Call this when you have finished adding data to a UMessage that was returned to you via UGGetOutgoingMessage(). + * This function tells the UMessageGateway that it is now okay to start sending the UMessage. + * @param gateway The same gateway object that you previously passed to UGGetOutgoingMessage(). + * @param msg A pointer to the UMesage that you previously received from UGGetOutgoingMessage(). + */ +void UGOutgoingMessagePrepared(UMessageGateway * gateway, const UMessage * msg); + +/** You can call this instead of UGOutgoingMessagePrepared() if you decided you don't want to send a UMessage after all. + * (e.g. because the UMessage returned by UGGetOutgoingMessage() was too small, or something). + * This will set the gateway back into its pre-UGGetOutgoingMessage() state. + * @param gateway The same gateway object that you previously passed to UGGetOutgoingMessage(). + * @param msg A pointer to the UMesage that you previously received from UGGetOutgoingMessage(). + */ +void UGOutgoingMessageCancelled(UMessageGateway * gateway, const UMessage * msg); + +/** Returns UTrue iff the given gateway has any output bytes queued up, that it wants to send. + * @param gw The Gateway to query. + * @returns UTrue if there are bytes queued up to send, or UFalse otherwise. + */ +UBool UGHasBytesToOutput(const UMessageGateway * gw); + +/** Writes out as many queued bytes as possible (up to maxBytes). + * @param gw The Gateway that should do the outputting. + * @param maxBytes The maximum number of bytes that should be sent by this function call. Pass in ~0 to write without limit. + * @param sendFunc The function that the gateway way will call to actually do the write operation. + * @param arg The argument to pass to the write function. + * @returns The number of bytes written, or a negative number if there was an error. + */ +int32 UGDoOutput(UMessageGateway * gw, uint32 maxBytes, UGSendFunc sendFunc, void * arg); + +/** Reads in as many queued bytes as possible (up to maxBytes, or until a full UMessage is read). + * @param gw The Gateway that should do the inputting. + * @param maxBytes The maximum number of bytes that should be read by this function call. Pass in ~0 to read without limit. + * @param recvFunc The function that the gateway way will call to actually do the read operation. + * @param arg The argument to pass to the read function. + * @param optRetMsg When a full UMessage has been read, this UMessage object will be updated to be valid, and it should + * be examined after UGDoInput() returned. Note that the UMessage will only remain valid until the next + * call to UGDoInput(), so any data you need from the UMessage should be examined or copied out immediately. + * @returns The number of bytes read, or a negative number if there was an error. + */ +int32 UGDoInput(UMessageGateway * gw, uint32 maxBytes, UGReceiveFunc recvFunc, void * arg, UMessage * optRetMsg); + +/** @} */ // end of micromessagegateway doxygen group + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/micromessage/README.TXT b/micromessage/README.TXT new file mode 100644 index 00000000..25b6c650 --- /dev/null +++ b/micromessage/README.TXT @@ -0,0 +1,62 @@ + +This folder contains an extremely bare-bones C implementation of the +MUSCLE Message and MessageGateway APIs. + +If you want to avoid C++ in your program (usually because you want +to minimize executable size, or because a C++ compiler is not available) +you can use the MiniMessages APIs in the minimessage folder to +implement simple MUSCLE Message functionality instead. + +However, if even MiniMessage is too heavyweight, and you want an +implementation that avoids dynamic memory allocation altogether, +you can use this MicroMessage implementation instead. + +This implementation never boths to flatten or unflatten Message +objects at all; instead it reads and writes flattened Message +data directly from a raw buffer of bytes. + +That makes this implementation very efficient, but it does restrict +its functionality. In particular when constructing a MicroMessage +buffer, data can only be appended. Data already existing in the +MicroMessage buffer is considered read-only. Also, you can only +add as much data as will fit into the byte-buffer you provided. +Attempts to add more data than that will fail. + +These files do not depend on any other files outside of this folder, +except for support/MuscleSupport.h. + +The MicroMessage.{c,h} files contain the MicroMessage API -- the +equivalent to the MiniMessage class in the minimessage folder, or +the Message class in C++. There are some significant +differences between the two implementations, however: + +1) A MicroMessage object does not contain its own memory buffer -- + you must provide (and guarantee the lifetime of) your own + external memory buffer that the MicroMessage object can + read from and/or write to. + +2) Unlike the Message class, where fields in the Message are dynamically + extensible, the MMessage API requires you to specify the number of + items in a field in advance. For example, to add three int32's to + a C++ Message, you could do this: + + Message msg; + msg.AddInt32("fieldname", 1); + msg.AddInt32("fieldname", 2); + msg.AddInt32("fieldname", 3); + [...] + + + The same code using the MMessage API would look like this: + + char buf[1024] + MicroMessage msg; + UMInitialize(&msg, buf, sizeof(buf)); + UMAddInt32("fieldname", 1); + UMAddInt32("fieldname", 2); + UMAddInt32("fieldname", 3); + + It's a bit less flexible, but it makes the implementation much + simpler and more efficient. See the test file tests/testmini.cpp + for example usage of the MMessage API. + diff --git a/minimessage/MiniMessage.c b/minimessage/MiniMessage.c new file mode 100644 index 00000000..38382ca7 --- /dev/null +++ b/minimessage/MiniMessage.c @@ -0,0 +1,1343 @@ +#include +#include "minimessage/MiniMessage.h" + +#ifdef cplusplus +extern "C" { +#endif + +static uint32 _allocedBytes = 0; /* for memory-leak debugging */ + +uint32 MGetNumBytesAllocated() {return _allocedBytes;} + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + +void * MMalloc(uint32 numBytes) +{ + uint32 * ret = (uint32 *) malloc(numBytes+sizeof(uint32)); + if (ret) + { + *ret = numBytes; + _allocedBytes += numBytes; +/*printf("++"UINT32_FORMAT_SPEC" -> "UINT32_FORMAT_SPEC"\n", *ret, _allocedBytes);*/ + return ret+1; + } + return NULL; +} + +void * MFree(void * ptr) +{ + if (ptr) + { + uint32 * ret = ((uint32 *)ptr)-1; + _allocedBytes -= *ret; +/*printf("--"UINT32_FORMAT_SPEC" -> "UINT32_FORMAT_SPEC"\n", *ret, _allocedBytes);*/ + free(ret); + } +} + +void * MRealloc(void * oldBuf, uint32 newSize) +{ + uint32 oldSize = oldBuf ? (*(((uint32*)oldBuf)-1)) : 0; + + if (newSize == oldSize) return oldBuf; + else + { + void * newBuf = (newSize > 0) ? MMalloc(newSize) : NULL; + if ((newSize > 0)&&(newBuf == NULL)) return NULL; // out-of-memory error! Avoid side effects + + if ((newBuf)&&(oldBuf)) memcpy(newBuf, oldBuf, (newSize inputBufferBytes) return B_ERROR; + memcpy(copyTo, &inBuf[*readOffset], blockSize); + *readOffset += blockSize; + return B_NO_ERROR; +}; + +static void WriteData(uint8 * outBuf, uint32 * writeOffset, const void * copyFrom, uint32 blockSize) +{ + memcpy(&outBuf[*writeOffset], copyFrom, blockSize); + *writeOffset += blockSize; +}; + +#define OLDEST_SUPPORTED_PROTOCOL_VERSION 1347235888 /* 'PM00' */ +#define CURRENT_PROTOCOL_VERSION 1347235888 /* 'PM00' */ + +struct _MMessageField; /* forward declaration */ +struct _MMessageField { + struct _MMessageField * prevField; /* used by our linked list */ + struct _MMessageField * nextField; /* used by our linked list */ + uint32 allocSize; /* Our allocation size, for convenience */ + const char * name; /* pointer to our field name, for convenience */ + uint32 nameBytes; /* number of bytes in field name (including NUL) */ + uint32 typeCode; + void * data; /* Pointer to our data bytes, for convenience */ + uint32 itemSize; /* item size, for convenience */ + uint32 numItems; /* number of item-slots in this field */ + MBool isFixedSize; /* If MFalse, data is an array of MByteBuffers */ + MBool isFlattenable; /* If MFalse, data shouldn't be Flattened */ + /* ... data bytes here ... */ + /* name bytes start here, after the last data byte */ + /* Note that name bytes must be last, or MMRenameField() breaks */ +}; +typedef struct _MMessageField MMessageField; + +/* Our internal implementation of the MMessage object... known only to the */ +/* code inside this file! All external code shouldn't need to know about this. */ +struct _MMessage { + uint32 what; + uint32 numFields; + MMessageField * firstField; + MMessageField * lastField; + + struct _MMessage * scratch; /* scratch variable, used in Unflatten */ +}; + +MByteBuffer * MBAllocByteBuffer(uint32 numBytes, MBool clearBytes) +{ + MByteBuffer * mbb = (MByteBuffer *) MMalloc((sizeof(MByteBuffer)+numBytes)-1); /* -1 since at least 1 data byte is in the struct */ + if (mbb) + { + mbb->numBytes = numBytes; + if (clearBytes) memset(&mbb->bytes, 0, numBytes); + } + return mbb; +} + +MByteBuffer * MBStrdupByteBuffer(const char * sourceString) +{ + int slen = ((int)strlen(sourceString))+1; + MByteBuffer * mbb = MBAllocByteBuffer(slen, MFalse); + if (mbb) memcpy(&mbb->bytes, sourceString, slen); + return mbb; +} + +MByteBuffer * MBCloneByteBuffer(const MByteBuffer * cloneMe) +{ + MByteBuffer * mbb = MBAllocByteBuffer(cloneMe->numBytes, MFalse); + if (mbb) memcpy(&mbb->bytes, &cloneMe->bytes, mbb->numBytes); + return mbb; +} + +MBool MBAreByteBuffersEqual(const MByteBuffer * buf1, const MByteBuffer * buf2) +{ + return ((buf1 == buf2)||((buf1->numBytes == buf2->numBytes)&&(memcmp(&buf1->bytes, &buf2->bytes, buf1->numBytes) == 0))); +} + +void MBFreeByteBuffer(MByteBuffer * msg) +{ + /* yup, it's that easy... but you should still always call this function */ + /* instead of calling MFree() directly, in case the implementation changes later */ + if (msg) MFree(msg); +} + +/* Note that numNameBytes includes the NUL byte! */ +static MMessageField * AllocMMessageField(const char * fieldName, uint32 numNameBytes, uint32 typeCode, uint32 numItems, uint32 itemSize) +{ + if (numItems > 0) + { + const uint32 dataSize = numItems*itemSize; + const uint32 allocSize = sizeof(MMessageField)+dataSize+numNameBytes; + MMessageField * ret = (MMessageField *) MMalloc(allocSize); + if (ret) + { + ret->prevField = ret->nextField = NULL; + ret->allocSize = allocSize; + ret->nameBytes = numNameBytes; + ret->typeCode = typeCode; + ret->itemSize = itemSize; + ret->numItems = numItems; + ret->data = ((char *)ret) + sizeof(MMessageField); /* data bytes start immediately after the struct */ + ret->name = ((char *)ret->data)+dataSize; /* name starts right after the data */ + ret->isFixedSize = ret->isFlattenable = MTrue; + memset(ret->data, 0, dataSize); + memcpy((void *)ret->name, fieldName, numNameBytes); + ((char *)ret->name)[ret->nameBytes-1] = '\0'; /* paranoia: guarantee that the field name is terminated */ + } + return ret; + } + else return NULL; /* we don't allow zero-item fields! */ +} + +static MMessageField * CloneMMessageField(const MMessageField * cloneMe) +{ + MMessageField * clone = (MMessageField *) MMalloc(cloneMe->allocSize); + if (clone) + { + memcpy(clone, cloneMe, cloneMe->isFixedSize ? cloneMe->allocSize : sizeof(MMessageField)); + clone->prevField = clone->nextField = NULL; + clone->data = ((char *)clone) + (((char *)cloneMe->data)-((char *)cloneMe)); + clone->name = ((const char *)clone) + (cloneMe->name-((const char *)cloneMe)); + if (clone->isFixedSize == MFalse) + { + /* Oops, this field has alloced-pointer semantics, so we have to clone all the items too */ + if (clone->typeCode == B_MESSAGE_TYPE) + { + MMessage ** dstArray = (MMessage **) clone->data; + const MMessage ** srcArray = (const MMessage **) cloneMe->data; + int32 i; + for (i=cloneMe->numItems-1; i>=0; i--) + { + if (srcArray[i]) + { + if ((dstArray[i] = MMCloneMessage(srcArray[i])) == NULL) + { + /* Allocation failure! Roll back previous allocs and fail cleanly */ + int32 j; + for (j=cloneMe->numItems-1; j>i; j--) MMFreeMessage(dstArray[j]); + MFree(clone); + return NULL; + } + } + else dstArray[i] = NULL; + } + } + else + { + MByteBuffer ** dstArray = (MByteBuffer **) clone->data; + const MByteBuffer ** srcArray = (const MByteBuffer **) cloneMe->data; + int32 i; + for (i=cloneMe->numItems-1; i>=0; i--) + { + if (srcArray[i]) + { + if ((dstArray[i] = MBCloneByteBuffer(srcArray[i])) == NULL) + { + /* Allocation failure! Roll back previous allocs and fail cleanly */ + int32 j; + for (j=cloneMe->numItems-1; j>i; j--) MBFreeByteBuffer(dstArray[j]); + MFree(clone); + return NULL; + } + } + else dstArray[i] = NULL; + } + } + + /* copy the name too, since we didn't do it above */ + memcpy((char *)clone->name, cloneMe->name, clone->nameBytes); + } + } + return clone; +} + +static void FreeMMessageField(MMessageField * field) +{ + if (field) + { + if (field->isFixedSize == MFalse) + { + /* Oops, this field has alloced-pointer semantics, so we have to free all the items too */ + if (field->typeCode == B_MESSAGE_TYPE) + { + MMessage ** array = (MMessage **) field->data; + int32 i; + for (i=field->numItems-1; i>=0; i--) MMFreeMessage(array[i]); + } + else + { + MByteBuffer ** array = (MByteBuffer **) field->data; + int32 i; + for (i=field->numItems-1; i>=0; i--) MBFreeByteBuffer(array[i]); + } + } + MFree(field); + } +} + +MMessage * MMAllocMessage(uint32 what) +{ + MMessage * ret = (MMessage *) MMalloc(sizeof(MMessage)); + if (ret) + { + ret->what = what; + ret->numFields = 0; + ret->firstField = ret->lastField = NULL; + } + return ret; +} + +MMessage * MMCloneMessage(const MMessage * cloneMe) +{ + MMessage * clone = MMAllocMessage(cloneMe->what); + if (clone) + { + const MMessageField * nextField = cloneMe->firstField; + while(nextField) + { + MMessageField * cloneField = CloneMMessageField(nextField); + if (cloneField) + { + if (clone->lastField) + { + clone->lastField->nextField = cloneField; + cloneField->prevField = clone->lastField; + clone->lastField = cloneField; + } + else clone->firstField = clone->lastField = cloneField; + + clone->numFields++; + } + else + { + MMFreeMessage(clone); + return NULL; + } + nextField = nextField->nextField; + } + } + return clone; +} + +void MMFreeMessage(MMessage * msg) +{ + if (msg) + { + MMClearMessage(msg); + MFree(msg); + } +} + +uint32 MMGetWhat(const MMessage * msg) +{ + return msg->what; +} + +void MMSetWhat(MMessage * msg, uint32 newWhat) +{ + msg->what = newWhat; +} + +void MMClearMessage(MMessage * msg) +{ + MMessageField * field = msg->firstField; + while(field) + { + MMessageField * nextField = field->nextField; + FreeMMessageField(field); + field = nextField; + } + msg->firstField = msg->lastField = NULL; + msg->numFields = 0; +} + +/** This function adds the given field into msg's linked list of fields. */ +static void AddMMessageField(MMessage * msg, MMessageField * field) +{ + field->prevField = msg->lastField; + if (msg->lastField) msg->lastField->nextField = field; + else msg->firstField = field; + msg->lastField = field; + msg->numFields++; +} + +static MBool IsTypeCodeVariableSize(uint32 typeCode) +{ + /* Don't allow the user to put data blobs under any of the non-data-blob type codes, */ + /* because that would mess up our internal logic which assumes that fields of those */ + /* types have the appropriate data for their type */ + switch(typeCode) + { + case B_BOOL_TYPE: case B_DOUBLE_TYPE: case B_FLOAT_TYPE: case B_INT64_TYPE: + case B_INT32_TYPE: case B_INT16_TYPE: case B_INT8_TYPE: + case B_POINTER_TYPE: case B_POINT_TYPE: case B_RECT_TYPE: + return MFalse; + + default: + return MTrue; + } +} + +static MMessageField * LookupMMessageField(const MMessage * msg, const char * fieldName, uint32 typeCode) +{ + MMessageField * f = msg->lastField; /* assuming we're more likely to look up what we added most recently (?) */ + while(f) + { + if (strcmp(f->name, fieldName) == 0) return ((typeCode == B_ANY_TYPE)||(typeCode == f->typeCode)) ? f : NULL; + f = f->prevField; + } + return NULL; /* field not found */ +} + +static void DetachMMessageField(MMessage * msg, MMessageField * field) +{ + if (field->prevField) field->prevField->nextField = field->nextField; + if (field->nextField) field->nextField->prevField = field->prevField; + if (field == msg->lastField) msg->lastField = field->prevField; + if (field == msg->firstField) msg->firstField = field->nextField; + field->prevField = field->nextField = NULL; + msg->numFields--; +} + +/** This function handles placing a new variable-sized-data-blob field into a Message */ +static MByteBuffer ** PutMMVariableFieldAux(MMessage * msg, MBool retainOldData, uint32 typeCode, const char * fieldName, uint32 numItems) +{ + MMessageField * newField = AllocMMessageField(fieldName, ((uint32)(strlen(fieldName)))+1, typeCode, numItems, sizeof(MByteBuffer *)); + if (newField) + { + MMessageField * oldField = LookupMMessageField(msg, fieldName, B_ANY_TYPE); + newField->isFixedSize = MFalse; + if (oldField) + { + if ((retainOldData)&&(oldField->typeCode == typeCode)) + { + uint32 commonItems = (numItems < oldField->numItems) ? numItems : oldField->numItems; + uint32 i; + MByteBuffer ** fromArray = (MByteBuffer **) oldField->data; + MByteBuffer ** toArray = (MByteBuffer **) newField->data; + for (i=0; idata : NULL; +} + +static void * GetFieldDataAux(const MMessage * msg, const char * fieldName, uint32 typeCode, uint32 * optRetNumItems) +{ + MMessageField * f = LookupMMessageField(msg, fieldName, typeCode); + if ((f)&&(f->numItems > 0)) + { + if (optRetNumItems) *optRetNumItems = f->numItems; + return f->data; + } + return NULL; +} + +static void * PutMMFixedFieldAux(MMessage * msg, MBool retainOldData, uint32 typeCode, const char * fieldName, uint32 numItems, uint32 itemSize) +{ + MMessageField * newField = AllocMMessageField(fieldName, ((uint32)strlen(fieldName))+1, typeCode, numItems, itemSize); + if (newField) + { + MMessageField * oldField = LookupMMessageField(msg, fieldName, B_ANY_TYPE); + if (oldField) + { + if ((retainOldData)&&(oldField->typeCode == typeCode)) memcpy(newField->data, oldField->data, ((numItems < oldField->numItems) ? numItems : oldField->numItems)*itemSize); + DetachMMessageField(msg, oldField); + FreeMMessageField(oldField); + } + AddMMessageField(msg, newField); + } + return newField ? newField->data : NULL; +} + +status_t MMRemoveField(MMessage * msg, const char * fieldName) +{ + MMessageField * field = LookupMMessageField(msg, fieldName, B_ANY_TYPE); + if (field) + { + DetachMMessageField(msg, field); + FreeMMessageField(field); + return B_NO_ERROR; + } + return B_ERROR; +} + +MByteBuffer ** MMPutStringField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return PutMMVariableFieldAux(msg, retainOldData, B_STRING_TYPE, fieldName, numItems); +} + +MBool * MMPutBoolField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (MBool *) PutMMFixedFieldAux(msg, retainOldData, B_BOOL_TYPE, fieldName, numItems, sizeof(MBool)); +} + +int8 * MMPutInt8Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (int8 *) PutMMFixedFieldAux(msg, retainOldData, B_INT8_TYPE, fieldName, numItems, sizeof(int8)); +} + +int16 * MMPutInt16Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (int16 *) PutMMFixedFieldAux(msg, retainOldData, B_INT16_TYPE, fieldName, numItems, sizeof(int16)); +} + +int32 * MMPutInt32Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (int32 *) PutMMFixedFieldAux(msg, retainOldData, B_INT32_TYPE, fieldName, numItems, sizeof(int32)); +} + +int64 * MMPutInt64Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (int64 *) PutMMFixedFieldAux(msg, retainOldData, B_INT64_TYPE, fieldName, numItems, sizeof(int64)); +} + +float * MMPutFloatField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (float *) PutMMFixedFieldAux(msg, retainOldData, B_FLOAT_TYPE, fieldName, numItems, sizeof(float)); +} + +double * MMPutDoubleField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (double *) PutMMFixedFieldAux(msg, retainOldData, B_DOUBLE_TYPE, fieldName, numItems, sizeof(double)); +} + +MMessage ** MMPutMessageField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + MMessageField * newField = AllocMMessageField(fieldName, ((uint32)strlen(fieldName))+1, B_MESSAGE_TYPE, numItems, sizeof(MMessage *)); + if (newField) + { + MMessageField * oldField = LookupMMessageField(msg, fieldName, B_ANY_TYPE); + newField->isFixedSize = MFalse; + if (oldField) + { + if ((retainOldData)&&(oldField->typeCode == B_MESSAGE_TYPE)) + { + uint32 commonItems = (numItems < oldField->numItems) ? numItems : oldField->numItems; + uint32 i; + MMessage ** fromArray = (MMessage **) oldField->data; + MMessage ** toArray = (MMessage **) newField->data; + for (i=0; idata : NULL; +} + +void ** MMPutPointerField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + void ** ret = (void **) PutMMFixedFieldAux(msg, retainOldData, B_POINTER_TYPE, fieldName, numItems, sizeof(void *)); + if (ret) + { + MMessageField * f = LookupMMessageField(msg, fieldName, B_POINTER_TYPE); + if (f) f->isFlattenable = MFalse; + } + return ret; +} + +MPoint * MMPutPointField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (MPoint *) PutMMFixedFieldAux(msg, retainOldData, B_POINT_TYPE, fieldName, numItems, sizeof(MPoint)); +} + +MRect * MMPutRectField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems) +{ + return (MRect *) PutMMFixedFieldAux(msg, retainOldData, B_RECT_TYPE, fieldName, numItems, sizeof(MRect)); +} + +MByteBuffer ** MMPutDataField(MMessage * msg, MBool retainOldData, uint32 typeCode, const char * fieldName, uint32 numItems) +{ + return ((typeCode != B_MESSAGE_TYPE)&&(IsTypeCodeVariableSize(typeCode))) ? PutMMVariableFieldAux(msg, retainOldData, typeCode, fieldName, numItems) : NULL; +} + +static uint32 GetMMessageFieldFlattenedSize(const MMessageField * f, MBool includeHeaders) +{ + uint32 sum = includeHeaders ? (sizeof(uint32) + f->nameBytes + sizeof(uint32) + sizeof(uint32)) : 0; /* Entry name length, entry name, string, type code, entry data length */ + if (IsTypeCodeVariableSize(f->typeCode)) + { + uint32 numItems = f->numItems; + uint32 i; + + sum += (numItems*sizeof(uint32)); /* because each sub-item is preceded by its byte-count */ + if (f->typeCode == B_MESSAGE_TYPE) + { + /* Note that NULL entries will be flattened as empty Messages, since the protocol doesn't support them directly. */ + const MMessage ** msgs = (const MMessage **) f->data; + for (i=0; idata; + sum += sizeof(uint32); /* num-items-in-array field */ + for (i=0; inumBytes : 0; + } + } + else sum += f->numItems*f->itemSize; /* Entry data length and data items */ + + return sum; +} + +/* Copies (numItems) items from (in) to (out), swapping the byte gender of each item as it goes. */ +static void SwapCopy(void * out, const void * in, uint32 numItems, uint32 itemSize) +{ + if (itemSize > 1) + { + const int numBytes = numItems * itemSize; + int whichByte; + for (whichByte=itemSize-1; whichByte>=0; whichByte--) + { + int i; + uint8 * out8 = ((uint8 *)out)+whichByte; + const uint8 * in8 = ((const uint8 *)in)+itemSize-(whichByte+1); + for(i=0; inameBytes); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Write entry name */ + memcpy(&buffer[writeOffset], f->name, f->nameBytes); writeOffset += f->nameBytes; + + /* Write entry type code */ + { + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(f->typeCode); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Write entry data length */ + { + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(GetMMessageFieldFlattenedSize(f, MFalse)); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Write entry data */ + if (IsTypeCodeVariableSize(f->typeCode)) + { + uint32 numItems = f->numItems; + uint32 i; + + if (f->typeCode == B_MESSAGE_TYPE) + { + /* Note that NULL entries will be flattened as empty Messages, since the protocol doesn't support them directly. */ + /* Note also that for this type the number-of-items-in-array field is NOT written into the buffer (sigh) */ + const MMessage ** msgs = (const MMessage **) f->data; + for (i=0; idata; + int i; + + /* Write the number of items in the array */ + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(numItems); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + + for (i=0; inumBytes : 0; + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(bufSize); + WriteData(buffer, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + memcpy(&buffer[writeOffset], &bufs[i]->bytes, bufSize); writeOffset += bufSize; + } + } + } + else + { + uint32 dataSize = f->numItems*f->itemSize; + MBool doMemcpy = MTrue; + if (BYTE_ORDER != LITTLE_ENDIAN) + { + doMemcpy = MFalse; + switch(f->typeCode) + { + case B_BOOL_TYPE: case B_INT8_TYPE: doMemcpy = MTrue; break; + case B_POINT_TYPE: SwapCopy(&buffer[writeOffset], f->data, f->numItems*2, sizeof(float)); break; + case B_RECT_TYPE: SwapCopy(&buffer[writeOffset], f->data, f->numItems*4, sizeof(float)); break; + default: SwapCopy(&buffer[writeOffset], f->data, f->numItems, f->itemSize); break; + } + } + if (doMemcpy) memcpy(&buffer[writeOffset], f->data, dataSize); + writeOffset += dataSize; + } + + return writeOffset; +} + +uint32 MMGetFlattenedSize(const MMessage * msg) +{ + /* For the message header: 4 bytes for the protocol revision #, 4 bytes for the number-of-entries field, 4 bytes for what code */ + uint32 sum = 3 * sizeof(uint32); + + const MMessageField * f = msg->firstField; + while(f) + { + if (f->isFlattenable) sum += GetMMessageFieldFlattenedSize(f, MTrue); + f = f->nextField; + } + return sum; +} + +void MMFlattenMessage(const MMessage * msg, void * outBuf) +{ + /* Format: 0. Protocol revision number (4 bytes, always set to CURRENT_PROTOCOL_VERSION) */ + /* 1. 'what' code (4 bytes) */ + /* 2. Number of entries (4 bytes) */ + /* 3. Entry name length (4 bytes) */ + /* 4. Entry name string (flattened String) */ + /* 5. Entry type code (4 bytes) */ + /* 6. Entry data length (4 bytes) */ + /* 7. Entry data (n bytes) */ + /* 8. loop to 3 as necessary */ + + /* Write current protocol version */ + uint32 writeOffset = 0; + { + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(CURRENT_PROTOCOL_VERSION); + WriteData(outBuf, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Write 'what' code */ + { + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(msg->what); + WriteData(outBuf, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Calculate the number of flattenable entries (may be less than the total number of entries!) */ + { + uint32 numFlattenableEntries = 0; + { + const MMessageField * f = msg->firstField; + while(f) + { + if (f->isFlattenable) numFlattenableEntries++; + f = f->nextField; + } + } + + /* Write number of entries */ + { + uint32 networkByteOrder = B_HOST_TO_LENDIAN_INT32(numFlattenableEntries); + WriteData(outBuf, &writeOffset, &networkByteOrder, sizeof(networkByteOrder)); + } + + /* Write entries */ + { + const MMessageField * f = msg->firstField; + while(f) + { + if (f->isFlattenable) writeOffset += FlattenMMessageField(f, &((char *)outBuf)[writeOffset]); + f = f->nextField; + } + } + } +/*printf("%s "UINT32_FORMAT_SPEC"/"UINT32_FORMAT_SPEC"\n", (writeOffset != MMGetFlattenedSize(msg))?"ERROR":"ok", writeOffset, MMGetFlattenedSize(msg)); DEBUG */ +} + +static MMessageField * ImportMMessageField(const char * fieldName, uint32 nameLength, uint32 tc, uint32 eLength, const void * dataPtr, uint32 itemSize, uint32 swapSize) +{ + uint32 numItems = eLength/itemSize; + MMessageField * field = AllocMMessageField(fieldName, nameLength, tc, numItems, itemSize); + if (field) + { + if (BYTE_ORDER != LITTLE_ENDIAN) SwapCopy(field->data, dataPtr, numItems*(itemSize/swapSize), swapSize); + else memcpy(field->data, dataPtr, numItems*itemSize); + return field; + } + return NULL; +} + +status_t MMUnflattenMessage(MMessage * msg, const void * inBuf, uint32 inputBufferBytes) +{ + uint32 i, readOffset = 0; + const uint8 * buffer = (const uint8 *) inBuf; + const uint8 * dataPtr; + + /* Read and check protocol version number */ + uint32 networkByteOrder, numEntries; + { + uint32 messageProtocolVersion; + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) return B_ERROR; + + messageProtocolVersion = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if ((messageProtocolVersion < OLDEST_SUPPORTED_PROTOCOL_VERSION)||(messageProtocolVersion > CURRENT_PROTOCOL_VERSION)) return B_ERROR; + + /* Read 'what' code */ + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) return B_ERROR; + msg->what = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + + /* Read number of entries */ + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) return B_ERROR; + numEntries = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + } + + MMClearMessage(msg); + + /* Read entries */ + for (i=0; i inputBufferBytes-readOffset) return B_ERROR; + + /* Read entry name */ + fieldName = (const char *) &buffer[readOffset]; + readOffset += nameLength; + + /* Read entry type code */ + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) return B_ERROR; + tc = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + + /* Read entry data length */ + if (ReadData(buffer, inputBufferBytes, &readOffset, &networkByteOrder, sizeof(networkByteOrder)) != B_NO_ERROR) return B_ERROR; + eLength = B_LENDIAN_TO_HOST_INT32(networkByteOrder); + if (eLength > inputBufferBytes-readOffset) return B_ERROR; + + dataPtr = &buffer[readOffset]; + switch(tc) + { + case B_BOOL_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(MBool), sizeof(MBool)); break; + case B_DOUBLE_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(double), sizeof(double)); break; + case B_FLOAT_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(float), sizeof(float)); break; + case B_INT64_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(int64), sizeof(int64)); break; + case B_INT32_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(int32), sizeof(int32)); break; + case B_INT16_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(int16), sizeof(int16)); break; + case B_INT8_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(int8), sizeof(int8)); break; + + case B_MESSAGE_TYPE: + { + /* Annoying special case, since this type doesn't contain a number-of-items field, */ + /* we have to read them in to a temporary linked list and find out how many there are that way */ + MMessage * head = NULL, * tail = NULL; + uint32 eUsed = 0; + uint32 eOffset = readOffset; + uint32 listLength = 0; + status_t ret = B_NO_ERROR; + while((eUsed < eLength)&&(ret == B_NO_ERROR)) + { + uint32 entryLen; + ret = B_ERROR; /* go pessimistic for a bit */ + if (ReadData(buffer, inputBufferBytes, &eOffset, &entryLen, sizeof(entryLen)) == B_NO_ERROR) + { + entryLen = B_LENDIAN_TO_HOST_INT32(entryLen); + if (eOffset + entryLen <= inputBufferBytes) + { + MMessage * newMsg = MMAllocMessage(0); + if (newMsg) + { + if (MMUnflattenMessage(newMsg, &buffer[eOffset], entryLen) == B_NO_ERROR) + { + ret = B_NO_ERROR; + eOffset += entryLen; + eUsed += (sizeof(uint32) + entryLen); + if (tail) + { + tail->scratch = newMsg; + tail = newMsg; + } + else head = tail = newMsg; + + listLength++; + } + else MMFreeMessage(newMsg); + } + } + } + } + if (ret == B_NO_ERROR) + { + MMessage ** newMsgField = MMPutMessageField(msg, MFalse, fieldName, listLength); + if (newMsgField) + { + uint32 i; + for (i=0; iscratch; + } + doAddField = MFalse; + } + else ret = B_ERROR; + } + if (ret != B_NO_ERROR) + { + /* Clean up on error */ + while(head) + { + MMessage * next = head->scratch; + MMFreeMessage(head); + head = next; + } + MMRemoveField(msg, fieldName); + return B_ERROR; + } + } + break; + + /** Note that this should never really happen since we don't put pointers in flattened messages anymore, but it's here for backwards compatibility + * with older code that did do that. + */ + case B_POINTER_TYPE: newField = ImportMMessageField(fieldName, nameLength, B_INT32_TYPE, eLength, dataPtr, sizeof(int32), sizeof(int32)); break; + case B_POINT_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(float)*2, sizeof(float)); break; + case B_RECT_TYPE: newField = ImportMMessageField(fieldName, nameLength, tc, eLength, dataPtr, sizeof(float)*4, sizeof(float)); break; + + default: + { + /* All other types get loaded as variable-sized byte buffers */ + uint32 numItems; + uint32 eOffset = readOffset; + if (ReadData(buffer, inputBufferBytes, &eOffset, &numItems, sizeof(numItems)) == B_NO_ERROR) + { + MByteBuffer ** bufs; + numItems = B_LENDIAN_TO_HOST_INT32(numItems); + bufs = PutMMVariableFieldAux(msg, MFalse, tc, fieldName, numItems); + if (bufs) + { + uint32 eLeft = eLength; + uint32 i; + + doAddField = MFalse; + for (i=0; ibytes, &buffer[eOffset], itemSize); + eOffset += itemSize; + ok = true; + } + } + if (ok == MFalse) + { + MMRemoveField(msg, fieldName); + return B_ERROR; + } + } + } + } + } + break; + } + + if (doAddField) + { + if (newField) AddMMessageField(msg, newField); + else return B_ERROR; + } + + readOffset += eLength; + } + return B_NO_ERROR; +} + +status_t MMMoveField(MMessage * sourceMsg, const char * fieldName, MMessage * destMsg) +{ + if (sourceMsg == destMsg) return B_NO_ERROR; /* easy to move it to itself, it's already there! */ + else + { + MMessageField * moveMe = LookupMMessageField(sourceMsg, fieldName, B_ANY_TYPE); + if (moveMe) + { + DetachMMessageField(sourceMsg, moveMe); + if (destMsg) + { + MMessageField * killMe = LookupMMessageField(destMsg, fieldName, B_ANY_TYPE); + if (killMe) + { + DetachMMessageField(destMsg, killMe); + FreeMMessageField(moveMe); + } + AddMMessageField(destMsg, moveMe); + } + else FreeMMessageField(moveMe); /* move to NULL == kill it */ + + return B_NO_ERROR; + } + return B_ERROR; + } +} + +status_t MMCopyField(const MMessage * sourceMsg, const char * fieldName, MMessage * destMsg) +{ + if (sourceMsg == destMsg) return B_NO_ERROR; /* easy to move it to itself, it's already there! */ + else + { + MMessageField * copyMe = LookupMMessageField(sourceMsg, fieldName, B_ANY_TYPE); + if (copyMe) + { + if (destMsg) + { + MMessageField * clone = CloneMMessageField(copyMe); + if (clone) + { + MMessageField * killMe = LookupMMessageField(destMsg, fieldName, B_ANY_TYPE); + if (killMe) + { + DetachMMessageField(destMsg, killMe); + FreeMMessageField(killMe); + } + AddMMessageField(destMsg, clone); + } + else return B_ERROR; + } + return B_NO_ERROR; + } + return B_ERROR; + } +} + +status_t MMRenameField(MMessage * msg, const char * oldFieldName, const char * newFieldName) +{ + if (strcmp(oldFieldName, newFieldName) == 0) return B_NO_ERROR; + else + { + MMessageField * f = LookupMMessageField(msg, oldFieldName, B_ANY_TYPE); + if (f) + { + MMessageField * overwriteThis = LookupMMessageField(msg, newFieldName, B_ANY_TYPE); /* do this lookup before any mods! */ + int slen = ((int)strlen(newFieldName))+1; + int diff = (slen - f->nameBytes); + if (diff != 0) + { + uint32 newSize = f->allocSize+diff; + MMessageField * g = (MMessageField *) MRealloc(f, newSize); + if (g == NULL) return B_ERROR; + + g->allocSize = newSize; + g->nameBytes = slen; + if (g != f) + { + /* Oops, gotta update all the pointers to point to the new field */ + g->data = ((char *)g) + sizeof(MMessageField); /* data bytes start immediately after the struct */ + g->name = ((const char *)g->data)+(g->numItems*g->itemSize); /* name starts right after the data */ + if (g->prevField) g->prevField->nextField = g; + if (g->nextField) g->nextField->prevField = g; + if (msg->firstField == f) msg->firstField = g; + if (msg->lastField == f) msg->lastField = g; + f = g; + } + } + memcpy((char *)f->name, newFieldName, slen); + + /* If our field now occupies the same name as another, delete the other, to maintain fieldname uniqueness */ + if (overwriteThis) + { + DetachMMessageField(msg, overwriteThis); + FreeMMessageField(overwriteThis); + } + + return B_NO_ERROR; + } + } + return B_ERROR; +} + +MByteBuffer ** MMGetStringField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (MByteBuffer **) GetFieldDataAux(msg, fieldName, B_STRING_TYPE, optRetNumItems); +} + +MBool * MMGetBoolField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (MBool *) GetFieldDataAux(msg, fieldName, B_BOOL_TYPE, optRetNumItems); +} + +int8 * MMGetInt8Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (int8 *) GetFieldDataAux(msg, fieldName, B_INT8_TYPE, optRetNumItems); +} + +int16 * MMGetInt16Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (int16 *) GetFieldDataAux(msg, fieldName, B_INT16_TYPE, optRetNumItems); +} + +int32 * MMGetInt32Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (int32 *) GetFieldDataAux(msg, fieldName, B_INT32_TYPE, optRetNumItems); +} + +int64 * MMGetInt64Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (int64 *) GetFieldDataAux(msg, fieldName, B_INT64_TYPE, optRetNumItems); +} + +float * MMGetFloatField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (float *) GetFieldDataAux(msg, fieldName, B_FLOAT_TYPE, optRetNumItems); +} + +double * MMGetDoubleField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (double *) GetFieldDataAux(msg, fieldName, B_DOUBLE_TYPE, optRetNumItems); +} + +MMessage ** MMGetMessageField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (MMessage **) GetFieldDataAux(msg, fieldName, B_MESSAGE_TYPE, optRetNumItems); +} + +void ** MMGetPointerField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (void **) GetFieldDataAux(msg, fieldName, B_POINTER_TYPE, optRetNumItems); +} + +MPoint * MMGetPointField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (MPoint *) GetFieldDataAux(msg, fieldName, B_POINT_TYPE, optRetNumItems); +} + +MRect * MMGetRectField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems) +{ + return (MRect *) GetFieldDataAux(msg, fieldName, B_RECT_TYPE, optRetNumItems); +} + +MByteBuffer ** MMGetDataField(const MMessage * msg, uint32 typeCode, const char * fieldName, uint32 * optRetNumItems) +{ + return ((typeCode != B_MESSAGE_TYPE)&&(IsTypeCodeVariableSize(typeCode))) ? (MByteBuffer **) GetFieldDataAux(msg, fieldName, typeCode, optRetNumItems) : NULL; +} + +status_t MMGetFieldInfo(const MMessage * msg, const char * fieldName, uint32 typeCode, uint32 * optRetNumItems, uint32 * optRetTypeCode) +{ + MMessageField * f = LookupMMessageField(msg, fieldName, typeCode); + if (f) + { + if (optRetNumItems) *optRetNumItems = f->numItems; + if (optRetTypeCode) *optRetTypeCode = f->typeCode; + return B_NO_ERROR; + } + return B_ERROR; +} + +MMessageIterator MMGetFieldNameIterator(const MMessage * msg, uint32 typeCode) +{ + MMessageIterator ret; + ret.message = msg; + ret.iterState = msg->firstField; + ret.typeCode = typeCode; + return ret; +} + +const char * MMGetNextFieldName(MMessageIterator * iter, uint32 * optRetTypeCode) +{ + while(iter->iterState) + { + MMessageField * f = (MMessageField *) iter->iterState; + if ((iter->typeCode == B_ANY_TYPE)||(iter->typeCode == f->typeCode)) + { + const char * ret = f->name; + if (optRetTypeCode) *optRetTypeCode = f->typeCode; + iter->iterState = f->nextField; + return ret; + } + else iter->iterState = f->nextField; + } + return NULL; +} + +static void DoIndent(int numSpaces) +{ + int i=0; + for (i=0; idata; + + int i = 0; + int ni = field->numItems; + if (ni > 10) ni = 10; /* truncate to avoid too much spam */ + + MakePrettyTypeCodeString(field->typeCode, pbuf); + DoIndent(indent); fprintf(file, "Entry: Name=[%s] GetNumItems()="UINT32_FORMAT_SPEC", TypeCode=%s ("INT32_FORMAT_SPEC") flatSize="UINT32_FORMAT_SPEC"\n", field->name, field->numItems, pbuf, field->typeCode, GetMMessageFieldFlattenedSize(field, MFalse)); + for (i=0; itypeCode) + { + case B_BOOL_TYPE: fprintf(file, "%i\n", (((const MBool *)data)[i])); break; + case B_DOUBLE_TYPE: fprintf(file, "%f\n", ((const double *)data)[i]); break; + case B_FLOAT_TYPE: fprintf(file, "%f\n", ((const float *)data)[i]); break; + case B_INT64_TYPE: fprintf(file, INT64_FORMAT_SPEC"\n", ((const int64 *)data)[i]); break; + case B_INT32_TYPE: fprintf(file, INT32_FORMAT_SPEC"\n", ((const int32 *)data)[i]); break; + case B_POINTER_TYPE: fprintf(file, "%p\n", ((const void **)data)[i]); break; + case B_INT16_TYPE: fprintf(file, "%i\n", ((const int16 *)data)[i]); break; + case B_INT8_TYPE: fprintf(file, "%i\n", ((const int8 *)data)[i]); break; + + case B_POINT_TYPE: + { + const MPoint * pt = &((const MPoint *)data)[i]; + fprintf(file, "x=%f y=%f\n", pt->x, pt->y); + } + break; + + case B_RECT_TYPE: + { + const MRect * rc = &((const MRect *)data)[i]; + fprintf(file, "l=%f t=%f r=%f b=%f\n", rc->left, rc->top, rc->right, rc->bottom); + } + break; + + case B_MESSAGE_TYPE: + { + MMessage * subMsg = ((MMessage **)field->data)[i]; + if (subMsg) PrintMMessageToStreamAux(subMsg, file, indent+3); + else fprintf(file, "(NULL Message)\n"); + } + break; + + case B_STRING_TYPE: + { + MByteBuffer * subBuf = ((MByteBuffer **)field->data)[i]; + if (subBuf) fprintf(file, "[%s]\n", (const char *) (&subBuf->bytes)); + else fprintf(file, "(NULL String)\n"); + } + break; + + default: + { + MByteBuffer * subBuf = ((MByteBuffer **)field->data)[i]; + if (subBuf) + { + int numBytes = subBuf->numBytes; + if (numBytes > 0) + { + const uint8 * b = &subBuf->bytes; + int nb = subBuf->numBytes; + int j; + + if (nb > 10) + { + fprintf(file, "(%i bytes, starting with", nb); + nb = 10; + } + else fprintf(file, "(%i bytes, equal to",nb); + + for (j=0; jnumBytes) fprintf(file, "...)\n"); + else fprintf(file, ")\n"); + } + else fprintf(file, "(zero-length buffer)\n"); + } + else fprintf(file, "(NULL Buffer)\n"); + } + break; + } + } +} + +static void PrintMMessageToStreamAux(const MMessage * msg, FILE * file, int indent) +{ + /* Make a pretty type code string */ + char buf[5]; + MakePrettyTypeCodeString(msg->what, buf); + + fprintf(file, "MMessage: msg=%p, what='%s' ("INT32_FORMAT_SPEC"), entryCount="INT32_FORMAT_SPEC", flatSize="UINT32_FORMAT_SPEC"\n", msg, buf, msg->what, msg->numFields, MMGetFlattenedSize(msg)); + + indent += 2; + { + MMessageField * f = msg->firstField; + while(f) + { + PrintMMessageFieldToStream(f, file, indent); + f = f->nextField; + } + } +} + +void MMPrintToStream(const MMessage * msg, FILE * optFile) +{ + PrintMMessageToStreamAux(msg, optFile?optFile:stdout, 0); +} + +MBool MMAreMessagesEqual(const MMessage * msg1, const MMessage * msg2) +{ + if (msg1 == msg2) return MTrue; + if ((msg1->what != msg2->what)||(msg1->numFields != msg2->numFields)) return MFalse; + else + { + const MMessageField * f1 = msg1->firstField; + while(f1) + { + uint32 numItems = f1->numItems; + const MMessageField * f2 = LookupMMessageField(msg2, f1->name, f1->typeCode); + if ((f2 == NULL)||(f2->numItems != numItems)||(f2->typeCode != f1->typeCode)) return MFalse; + + if (IsTypeCodeVariableSize(f1->typeCode)) + { + uint32 i; + if (f1->typeCode == B_MESSAGE_TYPE) + { + const MMessage ** msgs1 = (const MMessage **) f1->data; + const MMessage ** msgs2 = (const MMessage **) f2->data; + for (i=0; idata; + const MByteBuffer ** bufs2 = (const MByteBuffer **) f2->data; + for (i=0; idata, f2->data, numItems*f1->itemSize) != 0) return MFalse; + + f1 = f1->nextField; + } + } + return MTrue; +} + +#ifdef cplusplus +}; +#endif diff --git a/minimessage/MiniMessage.h b/minimessage/MiniMessage.h new file mode 100644 index 00000000..dd84a2c5 --- /dev/null +++ b/minimessage/MiniMessage.h @@ -0,0 +1,585 @@ +#ifndef MiniMessage_h +#define MiniMessage_h + +#include "support/MuscleSupport.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup minimessage The MiniMessage C function API + * These functions are all defined in MiniMessage(.c,.h), and are stand-alone + * C functions that provide a way for C programs to use MUSCLE Messages. + * This is a minimalist implentation and not so easy to use as the C++ Message + * class, but on the plus side it is much more space-efficient, compiling + * to just over 80KB of code. + * @{ + */ + +/* My own little boolean type, since C doesn't come with one built in. */ +typedef char MBool; +enum {MFalse = 0, MTrue}; /* and boolean values to go in it */ + +/* This file contains a C API for a "minimalist" implementation of the MUSCLE */ +/* Message dictionary object. This implementation sacrifices a certain amount */ +/* of flexibility and convenience in exchange for a very lightweight */ +/* and efficient implementation. */ + +/** Definition of our Point class -- two floats */ +typedef struct _MPoint { + float x; /**< horizontal axis co-ordinate */ + float y; /**< vertical axis co-ordinate */ +} MPoint; + +/** Definition of our Rect class -- four floats */ +typedef struct _MRect { + float left; /**< left edge of the rectangle */ + float top; /**< top edge of the rectangle */ + float right; /**< right edge of the rectangle */ + float bottom; /**< bottom edge of the rectangle */ +} MRect; + +/** Definition of our opaque handle to a MMessage object. Your + * code doesn't know what a (MMessage *) points to, and it doesn't care, + * because all operations on it should happen via calls to the functions + * that are defined below. + */ +struct _MMessage; +typedef struct _MMessage MMessage; + +/** This object is used in field name iterations */ +typedef struct _MMessageIterator { + const MMessage * message; /**< Message whose fields we are currently iterating over */ + void * iterState; /**< Internal implementation detail, please ignore */ + uint32 typeCode; /**< type code we are looking for, or B_ANY_TYPE if any type is okay */ +} MMessageIterator; + +/** Definition of our byte-array class, including size value */ +typedef struct _MByteBuffer { + uint32 numBytes; /**< Number of bytes allocated in this struct */ + uint8 bytes; /**< Note that this is only the first byte; there are usually more after this one */ +} MByteBuffer; + +/** Allocates and initializes a new MByteBuffer with the specified number of bytes, and returns a pointer to it. + * @param numBytes How many bytes to allocate in this buffer. + * @param clearBytes If MTrue, all the data bytes in the returned MByteBuffer will be zero. + * If MFalse, the bytes' values will be undefined (which is a bit more efficient) + * @returns a newly allocated MByteBuffer with the specified number of bytes, or NULL on failure. + * If non-NULL, it becomes the responsibility of the calling code to call FreeMByteBuffer() + * on the MByteBuffer when it is done using it. + */ +MByteBuffer * MBAllocByteBuffer(uint32 numBytes, MBool clearBytes); + +/** Allocates and initializes a new MByteBuffer to contain a copy of the specified NUL-terminated string. + * @param sourceString The string to copy into the newly allocated byte buffer. + * @returns A newly allocated MByteBuffer containing a copy of the specified string, or NULL on failure. + * Note that the returned MByteBuffer's string will be NUL-terminated too. + */ +MByteBuffer * MBStrdupByteBuffer(const char * sourceString); + +/** Attempts to create and return a cloned copy of (cloneMe). + * @param cloneMe The MByteBuffer to create a copy of. + * @returns The newly allocated MByteBuffer, or NULL on failure. + */ +MByteBuffer * MBCloneByteBuffer(const MByteBuffer * cloneMe); + +/** Returns MTrue iff the two byte buffers are equal (i.e. both hold the same byte sequence) + * @param buf1 The first byte buffer to compare. + * @param buf2 The first byte buffer to compare. + * @returns True if buf1 has the same byte sequence as buf2, otherwise MFalse. + */ +MBool MBAreByteBuffersEqual(const MByteBuffer * buf1, const MByteBuffer * buf2); + +/** Frees a previously created MByteBuffer and all the data that it holds. + * @param msg The MByteBuffer to free. If NULL, no action will be taken. + */ +void MBFreeByteBuffer(MByteBuffer * msg); + +/** Allocates and initializes a new MMessage with the specified what code, and returns a pointer to it. + * @param what Initial 'what' code to give to the MMessage. + * @returns a newly allocated MMessage, or NULL on failure. If non-NULL, it becomes the + * the responsibility of the calling code to call MMFreeMessage() on the MMessage + * when it is done using it. + */ +MMessage * MMAllocMessage(uint32 what); + +/** Attempts to create and return a cloned copy of (cloneMe). + * @param cloneMe The MMessage to create a copy of. + * @returns The newly allocated MMessage, or NULL on failure. + */ +MMessage * MMCloneMessage(const MMessage * cloneMe); + +/** Frees a previously created MMessage and all the data that it holds. + * @param msg The MMessage to free. If NULL, no action will be taken. + */ +void MMFreeMessage(MMessage * msg); + +/** Returns the 'what' code associated with the specified MMessage. + * @param msg The MMessage to retrieve the 'what' code of.' + * @returns The MMessage's what code. + */ +uint32 MMGetWhat(const MMessage * msg); + +/** Sets the 'what' code associated with the specified MMessage. + * @param msg The MMessage to set the 'what' code of'. + * @param newWhat The new 'what' code to install. (Typically a B_*_TYPE value) + */ +void MMSetWhat(MMessage * msg, uint32 newWhat); + +/** Removes and frees all of the supplied MMessage's field data. The MMessage itself + * is not destroyed, however (use MMFreeMessage() to do that) + * @param msg The MMessage object to remove all data fields from. + */ +void MMClearMessage(MMessage * msg); + +/** Attempts to remove and free the specified field from the given MMessage. + * @param msg the MMessage object to remove the field from + * @param fieldName Name of the field to remove and free. + * @returns B_NO_ERROR if the field was found and removed, or B_ERROR if it wasn't found. + */ +status_t MMRemoveField(MMessage * msg, const char * fieldName); + +/** Attempts to create and install a string field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a string field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all NULL string values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) MByteBuffer pointers, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + * The MByteBuffer pointers in the array, when non-NULL, are also considered to belong to the + * MMessage, and will have MBFreeByteBuffer() called on them when the field is destroyed. + * NOTE: When setting a value in the array, be sure to allocate its memory using MBAllocByteBuffer() + * or MBStrdupByteBuffer(), and be sure to call MBFreeByteBuffer() on the old value before + * you replace it. + */ +MByteBuffer ** MMPutStringField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a boolean field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a boolean field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all data values set to MFalse. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) booleans, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +MBool * MMPutBoolField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install an int8 field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if an int8 field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) int8s, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +int8 * MMPutInt8Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install an int16 field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if an int16 field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) int16s, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +int16 * MMPutInt16Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install an int32 field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if an int32 field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) int32s, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +int32 * MMPutInt32Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install an int64 field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if an int64 field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) int64s, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +int64 * MMPutInt64Field(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a float field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a float field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) floats, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +float * MMPutFloatField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a double field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a double field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) doubles, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +double * MMPutDoubleField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a Message field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a Message field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all NULL data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) Messages, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + * NOTE: Any MMessages that this array points to are considered to be owned by the MMessage + * as well, for as long as they are pointed to by the array. So when setting the array + * values, be sure to follow pointer semantics, such that any new values are allocated + * off the heap, and be sure to call MMFreeMessage() on any values you replace. + */ +MMessage ** MMPutMessageField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a pointer field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a pointer field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all NULL data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) pointers, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +void ** MMPutPointerField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a point field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a point field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) points, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +MPoint * MMPutPointField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install a rect field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a rect field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all zero data values. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) rects, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + */ +MRect * MMPutRectField(MMessage * msg, MBool retainOldData, const char * fieldName, uint32 numItems); + +/** Attempts to create and install an untyped data field with the specified field name into the MMessage. + * On success, any previously installed field with the same name will be replaced and freed. + * @param msg The MMessage to install the new field into. + * @param retainOldData This flag is relevant only if a data field with the same name already exists. + * If MTrue, as many of the old field's data values as possible will be transferred + * to the new field. Otherwise, all the old field's data will be destroyed and the + * new field will be created with all NULL string values. + * @param typeCode The type code of the data field to place. + * @param fieldName Field name of the new field. + * @param numItems Number of items that the new field should contain room for. + * @returns A pointer to an array of (numItems) MByteBuffer pointers, or NULL if there was an error. + * The returned array belongs to the MMessage, and will be freed by it at the proper time. + * The MByteBuffer pointers in the array, when non-NULL, are also considered to belong to the + * MMessage, and will have MBFreeByteBuffer() called on them when the field is destroyed. + * NOTE: When setting a value in the array, be sure to allocate its memory using MBAllocByteBuffer() + * or MBStrdupByteBuffer(), and be sure to call MBFreeByteBuffer() on the old value before + * you replace it. + */ +MByteBuffer ** MMPutDataField(MMessage * msg, MBool retainOldData, uint32 typeCode, const char * fieldName, uint32 numItems); + +/** Returns the number of bytes it would take to hold a flattened representation of (msg). + * @param msg MMessage to scan to determine its flattened size. + * @returns Number of bytes the flattened representation would require. + */ +uint32 MMGetFlattenedSize(const MMessage * msg); + +/** Flattens the supplied MMessage into a platform-neutral byte buffer that can be sent out over the + * network or saved to disk and later reassembled back into an equivalent MMessage object by calling + * MMUnflattenMessage(). + * @param msg MMessage to produce the byte buffer from. + * @param outBuf Pointer to the output array to write into. There must be at least + * MMGetFlattenedSize(msg) bytes of storage available at this location, + * or memory corruption will result. The output into this buffer will be in + * with the standard MUSCLE flattened byte buffer format. + */ +void MMFlattenMessage(const MMessage * msg, void * outBuf); + +/** Unflattens the supplied byte buffer into the supplied MMessage object. + * @param msg MMessage object to set the state of, based on the contents of the flattened byte buffer. + * @param inBuf Buffer containing the flattened MMessage bytes, as previously created by + * MMFlattenMessage() (or some other code that writes the flattened MUSCLE Message format). + * @param bufSizeBytes How many valid bytes of data are available at (inBuf). + * @returns B_NO_ERROR if the restoration was a success, or B_ERROR otherwise (in which case (msg) will + * likely be left in some valid but only partially restored state) + */ +status_t MMUnflattenMessage(MMessage * msg, const void * inBuf, uint32 bufSizeBytes); + +/** Moves the specified field from one MMessage to another. + * @param sourceMsg The MMessage where the field currently resides. + * @param fieldName Name of the field to move. + * @param destMsg The MMessage to move the field to. If a field with this name already exists + * inside (destMsg), it will be replaced and freed. If (destMsg) is NULL, the + * field will be removed from the source Message and freed. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t MMMoveField(MMessage * sourceMsg, const char * fieldName, MMessage * destMsg); + +/** Copies the specified field from one MMessage to another. + * @param sourceMsg The MMessage where the field currently resides. + * @param fieldName Name of the field to copy. + * @param destMsg The MMessage to copy the field to. If a field with this name already exists + * inside (destMsg), it will be replaced and freed. If (destMsg) is NULL, this + * call will have no effect. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t MMCopyField(const MMessage * sourceMsg, const char * fieldName, MMessage * destMsg); + +/** Change the name of a field within its Message. + * @param sourceMsg The MMessage where the field currently resides. + * @param oldFieldName Current name of the field. + * @param newFieldName Desired new name of the field. If a field with this name already exists + * inside (sourceMsg), it will be replaced and freed. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t MMRenameField(MMessage * sourceMsg, const char * oldFieldName, const char * newFieldName); + +/** Retrieves the string field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of MByteBuffers that represents the strings on success, or NULL on failure. + * Note that the returned array remains under the ownership of the MMessage object. + */ +MByteBuffer ** MMGetStringField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the boolean field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of MBool on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +MBool * MMGetBoolField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the int8 field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of int8s on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +int8 * MMGetInt8Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the int16 field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of int16s on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +int16 * MMGetInt16Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the int32 field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of int32s on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +int32 * MMGetInt32Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the int64 field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of int64 on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +int64 * MMGetInt64Field(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the float field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of floats on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +float * MMGetFloatField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the double field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of doubles on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +double * MMGetDoubleField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the Message field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of Messages on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +MMessage ** MMGetMessageField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the pointer field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of pointers on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +void ** MMGetPointerField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the point field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of points on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +MPoint * MMGetPointField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the rect field with the given name. + * @param msg The MMessage to look for the field in. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of rects on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +MRect * MMGetRectField(const MMessage * msg, const char * fieldName, uint32 * optRetNumItems); + +/** Retrieves the data field with the given name. + * @param msg The MMessage to look for the field in. + * @param typeCode The typecode of the field to look for, or B_ANY_TYPE if you aren't particular about type. + * @param fieldName Name of the field to look for. + * @param optRetNumItems If non-NULL, the number of items in the field will be written into the uint32 this points to. + * @returns A pointer to the array of data items on success, or NULL on failure (field not found). + * Note that the returned array remains under the ownership of the MMessage object. + */ +MByteBuffer ** MMGetDataField(const MMessage * msg, uint32 typeCode, const char * fieldName, uint32 * optRetNumItems); + +/** Returns information about the type and size of the specified field. + * @param msg The MMessage to look for the field in. + * @param fieldName The name of the field to look for. + * @param typeCode The typecode of the field to look for, or B_ANY_TYPE if you aren't particular about type. + * @param optRetNumItems If non-NULL, on success the uint32 this points to will have the number of items in the field written into it. + * @param optRetTypeCode If non-NULL, on success the uint32 this points to will have the type code of the field written into it. + * @returns B_NO_ERROR if the field was found, or B_ERROR if no field with the specified name and type were present. + */ +status_t MMGetFieldInfo(const MMessage * msg, const char * fieldName, uint32 typeCode, uint32 * optRetNumItems, uint32 * optRetTypeCode); + +/** Returns MTrue iff the two MMessage objects are exactly equivalent. (Note that field ordering is not considered) + * @param msg1 First MMessage to compare. Must not be NULL. + * @param msg2 Second MMessage to compare. Must not be NULL. + * @returns MTrue If the two MMessage objects are equal; MFalse otherwise. + */ +MBool MMAreMessagesEqual(const MMessage * msg1, const MMessage * msg2); + +/** Prints the contents of this MMessage to stdout. Useful for debugging. + * @param msg The MMessage to print the contents of to stdout. + * @param optFile If non-NULL, the file to print the output to. If NULL, the output will go to stdout. + */ +void MMPrintToStream(const MMessage * msg, FILE * optFile); + +/** Returns an iterator object that you can use to iterator over the field names of this MMessage. + * @param msg The MMessage object over whose field names you wish to iterate. + * @param typeCode The type of field you are interested in, or B_ANY_TYPE if you aren't particular. + * @returns the iterator object, suitable, for passing in to the MMGetNextFieldName() function. + * @note It is an error to remove or replace fields in a MMessage object while iterating over it. + */ +MMessageIterator MMGetFieldNameIterator(const MMessage * msg, uint32 typeCode); + +/** Returns the next field name in the field name iteration, or NULL if there are no more field names. + * @param iteratorPtr This should point to the iterator object that was returned by a MMGetFieldNameIterator() call. + * @param optRetTypeCode If non-NULL, the uint32 this points to will have the next field's type code written into it. + * @returns The next field name, or NULL if the iteration is complete. + */ +const char * MMGetNextFieldName(MMessageIterator * iteratorPtr, uint32 * optRetTypeCode); + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + +/** A wrapper for malloc() that allows us to track the number of bytes currently allocated. + * Good for catching memory leaks. Only enabled if MUSCLE_ENABLE_MEMORY_TRACKING is defined; otherwise #defined to malloc(). + */ +void * MMalloc(uint32 numBytes); + +/** A wrapper for free() that allows us to track the number of bytes currently allocated. + * Good for catching memory leaks. Only enabled if MUSCLE_ENABLE_MEMORY_TRACKING is defined; otherwise #defined to free(). + */ +void * MFree(void * ptr); + +/** A wrapper for realloc() that allows us to track the number of bytes currently allocated. + * Good for catching memory leaks. Only enabled if MUSCLE_ENABLE_MEMORY_TRACKING is defined; otherwise #defined to realloc(). + */ +void * MRealloc(void * oldBuf, uint32 newSize); + +#else +# define MMalloc malloc +# define MRealloc realloc +# define MFree free +#endif + +/** Returns the current number of allocated bytes. + * Good for catching memory leaks. Only enabled if MUSCLE_ENABLE_MEMORY_TRACKING is defined; otherwise always returns zero. + */ +uint32 MGetNumBytesAllocated(); + +/** @} */ // end of minimessage doxygen group + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/minimessage/MiniMessageGateway.c b/minimessage/MiniMessageGateway.c new file mode 100644 index 00000000..f7774319 --- /dev/null +++ b/minimessage/MiniMessageGateway.c @@ -0,0 +1,213 @@ +#include // for memcpy() +#include "minimessage/MiniMessageGateway.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct _MMessageGateway { + MByteBuffer * _curInput; /* We receive data into this buffer */ + MByteBuffer * _curOutput; /* The head of our output-buffers linked list */ + MByteBuffer * _outputTail; /* The tail of our output-buffers linked list */ + uint32 _curInputPos; /* Index of next byte to receive in _curInput */ + uint32 _maxInputPos; /* Index of last byte to receive in _curInput */ + uint32 _curOutputPos; /* Index of next byte to send in _curOutput */ +}; + +static inline MByteBuffer * GetNextPointer(const MByteBuffer * buf) +{ + return *((MByteBuffer **)(&buf->bytes)); +} + +static inline void SetNextPointer(MByteBuffer * buf, const MByteBuffer * next) +{ + *((const MByteBuffer **)(&buf->bytes)) = next; +} + +MMessageGateway * MGAllocMessageGateway() +{ + MMessageGateway * ret = MMalloc(sizeof(MMessageGateway)); + if (ret) + { + ret->_outputTail = NULL; + ret->_curInputPos = 0; /* input buffer doesn't have a next pointer */ + ret->_curOutputPos = sizeof(MByteBuffer *); /* start sending after the next pointer */ + ret->_maxInputPos = (2*sizeof(uint32)); /* start by reading the fixed-size header fields */ + ret->_curOutput = NULL; + ret->_curInput = MBAllocByteBuffer(ret->_maxInputPos, false); + if (ret->_curInput == NULL) + { + MFree(ret); + ret = NULL; + } + } + return ret; +} + +void MGFreeMessageGateway(MMessageGateway * gw) +{ + if (gw) + { + MBFreeByteBuffer(gw->_curInput); + while(gw->_curOutput) + { + MByteBuffer * next = GetNextPointer(gw->_curOutput); + MBFreeByteBuffer(gw->_curOutput); + gw->_curOutput = next; + } + MFree(gw); + } +} + +static const uint32 _MUSCLE_MESSAGE_ENCODING_DEFAULT = 1164862256; /* 'Enc0' -- vanilla encoding */ + +status_t MGAddOutgoingMessage(MMessageGateway * gw, const MMessage * msg) +{ + uint32 flatSize = MMGetFlattenedSize(msg); + MByteBuffer * buf = MBAllocByteBuffer(sizeof(MMessage *) + (2*sizeof(uint32)) + flatSize, false); + if (buf) + { + uint32 * h = (uint32 *)(((MMessage **)(&buf->bytes))+1); + + SetNextPointer(buf, NULL); + h[0] = B_HOST_TO_LENDIAN_INT32(flatSize); + h[1] = B_HOST_TO_LENDIAN_INT32(_MUSCLE_MESSAGE_ENCODING_DEFAULT); + MMFlattenMessage(msg, (uint8 *) &h[2]); + + if (gw->_outputTail) + { + SetNextPointer(gw->_outputTail, buf); + gw->_outputTail = buf; + } + else gw->_curOutput = gw->_outputTail = buf; + + return B_NO_ERROR; + } + else return B_ERROR; +} + +MBool MGHasBytesToOutput(const MMessageGateway * gw) +{ + return (gw->_curOutput != NULL); +} + +int32 MGDoOutput(MMessageGateway * gw, uint32 maxBytes, MGSendFunc sendFunc, void * arg) +{ + int32 totalSent = 0; + while(gw->_curOutput != NULL) + { + int32 bytesSent; + + uint32 bytesToSend = gw->_curOutput->numBytes - gw->_curOutputPos; + if (bytesToSend > maxBytes) bytesToSend = maxBytes; + + bytesSent = sendFunc((&gw->_curOutput->bytes)+(gw->_curOutputPos), bytesToSend, arg); + if (bytesSent < 0) return -1; /* error! */ + else + { + totalSent += bytesSent; + maxBytes -= bytesSent; + gw->_curOutputPos += bytesSent; + if (gw->_curOutputPos == gw->_curOutput->numBytes) + { + MByteBuffer * next = GetNextPointer(gw->_curOutput); + if (next == NULL) gw->_outputTail = NULL; + MBFreeByteBuffer(gw->_curOutput); + gw->_curOutput = next; + gw->_curOutputPos = sizeof(MByteBuffer *); /* send all data after the next-pointer */ + } + } + if (bytesSent < bytesToSend) break; /* short write indicates that the output buffer is full for now */ + } + return totalSent; +} + +int32 MGDoInput(MMessageGateway * gw, uint32 maxBytes, MGReceiveFunc recvFunc, void * arg, MMessage ** optRetMsg) +{ + int32 totalRecvd = 0; + + if (optRetMsg) *optRetMsg = NULL; + while(true) + { + int32 bytesReceived; + uint32 bytesToRecv = gw->_maxInputPos - gw->_curInputPos; + if (bytesToRecv > maxBytes) bytesToRecv = maxBytes; + + bytesReceived = recvFunc((&gw->_curInput->bytes)+gw->_curInputPos, bytesToRecv, arg); + if (bytesReceived < 0) return -1; /* error */ + else + { + totalRecvd += bytesReceived; + maxBytes -= bytesReceived; + gw->_curInputPos += bytesReceived; + if (gw->_curInputPos == gw->_maxInputPos) + { + if (gw->_curInputPos > (2*sizeof(uint32))) + { + /* We have received the Message body and we are ready to unflatten! */ + MMessage * msg = MMAllocMessage(0); + if (msg == NULL) return -1; /* out of memory */ + + if (MMUnflattenMessage(msg, (&gw->_curInput->bytes)+(2*sizeof(uint32)), gw->_maxInputPos-(2*sizeof(uint32))) == B_NO_ERROR) + { + /* set parser up for the next go-round */ + gw->_curInputPos = 0; + gw->_maxInputPos = 2*(sizeof(uint32)); + + /* For input buffers over 64KB, it's better to reclaim the extra space than avoid reallocating */ + if (gw->_curInput->numBytes > 64*1024) + { + MByteBuffer * smallBuf = MBAllocByteBuffer(64*1024, false); + if (smallBuf == NULL) + { + MMFreeMessage(msg); + return -1; + } + + MBFreeByteBuffer(gw->_curInput); + gw->_curInput = smallBuf; + } + + if (optRetMsg) + { + *optRetMsg = msg; + break; /* If we have a Message for the user, return it to him now */ + } + else MMFreeMessage(msg); /* User doesn't want it, so just throw it away */ + } + else + { + MMFreeMessage(msg); + return -1; /* parse error! */ + } + } + else + { + /* We have our fixed-size headers, now prepare to receive the actual Message body! */ + const uint32 * h = (const uint32 *)(&gw->_curInput->bytes); + uint32 bodySize = B_LENDIAN_TO_HOST_INT32(h[0]); + if ((bodySize == 0)||(B_LENDIAN_TO_HOST_INT32(h[1]) != _MUSCLE_MESSAGE_ENCODING_DEFAULT)) return -1; /* unsupported encoding! */ + + bodySize += (2*sizeof(uint32)); /* we'll include the fixed headers in our body buffer too */ + if (bodySize > gw->_curInput->numBytes) + { + /** Gotta trade up for a larger buffer, so that all our data will fit! */ + MByteBuffer * bigBuf = MBAllocByteBuffer(2*bodySize, false); /* might as well alloc some extra space too */ + if (bigBuf == NULL) return -1; /* out of memory! */ + + memcpy(&bigBuf->bytes, &gw->_curInput->bytes, gw->_curInputPos); /* not strictly necessary, but for form's sake... */ + MBFreeByteBuffer(gw->_curInput); /* dispose of the too-small old input buffer */ + gw->_curInput = bigBuf; + } + gw->_maxInputPos = bodySize; + } + } + } + if (bytesReceived < bytesToRecv) break; /* short read indicates that the input buffer is empty for now */ + } + return totalRecvd; +} + +#ifdef __cplusplus +}; +#endif diff --git a/minimessage/MiniMessageGateway.h b/minimessage/MiniMessageGateway.h new file mode 100644 index 00000000..d9ca3bf7 --- /dev/null +++ b/minimessage/MiniMessageGateway.h @@ -0,0 +1,97 @@ +#ifndef MiniMessageGateway_h +#define MiniMessageGateway_h + +#include "minimessage/MiniMessage.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup minimessagegateway The MiniMessageGateway C function API + * These functions are all defined in MiniMessageGateway(.c,.h), and are stand-alone + * C functions that provide functionality similar to that of the C++ + * MessageIOGateway class. + * @{ + */ + +/** Definition of our opaque handle to a MMessageGateway object. Your + * code doesn't know what a (MMessageGateway *) points to, and it doesn't care, + * because all operations on it should happen via calls to the functions + * that are defined below. + */ +struct _MMessageGateway; +typedef struct _MMessageGateway MMessageGateway; + +/** Typedef for a callback function that knows how to read data from a buffer and send it + * out to (a file, the network, a serial line, wherever). + * @param buf The buffer to read bytes from. + * @param numBytes The number of bytes available for reading at (buf) + * @param arg This is a user-specified value; it will be the same as the value passed in to MMDoOutput(). + * @returns The number of bytes actually read from (buf), or a negative value if there was a critical error (e.g. disconnected socket). + */ +typedef int32 (*MGSendFunc)(const uint8 * buf, uint32 numBytes, void * arg); + +/** Typedef for a callback function that knows how to read data from + * (a file, the network, a serial line, wherever) and write it into a supplied buffer. + * @param buf The buffer to write bytes to. + * @param numBytes The number of bytes available for writing at (buf) + * @param arg This is a user-specified value; it will be the same as the value passed in to MMDoInput(). + * @returns The number of bytes actually written into (buf), or a negative value if there was a critical error (e.g. disconnected socket). + */ +typedef int32 (*MGReceiveFunc)(uint8 * buf, uint32 numBytes, void * arg); + +/** Allocates and initializes a new MMessageGateway. + * @returns a newly allocated MMessageGateway, or NULL on failure. If non-NULL, it becomes the + * the responsibility of the calling code to call MMFreeMessageGateway() on the + * MMessageGateway when it is done using it. + */ +MMessageGateway * MGAllocMessageGateway(); + +/** Frees a previously created MMessageGateway and all the data that it holds. + * @param gw The MMessageGateway to free. If NULL, no action will be taken. + */ +void MGFreeMessageGateway(MMessageGateway * gw); + +/** Flattens the given MMessage into bytes and adds the result to our queue of outgoing data. + * @param gw The Gateway to add the message data to. + * @param msg The MMessage object to flatten. Note that the gateway DOES NOT assume ownership of this MMessage! + * You are still responsible for freeing it, and may do so immediately on return of this function, if you wish. + * @returns B_NO_ERROR on success, or B_ERROR on error (out of memory?) + */ +status_t MGAddOutgoingMessage(MMessageGateway * gw, const MMessage * msg); + +/** Returns MTrue iff the given gateway has any output bytes queued up, that it wants to send. + * @param gw The Gateway to query. + * @returns MTrue if there are bytes queued up to send, or MFalse otherwise. + */ +MBool MGHasBytesToOutput(const MMessageGateway * gw); + +/** Writes out as many queued bytes as possible (up to maxBytes). + * @param gw The Gateway that should do the outputting. + * @param maxBytes The maximum number of bytes that should be sent by this function call. Pass in ~0 to write without limit. + * @param sendFunc The function that the gateway way will call to actually do the write operation. + * @param arg The argument to pass to the write function. + * @returns The number of bytes written, or a negative number if there was an error. + */ +int32 MGDoOutput(MMessageGateway * gw, uint32 maxBytes, MGSendFunc sendFunc, void * arg); + +/** Reads in as many queued bytes as possible (up to maxBytes, or until a full MMessage is read). + * @param gw The Gateway that should do the inputting. + * @param maxBytes The maximum number of bytes that should be read by this function call. Pass in ~0 to read without limit. + * @param recvFunc The function that the gateway way will call to actually do the read operation. + * @param arg The argument to pass to the read function. + * @param optRetMsg If non-NULL, the pointer this argument points to will be set to a returned MMessage object + * if there is one ready, or NULL otherwise. NOTE: If the pointer is set non-NULL, it becomes + * the calling code's responsibility to call MMFreeMessage() on the pointer when you are done with it! + * Failure to do so will result in a memory leak. + * @returns The number of bytes read, or a negative number if there was an error. + */ +int32 MGDoInput(MMessageGateway * gw, uint32 maxBytes, MGReceiveFunc recvFunc, void * arg, MMessage ** optRetMsg); + +/** @} */ // end of minimessagegateway doxygen group + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/minimessage/README.TXT b/minimessage/README.TXT new file mode 100644 index 00000000..cf98ce86 --- /dev/null +++ b/minimessage/README.TXT @@ -0,0 +1,62 @@ + +This folder contains an alpha C implementation of the MUSCLE Message +and MessageGateway APIs. + +If you want to avoid C++ in your program (usually because you want +to minimize executable size, or because a C++ compiler is not available) +you can use these APIs to implement simple MUSCLE Message functionality +instead. + +These files do not depend on any other files outside of this folder, +except for support/MuscleSupport.h. + +The MiniMessage.{c,h} files contain the MiniMessage API -- the +equivalent to the Message class in C++. There are some significant +differences between the two implementations, however: + +1) MMessages cannot be allocated on the stack -- you must allocate + them on the heap, by calling MMAllocMessage(). When you are done + using the MMessage, you must manually free it by calling MMFreeMessage(), + or you will leak memory. + +2) MMessages do not implement reference counting; they use simple + object-ownership semantics instead. This means that when you + e.g. add a child MMessage to another MMessage, the child MMessage + becomes the property of the parent MMessage and will be freed + when the parent MMessage is freed, etc. + +3) Unlike the Message class, where fields in the Message are dynamically + extensible, the MMessage API requires you to specify the number of + items in a field in advance. For example, to add three int32's to + a C++ Message, you could do this: + + Message msg; + msg.AddInt32("fieldname", 1); + msg.AddInt32("fieldname", 2); + msg.AddInt32("fieldname", 3); + [...] + + + The same code using the MMessage API would look like this: + + MMessage * msg = MMAllocMessage(0); + int32 * field = MMPutInt32Field(msg, false, "fieldname", 3); + field[0] = 1; + field[1] = 2; + field[2] = 3; + [...] + MMFreeMessage(msg); + + It's a bit less flexible, but it makes the implementation much + simpler and more efficient. See the test file tests/testmini.cpp + for example usage of the MMessage API. + +4) The MMessageGateway API doesn't use DataIO objects to interface + with the I/O API -- instead, it uses function callbacks. There + is one function callback for reading data, and another for + writing data, and your pass in these function pointers as part + of your calls to MGDoInput() and MGDoOutput(). See the test + file tests/minireflectclient.c for an example of how this + is used. + + diff --git a/python/PythonUtilityFunctions.cpp b/python/PythonUtilityFunctions.cpp new file mode 100644 index 00000000..f936e0b5 --- /dev/null +++ b/python/PythonUtilityFunctions.cpp @@ -0,0 +1,247 @@ +#ifdef __APPLE__ +# include // gotta do this or it won't compile under OS/X Tiger +#endif + +#include "python/PythonUtilityFunctions.h" +#include "message/Message.h" + +namespace muscle { + +static const char * fname(const String & key, const char * defaultFieldName) {return (key.HasChars()) ? key() : defaultFieldName;} + +static status_t ParsePythonDictionary(PyObject * keywords, Message & msg); // forward declaration +static status_t ParsePythonSequence( PyObject * args, Message & msg); // forward declaration + +enum { + MESSAGE_PYTHON_LIST = 0, + MESSAGE_PYTHON_DICTIONARY, + NUM_PYTHON_MESSAGE_TYPES +}; + +const char * GetDefaultPythonArgFieldName(uint32 type) +{ + switch(type) + { + case B_BOOL_TYPE: case B_INT8_TYPE: case B_INT16_TYPE: case B_INT32_TYPE: return "_argInt32"; + case B_FLOAT_TYPE: case B_DOUBLE_TYPE: return "_argFloat"; + case B_STRING_TYPE: return "_argString"; + case B_POINT_TYPE: return "_argPoint"; + case B_MESSAGE_TYPE: return "_argMessage"; + default: return NULL; + } +} + +status_t AddPyObjectToMessage(const String & optKey, PyObject * pyValue, Message & msg) +{ + status_t ret = B_ERROR; + + if (pyValue == Py_None) return B_NO_ERROR; // None is the same as not specifying the arg + + if (PyInt_Check (pyValue)) ret = msg.AddInt32( fname(optKey, "_argInt32"), PyInt_AS_LONG(pyValue)); + else if (PyLong_Check (pyValue)) ret = msg.AddInt64( fname(optKey, "_argInt64"), PyLong_AsLongLong(pyValue)); + else if (PyFloat_Check (pyValue)) ret = msg.AddFloat( fname(optKey, "_argFloat"), (float)PyFloat_AsDouble(pyValue)); + else if (PyString_Check (pyValue)) ret = msg.AddString(fname(optKey, "_argString"), PyString_AsString(pyValue)); + else if (PyComplex_Check(pyValue)) ret = msg.AddPoint( fname(optKey, "_argPoint"), Point((float)PyComplex_RealAsDouble(pyValue), (float)PyComplex_ImagAsDouble(pyValue))); + else if (PyUnicode_Check(pyValue)) + { + PyObject * utf8 = PyUnicode_AsUTF8String(pyValue); + if (utf8) + { + ret = msg.AddString(fname(optKey, "_argString"), PyString_AsString(utf8)); + Py_DECREF(utf8); + } + else ret = B_ERROR; + } + else if (PyDict_Check(pyValue)) + { + MessageRef subMsg = GetMessageFromPool(); + if ((subMsg())&&(msg.AddMessage(fname(optKey, "_argMessage"), subMsg) == B_NO_ERROR)) ret = ParsePythonDictionary(pyValue, *subMsg()); + } + else if (PySequence_Check(pyValue)) + { + MessageRef subMsg = GetMessageFromPool(); + if ((subMsg())&&(msg.AddMessage(fname(optKey, "_argMessage"), subMsg) == B_NO_ERROR)) ret = ParsePythonSequence(pyValue, *subMsg()); + } + return ret; +} + +static status_t ParsePythonSequence(PyObject * args, Message & msg) +{ + msg.what = MESSAGE_PYTHON_LIST; + int seqLen = PySequence_Fast_GET_SIZE(args); + for (int i=0; iwhat == MESSAGE_PYTHON_LIST) ? PyList_New(0) : PyDict_New(); + if (ret) + { + for (MessageFieldNameIterator iter(*subMsg(), B_ANY_TYPE, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + uint32 j = 0; + while(true) + { + PyObject * sub = ConvertMessageItemToPyObject(*subMsg(), iter.GetFieldName(), j++); + if (sub) + { + if (subMsg()->what == MESSAGE_PYTHON_LIST) + { + if (PyList_Append(ret, sub) != 0) {Py_DECREF(sub);} + } + else + { + if (PyDict_SetItemString(ret, (char *) iter.GetFieldName()(), sub) != 0) {Py_DECREF(sub);} + } + } + else break; + } + } + return ret; + } + } + } + break; + + default: + PyErr_SetString(PyExc_RuntimeError, String("Message contained unsupported datatype (field=[%1] index=%2 type=%3)").Arg(fieldName).Arg(index).Arg(type)()); + setErr = true; + break; + } + if (setErr == false) PyErr_SetString(PyExc_RuntimeError, String("Message item [%1] item not found (field=[%1], index=%2)").Arg(fieldName).Arg(index)()); + } + else PyErr_SetString(PyExc_RuntimeError, String("Field name [%1] not found in Message object").Arg(fieldName())()); + + return NULL; +} + +}; // end namespace muscle + diff --git a/python/PythonUtilityFunctions.h b/python/PythonUtilityFunctions.h new file mode 100644 index 00000000..bbc0aa0b --- /dev/null +++ b/python/PythonUtilityFunctions.h @@ -0,0 +1,66 @@ +#ifndef PythonUtilityFunctions_h +#define PythonUtilityFunctions_h + +// Python.h is part of the Python2.2.1 distribution, in the Include folder. +// If you want to include PythonUtilityFunctions.h in your code, you'll need +// to make sure that python/Include is in your includes-path. +#include "support/MuscleSupport.h" +#include "Python.h" + +namespace muscle { + +/** @defgroup pythonutilityfunctions The PythonUtilityFunctions function API + * These functions are all defined in PythonUtilityFunctions(.cpp,.h), and are stand-alone + * functions that are useful when embedding a Python interpreter into MUSCLE code. + * @{ + */ + +class Message; +class String; + +/** Given the arguments passed by a Python script back to a C callback method, populates the given Message + * with corresponding argument data. + * @note This function is only useful if you are trying to interface Python scripts and MUSCLE C++ code in the same executable. + * @param args The args tuple parameter for the positional arguments in the call. May be NULL. + * @param keywords The keywords dictionary parameter for the keyword arguments in the call. May be NULL. + * @param msg The Message object where the argument data will be written to on success. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ParsePythonArgs(PyObject * args, PyObject * keywords, Message & msg); + +/** Given a Message with the specified field, attempts to return a newly referenced PyObject that + * represents the value at the (index)'th position in the field. + * @note This function is only useful if you are interfacing Python and MUSCLE C++ code together in the same executable. + * @param msg The Message to look into + * @param fieldName The field name to look in + * @param index The index of the item in the field to use (often zero) + * @return A PyObject on success, or NULL (and sets the exception string) on failure. + */ +PyObject * ConvertMessageItemToPyObject(const Message & msg, const String & fieldName, uint32 index); + +/** Adds the given PyObject to the given Message, under the given key. + * Note that this function will not properly handle all possible Python types, but only + * the more straightforward ones, including: Ints (stored as int32s), LongLongs (stored as int64s) + * Floats (stored as floats), Strings (stored as strings), Complex (stored as Points), Unicode + * (stored as strings in UTF8 format), dicts (stored as Messages... note that the only the keys/value + * pairs that use strings as keys will be stored), and lists (note that if the list items are not + * all of the same type, then the list may be stored in a different order) + * @param optKey If the length of this string is greater than zero, then this string will be used + * as the field name under which data is added to the Message. If (optKey) is "", + * then an appropriate default name will be chosen (see GetDefaultPythonArgFieldName()). + * @param pyValue The value to add into the Mesasge. Should not be NULL. + * @param addToMsg The Message to add the data to. + * @return B_NO_ERROR on success, or B_ERROR if the function was unable to add the data to the Message. + */ +status_t AddPyObjectToMessage(const String & optKey, PyObject * pyValue, Message & addToMsg); + +/** Given a standard data type code (e.g. B_STRING_TYPE) returns the default field name that will + * be used in a Message for an arg of that type, if a fieldname wasn't explicitly specified. + */ +const char * GetDefaultPythonArgFieldName(uint32 type); + +/** @} */ // end of pythonutilityfunctions doxygen group + +}; // end namespace muscle + +#endif diff --git a/python/README.TXT b/python/README.TXT new file mode 100644 index 00000000..8d1a01d9 --- /dev/null +++ b/python/README.TXT @@ -0,0 +1,28 @@ +This folder contains some Python code which implements client-side +support for the MUSCLE protocol for the Python language/environment. +File in this folder include: + +message.py - Python implementation of the MUSCLE Message class. + This typed-data container class forms the heart of + the MUSCLE protocol. + +message_transceiver_thread.py - A networking utility class that provides + a separate thread that will send and receive + Message objects over a TCP connection for you. + Using this to connect to a MUSCLE server or + peer is much easier than trying to do it + yourself using select(), etc. + +storage_reflect_constants.py - This file contains some constants that + are meaningful to the default MUSCLE server. + The constants' names and values are the same + as they are for the C++ implementation. + +python_chat.py - A VERY simple Python-based BeShare chat client, to + demonstrate how to use the Python MUSCLE APIs. + + +All public methods in these files are commented, and most files +have well-commented testing stubs at the bottom that demonstrate +how to use the class. + diff --git a/python/message.py b/python/message.py new file mode 100644 index 00000000..2e25f067 --- /dev/null +++ b/python/message.py @@ -0,0 +1,540 @@ +""" +Python implementation of the MUSCLE Message class. +Messages Flatten()'d by this class produce data buffers that are compatible with the MUSCLE standard. +""" + +__author__ = "Jeremy Friesner (jaf@meyersound.com)" +__version__ = "$Revision: 1.18 $" +__date__ = "$Date: 2005/01/01 01:03:21 $" +__copyright__ = "Copyright (c) 2000-2013 Meyer Sound Laboratories Inc" +__license__ = "See enclosed LICENSE.TXT file" + +import struct +import types +import array +import sys +import cStringIO + +# Standard MUSCLE field type-constants +B_ANY_TYPE = 1095653716 # 'ANYT', // wild card +B_BOOL_TYPE = 1112493900 # 'BOOL', +B_DOUBLE_TYPE = 1145195589 # 'DBLE', +B_FLOAT_TYPE = 1179406164 # 'FLOT', +B_INT64_TYPE = 1280069191 # 'LLNG', +B_INT32_TYPE = 1280265799 # 'LONG', +B_INT16_TYPE = 1397248596 # 'SHRT', +B_INT8_TYPE = 1113150533 # 'BYTE', +B_MESSAGE_TYPE = 1297303367 # 'MSGG', +B_POINTER_TYPE = 1347310674 # 'PNTR', +B_POINT_TYPE = 1112559188 # 'BPNT', +B_RECT_TYPE = 1380270932 # 'RECT', +B_STRING_TYPE = 1129534546 # 'CSTR', +B_OBJECT_TYPE = 1330664530 # 'OPTR', // used for flattened objects +B_RAW_TYPE = 1380013908 # 'RAWT', // used for raw byte arrays + +CURRENT_PROTOCOL_VERSION = 1347235888 # 'PM00' -- our magic number + +_dataNeedsSwap = not ord(array.array("i",[1]).tostring()[0]) +_hasStruct64 = (((sys.version_info[0]*10) + sys.version_info[1]) >= 22) +_longIs64Bits = (array.array('l').itemsize > 4) + +def GetHumanReadableTypeString(t): + """Given a B_*_TYPE value, returns a human-readable string showing its bytes""" + ret = "" + for dummy in range(4): + nextByte = t%256 + ret = chr(nextByte) + ret + t = t/256 + return ret + +class Message: + """Python implementation of the MUSCLE Message data-storage class. + + This class can hold various types of data, and be flattened out into a + buffer of bytes that is compatible with the MUSCLE flattened-data standard. + """ + def __init__(self, what=0): + self.what = what + self.__fields = {} + + def Clear(self): + """Removes all fields from this Message and sets the what code to zero""" + self.what = 0 + self.__fields = {} + + def GetFieldNames(self): + """Returns the list of field names currently in this Message""" + return self.__fields.keys() + + def HasFieldName(self, fieldName, fieldTypeCode=B_ANY_TYPE): + """Returns 1 if the given fieldName exists and is of the given type, or 0 otherwise.""" + if self.GetFieldContents(fieldName, fieldTypeCode) != None: + return 1 + else: + return 0 + + def PutFieldContents(self, fieldName, fieldTypeCode, fieldContents): + """Adds (or replaces) a new field into this Message. + + (fieldName) should be a string representing the unique name of the field. + (fieldTypeCode) should be a numeric type code for the field (usually B_*_TYPE). + (fieldContents) should be the field's contents (either an item or a list or array of items) + Returns None. + """ + ctype = type(fieldContents) + if ctype == types.ListType or ctype == array.ArrayType: + self.__fields[fieldName] = (fieldTypeCode, fieldContents) + else: + self.__fields[fieldName] = (fieldTypeCode, [fieldContents]) + + def GetFieldContents(self, fieldName, fieldTypeCode=B_ANY_TYPE, defaultValue=None): + """Returns the contents of an existing field in this message (if any). + + (fieldName) should be a string representing the unique name of the field. + (fieldTypeCode) may specify the only type code we want. If not specified, + the default value, B_ANY_TYPE, means that any field found + under the given field name will be returned, regardless of type. + (defaultValue) is the value that should be returned if the requested data is + not available. Default value of this parameter is None. + Returns the contents of the field, or None if the field doesn't exist or + is of the wrong type. + """ + if self.__fields.has_key(fieldName): + ret = self.__fields[fieldName] + if fieldTypeCode != B_ANY_TYPE and fieldTypeCode != ret[0]: + return defaultValue # oops! typecode mismatch + else: + return ret[1] # return just the data portion + else: + return defaultValue + + def GetFieldItem(self, fieldName, fieldTypeCode=B_ANY_TYPE, index=0): + """Returns the (index)th item of an existing field in this message (if any). + + (fieldName) should be a string representing the unique name of the field. + (fieldTypeCode) may specify the only type code we want. If not specified, + the default value, B_ANY_TYPE, means that any field found + under the given field name will be returned, regardless of type. + (index) specifies which item from the given field's list we want. + Defaults to zero (the first item in the list). + Returns the (index)th item of the given field's contents, or None if + the field doesn't exist or is of the wrong type, or if the index isn't + a valid one for that list. + """ + ret = self.GetFieldContents(fieldName, fieldTypeCode); + if ret != None: + num = len(ret) + if index < -num or index >= num: + ret = None + else: + ret = ret[index] + return ret + + def GetFieldType(self, fieldName): + """Returns the type code of the given field, or None if the field doesn't exist. + + (fieldName) should be a string representing the unique name of the field. + """ + if self.__fields.has_key(fieldName): + return self.__fields[fieldName][0] + else: + return None + + def PrintToStream(self, maxRecurseLevel = 2147483648, linePrefix = ""): + """Dumps our state to stdout. Handy for debugging purposes""" + print self.ToString(maxRecurseLevel, linePrefix) + + def __str__(self): + return self.ToString() + + def ToString(self, maxRecurseLevel = 2147483648, linePrefix = ""): + """Returns our state as a string, up to the specified recursion level.""" + s = "%sMessage: what=%i FlattenedSize=%i\n" % (linePrefix, self.what, self.FlattenedSize()) + for x in self.__fields.keys(): + ft = self.GetFieldType(x) + s = s + "%s [ %s : %s : %s ]\n" % (linePrefix, x, GetHumanReadableTypeString(ft), self.GetFieldContents(x)) + if (ft == B_MESSAGE_TYPE) and (maxRecurseLevel > 0): + subMsgs = self.GetMessages(x) + subPrefix = linePrefix + " " + for sm in subMsgs: + s = s + sm.ToString(maxRecurseLevel-1, subPrefix) + return s + + def FlattenedSize(self): + """Returns the number of bytes that this Message would take up if flattened""" + + # Header is 12 bytes: protocol_version(4) + num_entries(4) + what(4) + ret = 3*4 + + # Now calculate the space for each field + fields = self.GetFieldNames() + for fieldName in fields: + ret += 4+len(fieldName)+1+4+4 # namelen(4), name(n), NUL(1), type(4), fieldBytes(4) + fieldContents = self.GetFieldContents(fieldName) + fieldType = self.GetFieldType(fieldName) + ret += self.GetFieldContentsLength(fieldType, fieldContents) + return ret + + def Unflatten(self, file): + """Reads in the new contents of this Message from the given file object.""" + global _dataNeedsSwap + self.Clear() + protocolVersion, self.what, numFields = struct.unpack("<3L", file.read(3*4)) + if protocolVersion != CURRENT_PROTOCOL_VERSION: + raise IOError, "Bad flattened-Message Protocol version ", protocolVersion + for dummy in range(numFields): + fieldName = file.read(struct.unpack(" 0: + subMessageLength = struct.unpack("> 32)) + elif fieldType == B_MESSAGE_TYPE: + for fieldItem in fieldContents: + file.write(struct.pack(" 0)): # paranoia + toremote.connect(r[0][4]) + else: + raise socket.error + except socket.error, why: + if why[0] in (EINPROGRESS, EALREADY, EWOULDBLOCK): + connectStillInProgress = 1 + else: + raise + if connectStillInProgress == 0: + self.__sendReplyToMainThread(MTT_EVENT_CONNECTED) + + # setup the lists we will select() on in advance, to avoid having to do it every time + inlist = [toremote, self.__threadsocket] + notifyonly = [self.__threadsocket] + notifyaccept = [self.__threadsocket, self.__acceptsocket] + outlist = [toremote] + emptylist = [] + + inHeader, inBody, inBodySize = "", "", 0 + outHeader, outBody = self.__getNextMessageFromMain() + while 1: + try: + waitingForAccept = self.__acceptsocket != None and toremote == None + + if (outHeader != None or outBody != None or connectStillInProgress) and waitingForAccept == 0: + useoutlist = outlist + else: + useoutlist = emptylist + + if connectStillInProgress: + useinlist = notifyonly + elif waitingForAccept: + useinlist = notifyaccept + else: + useinlist = inlist + + inready, outready, exlist = select.select(useinlist, useoutlist, emptylist) + + if waitingForAccept and self.__acceptsocket in inready: + toremote = self.__acceptsocket.accept()[0] + inlist = [toremote, self.__threadsocket] + outlist = [toremote] + self.__sendReplyToMainThread(MTT_EVENT_CONNECTED) + + if toremote in inready: + if inHeader != None: + newbytes = toremote.recv(8-len(inHeader)) + if len(newbytes) == 0: + raise socket.error + inHeader = inHeader + newbytes + if len(inHeader) == 8: # full header size is 2*sizeof(int32) + # Okay, we have the entire inHeader! Now decode it + inBodySize, magic = struct.unpack("<2L", inHeader) + if magic != MUSCLE_MESSAGE_ENCODING_DEFAULT: + raise socket.error + inHeader = None # signal that we are now ready to read the inBody + else: + newbytes = toremote.recv(inBodySize-len(inBody)) + if len(newbytes) == 0: + raise socket.error + inBody = inBody + newbytes + if len(inBody) == inBodySize: + inmsg = message.Message() + inmsg.Unflatten(cStringIO.StringIO(inBody)) + self.__sendReplyToMainThread(inmsg) + inHeader, inBody, inBodySize = "", "", 0 # reset to receive next inHeader + + # Note that we don't need to do anything with the data we read from + # threadsocket; it's enough that it woke us up so we can check the out-queue. + if self.__threadsocket in inready and len(self.__threadsocket.recv(1024)) == 0: + raise self._endSession + + if toremote in outready: + if connectStillInProgress: + toremote.send("") # finalize the connect # everyone else can get by with just this + self.__sendReplyToMainThread(MTT_EVENT_CONNECTED) + connectStillInProgress = 0 + else: + if outHeader != None: + numSent = toremote.send(outHeader) + if numSent > 0: + outHeader = outHeader[numSent:] + if len(outHeader) == 0: + outHeader = None + elif outBody != None: + numSent = toremote.send(outBody) + if numSent > 0: + outBody = outBody[numSent:] + if len(outBody) == 0: + outBody = None + + if outHeader == None and outBody == None: + outHeader, outBody = self.__getNextMessageFromMain() + except socket.error: + if self.__acceptsocket != None: + self.__sendReplyToMainThread(MTT_EVENT_DISCONNECTED) + toremote = None # for accepting sessions, we can disconnect and then go back to waiting for an accept + else: + raise # for connecting sessions, the first disconnection means game over, so rethrow + except socket.error: + self.__sendReplyToMainThread(MTT_EVENT_DISCONNECTED) + except IOError, inst: + if (inst == self._endSession): + pass # this exception just means to end the thread without comment + else: + raise + + if toremote != None: + toremote.close() + + def __sendReplyToMainThread(self, what): + self.__inQ.put(what, 0) + self.NotifyMainThread() + + def __getNextMessageFromMain(self): + try: + nextItem = self.__outQ.get(0) + if nextItem == self._endSession: + raise self._endSession + sio = cStringIO.StringIO() + nextItem.Flatten(sio) + return (struct.pack("<2L", nextItem.FlattenedSize(), MUSCLE_MESSAGE_ENCODING_DEFAULT), sio.getvalue()) + except Queue.Empty: + return None, None # oops, nothing more to send for the moment + + def __getSocketFamily(self): + if (self.__useIPv6 == 1): + return socket.AF_INET6 + else: + return socket.AF_INET + +# ----------------------------------------------------------------------------------------------- +# +# Test stub below: exercises basic sending and receiving of Message objects. +# Easiest way to test this is to open two shell windows, and in the first type: +# python MessageTransceiverThread.py accept 9999 +# and then in the other shell window, type: +# python MessageTransceiverThread.py localhost 9999 +# Then the two programs should connect to each other, and typing anything into +# one window should result in output being printed to the other. Fun! +# +# ----------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + + # A custom subclass to make things simple for us. A "real" program wouldn't do it this way. + class TestMTT(MessageTransceiverThread): + def __init__(self, hostname, port): + MessageTransceiverThread.__init__(self, hostname, port) + + # Overridden to merely dequeue and print out any new incoming Messages or events. + # Note that this isn't usually the best way to handle incoming events in a 'real' + # program, since this method is executed in the internal network thread and not in + # the main thread. In a more complex program, doing stuff here could easily lead + # to race conditions. Therefore, a better-written program would notify the main + # thread, and the main thread would respond by picking up the queued events and + # handle them locally, instead. But this will do, for testing. + # (see pythonchat.py for an example of a program that does things the right way) + def NotifyMainThread(self): + while 1: + nextEvent = self.GetNextIncomingEvent() + if nextEvent == MTT_EVENT_CONNECTED: + print "" + print "TCP Connection has been successfully created!" + elif nextEvent == MTT_EVENT_DISCONNECTED: + print "" + print "TCP Connection failed or was disconnected!" + elif nextEvent == None: + break # If we got here, the Queue is empty, nothing more to show + else: + print "" + print "Received the following Message from the remote peer:" + nextEvent.PrintToStream() + + if len(sys.argv) < 2: + print "Usage: python MessageTransceiverThread.py accept [port=0]" + print " or: python MessageTransceiverThread.py [port=2960]" + sys.exit(5) + + if sys.argv[1] == "accept": + port = 0 # Default -- 0 means we get to pick the port + if len(sys.argv) >= 3: + port = sys.argv[2] + mtt = TestMTT(None, int(port)) + print "Accepting connections on port", mtt.GetPort() + else: + hostname = sys.argv[1] + port = 2960 + if len(sys.argv) >= 3: + port = sys.argv[2] + mtt = TestMTT(hostname, int(port)) + print "Attempting to connect to", hostname, "port", port, "..." + + mtt.start(); # important! Otherwise nothing will happen :^) + while 1: + nextline = sys.stdin.readline().strip() + if nextline == "q": + mtt.Destroy() + print "Bye bye!" + sys.exit() + else: + m = message.Message(666) + m.PutString("special message!", nextline) + mtt.SendOutgoingMessage(m) diff --git a/python/python_chat.py b/python/python_chat.py new file mode 100644 index 00000000..141f6555 --- /dev/null +++ b/python/python_chat.py @@ -0,0 +1,148 @@ +""" +A very simple little BeShare-compatible chat client, written in Python. +""" + +__author__ = "Jeremy Friesner (jaf@meyersound.com)" +__version__ = "$Revision: 1.5 $" +__date__ = "$Date: 2005/01/01 01:03:21 $" +__copyright__ = "Copyright (c) 2000-2013 Meyer Sound Laboratories Inc" +__license__ = "See enclosed LICENSE.TXT file" + +import select +import sys + +import message +import message_transceiver_thread +import storage_reflect_constants + +VERSION_STRING = "1.0" + +# some constants defined by BeShare +NET_CLIENT_NEW_CHAT_TEXT = 2 +NET_CLIENT_PING = 5 +NET_CLIENT_PONG = 6 + +# This method retrieves sessionID, e.g. +# Given "/199.42.1.106/1308/beshare/name", it returns "1308" +def GetSessionID(x): + if x[0] == "/": + x = x[1:] + x = x[x.find("/")+1:] # remove leading slash, hostname and second slash + x = x[:x.find("/")] # remove everything after the session ID + return x + else: + return None + +# our program all runs here +if __name__ == "__main__": + if len(sys.argv) < 2: + print "Usage: python python_chat.py [nick]" + print "Example: python python_chat.py beshare.tycomsystems.com PythonBoy" + sys.exit(5) + + # our settings + servername = sys.argv[1] + port = 2960 + nick = "PythonBoy" + if len(sys.argv) >= 3: + nick = sys.argv[2] + + mtt = None + try: + print "Now attempting to connect to server", servername, "..." + mtt = message_transceiver_thread.MessageTransceiverThread(servername, port) + mtt.start() # important! Gotta remember to start the thread going + notifySocket = mtt.GetNotificationSocket() + users = {} + while 1: + inReady, outReady, exReady = select.select([sys.stdin, notifySocket], [], []) + if notifySocket in inReady: + notifySocket.recv(1024) # just to clear the notify bytes out + while 1: + nextEvent = mtt.GetNextIncomingEvent() + if nextEvent == message_transceiver_thread.MTT_EVENT_CONNECTED: + print "Connection to server established! Logging in as", nick + + # Upload our user name so people know who we are + nameMsg = message.Message() + nameMsg.PutString("name", nick) + nameMsg.PutInt32("port", 0) # BeShare requires this, although we don't use it + nameMsg.PutString("version_name", "PythonChat") + nameMsg.PutString("version_num", VERSION_STRING) + uploadMsg = message.Message(storage_reflect_constants.PR_COMMAND_SETDATA) + uploadMsg.PutMessage("beshare/name", nameMsg) + mtt.SendOutgoingMessage(uploadMsg) + + # and subscribe to get updates regarding who else is on the server + subscribeMsg = message.Message(storage_reflect_constants.PR_COMMAND_SETPARAMETERS) + subscribeMsg.PutBool("SUBSCRIBE:beshare/name", 1) + mtt.SendOutgoingMessage(subscribeMsg) + elif nextEvent == message_transceiver_thread.MTT_EVENT_DISCONNECTED: + print "Connection to server broken, goodbye!" + sys.exit(10) + elif nextEvent == None: + break # no more events to process, go back to sleep now + else: + if nextEvent.what == NET_CLIENT_NEW_CHAT_TEXT: + sessionID = nextEvent.GetString("session") + chatText = nextEvent.GetString("text") + if nextEvent.HasFieldName("private"): + print " ", + if users.has_key(sessionID): + user = users[sessionID] + else: + user = "" + if chatText[0:3] == "/me": + print " " + user + chatText[3:] + else: + print "("+sessionID+") "+user+": "+chatText + elif nextEvent.what == NET_CLIENT_PING: + fromSession = nextEvent.GetString("session") + if fromSession != None: + nextEvent.what = NET_CLIENT_PONG + nextEvent.PutString(storage_reflect_constants.PR_NAME_KEYS, "/*/"+fromSession+"/beshare"); + nextEvent.PutString("session", "blah") # server will set this + nextEvent.PutString("version", "PythonChat v" + VERSION_STRING) + mtt.SendOutgoingMessage(nextEvent) + elif nextEvent.what == storage_reflect_constants.PR_RESULT_DATAITEMS: + # Username/session list updates! Gotta scan it and see what it says + + # First check for any node-removal notices + removedList = nextEvent.GetStrings(storage_reflect_constants.PR_NAME_REMOVED_DATAITEMS) + for nextStr in removedList: + sessionID = GetSessionID(nextStr) + if users.has_key(sessionID): + print "User ("+sessionID+") "+users[sessionID]+" has disconnected." + del users[sessionID] + + # Now check for any node-update notices + keys = nextEvent.GetFieldNames() + for fieldName in keys: + sessionID = GetSessionID(fieldName) + if sessionID != None: + datamsg = nextEvent.GetMessage(fieldName) + if datamsg != None: + username = datamsg.GetString("name") + if username != None: + if users.has_key(sessionID): + print "User ("+sessionID+") (a.k.a. "+users[sessionID]+") is now known as " + username + else: + print "User ("+sessionID+") (a.k.a. "+username+") has connected to the server." + users[sessionID] = username + + if sys.stdin in inReady: + usertyped = sys.stdin.readline().strip() + if usertyped == "/quit": + print "Exiting!" + mtt.Destroy() # important, or we'll hang + sys.exit(0) + if len(usertyped) > 0: + chatMsg = message.Message(NET_CLIENT_NEW_CHAT_TEXT) + chatMsg.PutString(storage_reflect_constants.PR_NAME_KEYS, "/*/*/beshare") + chatMsg.PutString("session", "blah") # server will set this for us + chatMsg.PutString("text", usertyped) + mtt.SendOutgoingMessage(chatMsg) + except: + if mtt != None: + mtt.Destroy() + raise diff --git a/python/storage_reflect_constants.py b/python/storage_reflect_constants.py new file mode 100644 index 00000000..e692aa33 --- /dev/null +++ b/python/storage_reflect_constants.py @@ -0,0 +1,510 @@ +""" +Constants that are used and understood by the standard MUSCLE server. +""" + +__author__ = "Jeremy Friesner (jaf@meyersound.com)" +__version__ = "$Revision: 1.4 $" +__date__ = "$Date: 2005/01/01 01:03:21 $" +__copyright__ = "Copyright (c) 2000-2013 Meyer Sound Laboratories Inc" +__license__ = "See enclosed LICENSE.TXT file" + +# Unused dummy value ('!Pc0'), marks the beginning of the reserved command array +BEGIN_PR_COMMANDS = 558916400 + +# Adds/replaces the given fields in the parameters table +PR_COMMAND_SETPARAMETERS = 558916401 + +# Returns the current parameter set to the client +PR_COMMAND_GETPARAMETERS = 558916402 + +# deletes the parameters specified in PR_NAME_KEYS +PR_COMMAND_REMOVEPARAMETERS = 558916403 + +# Adds/replaces the given message in the data table +PR_COMMAND_SETDATA = 558916404 + +# Retrieves the given message(s) in the data table +PR_COMMAND_GETDATA = 558916405 + +# Removes the gives message(s) from the data table +PR_COMMAND_REMOVEDATA = 558916406 + +# Removes data from outgoing result messages +PR_COMMAND_JETTISONRESULTS = 558916407 + +# Insert nodes underneath a node, as an ordered list +PR_COMMAND_INSERTORDEREDDATA = 558916408 + +# Echo this message back to the sending client +PR_COMMAND_PING = 558916409 + +# Kick matching clients off the server (Requires privilege) +PR_COMMAND_KICK = 558916410 + +# Add ban patterns to the server's ban list (Requires privilege) +PR_COMMAND_ADDBANS = 558916411 + +# Remove ban patterns from the server's ban list (Requires privilege) +PR_COMMAND_REMOVEBANS = 558916412 + +# Submessages under PR_NAME_KEYS are executed in order, as if they came separately +PR_COMMAND_BATCH = 558916413 + +# Server will ignore this message +PR_COMMAND_NOOP = 558916414 + +# Move one or more intries in a node index to a different spot in the index +PR_COMMAND_REORDERDATA = 558916415 + +# Add require patterns to the server's require list (Requires ban privilege) +PR_COMMAND_ADDREQUIRES = 558916416 + +# Remove require patterns from the server's require list (Requires ban privilege) +PR_COMMAND_REMOVEREQUIRES = 558916417 + +# Sets an entire subtree of data from a single Message (Not implemented!) +PR_COMMAND_SETDATATREES = 558916418 + +# Returns an entire subtree of data as a single Message +PR_COMMAND_GETDATATREES = 558916419 + +# Removes matching RESULT_DATATREES Messages from the outgoing queuen +PR_COMMAND_JETTISONDATATREES = 558916420 + +# Reserved for future expansion +PR_COMMAND_RESERVED14 = 558916421 + +# Reserved for future expansion +PR_COMMAND_RESERVED15 = 558916422 + +# Reserved for future expansion +PR_COMMAND_RESERVED16 = 558916423 + +# Reserved for future expansion +PR_COMMAND_RESERVED17 = 558916424 + +# Reserved for future expansion +PR_COMMAND_RESERVED18 = 558916425 + +# Reserved for future expansion +PR_COMMAND_RESERVED19 = 558916426 + +# Reserved for future expansion +PR_COMMAND_RESERVED20 = 558916427 + +# Reserved for future expansion +PR_COMMAND_RESERVED21 = 558916428 + +# Reserved for future expansion +PR_COMMAND_RESERVED22 = 558916429 + +# Reserved for future expansion +PR_COMMAND_RESERVED23 = 558916430 + +# Reserved for future expansion +PR_COMMAND_RESERVED24 = 558916431 + +# Reserved for future expansion +PR_COMMAND_RESERVED25 = 558916432 + +# Dummy value to indicate the end of the reserved command range +END_PR_COMMANDS = 558916433 + +# Marks beginning of range of 'what' codes that may be generated by the StorageReflectSession and sent back to the client +BEGIN_PR_RESULTS = 558920240 + +# Sent to client in response to PR_COMMAND_GETPARAMETERS +PR_RESULT_PARAMETERS = 558920241 + +# Sent to client in response to PR_COMMAND_GETDATA, or subscriptions +PR_RESULT_DATAITEMS = 558920242 + +# Sent to client to tell him that we don't know how to process his request message +PR_RESULT_ERRORUNIMPLEMENTED = 558920243 + +# Notification that an entry has been inserted into an ordered index +PR_RESULT_INDEXUPDATED = 558920244 + +# Response from a PR_COMMAND_PING message +PR_RESULT_PONG = 558920245 + +# Your client isn't allowed to do something it tried to do +PR_RESULT_ERRORACCESSDENIED = 558920246 + +# Reply to a PR_COMMAND_GETDATATREES message +PR_RESULT_DATATREES = 558920247 + +# Reserved for future expansion +PR_RESULT_RESERVED5 = 558920248 + +# Reserved for future expansion +PR_RESULT_RESERVED6 = 558920249 + +# Reserved for future expansion +PR_RESULT_RESERVED7 = 558920250 + +# Reserved for future expansion +PR_RESULT_RESERVED8 = 558920251 + +# Reserved for future expansion +PR_RESULT_RESERVED9 = 558920252 + +# Reserved for future expansion +PR_RESULT_RESERVED10 = 558920253 + +# Reserved for future expansion +PR_RESULT_RESERVED11 = 558920254 + +# Reserved for future expansion +PR_RESULT_RESERVED12 = 558920255 + +# Reserved for future expansion +PR_RESULT_RESERVED13 = 558920256 + +# Reserved for future expansion +PR_RESULT_RESERVED14 = 558920257 + +# Reserved for future expansion +PR_RESULT_RESERVED15 = 558920258 + +# Reserved for future expansion +PR_RESULT_RESERVED16 = 558920259 + +# Reserved for future expansion +PR_RESULT_RESERVED17 = 558920260 + +# Reserved for future expansion +PR_RESULT_RESERVED18 = 558920261 + +# Reserved for future expansion +PR_RESULT_RESERVED19 = 558920262 + +# Reserved for future expansion +PR_RESULT_RESERVED20 = 558920263 + +# Reserved for future expansion +PR_RESULT_RESERVED21 = 558920264 + +# Reserved for future expansion +PR_RESULT_RESERVED22 = 558920265 + +# Reserved for future expansion +PR_RESULT_RESERVED23 = 558920266 + +# Reserved for future expansion +PR_RESULT_RESERVED24 = 558920267 + +# Reserved for future expansion +PR_RESULT_RESERVED25 = 558920268 + +# Reserved for future expansion +END_PR_RESULTS = 558920269 + +# Privilege bit, indicates that the client is allowed to kick other clients off the MUSCLE server +PR_PRIVILEGE_KICK = 0 + +# Privilege bit, indicates that the client is allowed to ban other clients from the MUSCLE server +PR_PRIVILEGE_ADDBANS = 1 + +# Privilege bit, indicates that the client is allowed to unban other clients from the MUSCLE server +PR_PRIVILEGE_REMOVEBANS = 2 + +# Number of defined privilege bits +PR_NUM_PRIVILEGES = 3 + +# Op-code that indicates that an entry was inserted at the given slot index, with the given ID +INDEX_OP_ENTRYINSERTED = 'i' + +# Op-code that indicates that an entry was removed from the given slot index, had the given ID +INDEX_OP_ENTRYREMOVED = 'r' + +# Op-code that indicates that the index was cleared +INDEX_OP_CLEARED = 'c' + +# Field name for key-strings (often node paths or regex expressions) +PR_NAME_KEYS = "!SnKy" + +# Field name for archived QueryFilter objects (for applying predicate-logic to subscriptions, etc) +PR_NAME_FILTERS = "!SnFl" + +# Field name to contains node path strings of removed data items +PR_NAME_REMOVED_DATAITEMS = "!SnRd" + +# Field name (any type): If present in a PR_COMMAND_SETPARAMETERS message, disables inital-value-send from new subscriptions +PR_NAME_SUBSCRIBE_QUIETLY = "!SnQs" + +# Field name (any type): If present in a PR_COMMAND_SETDATA message, subscribers won't be notified about the change. +PR_NAME_SET_QUIETLY = "!SnQ2" + +# Field name (any type): If present in a PR_COMMAND_REMOVEDATA message, subscribers won't be notified about the change. +PR_NAME_REMOVE_QUIETLY = "!SnQ3" + +# Field name (any type): If set as parameter, include ourself in wildcard matches +PR_NAME_REFLECT_TO_SELF = "!Self" + +# If set as parameter, session broadcasts unrecognized Messages to neighbors (set by default) +PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS = "!G2N" + +# If set as parameter, session accepts unrecognized Messages from neighbors and sends them to gateway (set by default) +PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY = "!N2G" + +# Field name (any type): If set as a parameter, disable all subscription update messaging. +PR_NAME_DISABLE_SUBSCRIPTIONS = "!Dsub" + +# Field name of int parameter sets max # of items per PR_RESULT_DATAITEMS message +PR_NAME_MAX_UPDATE_MESSAGE_ITEMS = "!MxUp" + +# Field name of String returned in parameter set contains this session's /host/sessionID path +PR_NAME_SESSION_ROOT = "!Root" + +# Field name for Message: In PR_RESULT_ERROR_* messages, holds the client's message that failed to execute. +PR_NAME_REJECTED_MESSAGE = "!Rjct" + +# Field name of int32 bitchord of client's PR_PRIVILEGE_* bits. +PR_NAME_PRIVILEGE_BITS = "!Priv" + +# Field name of int64 indicating how many more bytes are available for MUSCLE server to use +PR_NAME_SERVER_MEM_AVAILABLE = "!Mav" + +# Field name of int64 indicating how many bytes the MUSCLE server currently has allocated +PR_NAME_SERVER_MEM_USED = "!Mus" + +# Field name of int64 indicating how the maximum number of bytes the MUSCLE server may have allocated at once. +PR_NAME_SERVER_MEM_MAX = "!Mmx" + +# Field name of String indicating version of MUSCLE that the server was compiled from +PR_NAME_SERVER_VERSION = "!Msv" + +# Field name of int64 indicating how many microseconds have elapsed since the server was started. +PR_NAME_SERVER_UPTIME = "!Mup" + +# uint64 indicating the server's current wall-clock (microseconds since 1970), in UTC format +PR_NAME_SERVER_CURRENTTIMEUTC = "!Mct" + +# uint64 indicating the server's current wall-clock (microseconds since 1970), in the server's local timezone format +PR_NAME_SERVER_CURRENTTIMELOCAL = "!Mcl" + +# uint64 indicating the server's current run-time clock (microseconds) +PR_NAME_SERVER_RUNTIME = "!Mrt" + +# uint64 that is unique to this particular instance of the server in this particular process +PR_NAME_SERVER_SESSION_ID = "!Ssi" + +# Field name of int32 indicating how many database nodes may be uploaded by this client (total). +PR_NAME_MAX_NODES_PER_SESSION = "!Mns" + +# Field name of a string that the server will replace with the session ID string of your +# session in any outgoing client-to-client messages. +PR_NAME_SESSION = "session" + +# Prefix for parameters that indicate a subscription request +PR_NAME_SUBSCRIBE_PREFIX = "SUBSCRIBE:" + +# Identifier field for associating PR_RESULT_DATATREES replies with PR_COMMAND_GETDATATREE commands +PR_NAME_TREE_REQUEST_ID = "!TRid" + +# Parameter name holding int32 of MUSCLE_MESSAGE_ENCODING_* used to send to client +PR_NAME_REPLY_ENCODING = "!Enc" + +# If present as an int32 in PR_COMMAND_GETDATATREES, returned trees will be clipped to this maximum depth. (0==roots only) +PR_NAME_MAXDEPTH = "!MDep" + +# this field name's submessage is the payload of the current node +# in the message created by StorageReflectSession::SaveNodeTreeToMessage() +PR_NAME_NODEDATA = "data" + +# this field name's submessage represents the children of the current node (recursive) +# in the message created by StorageReflectSession::SaveNodeTreeToMessage() +PR_NAME_NODECHILDREN = "kids" + +# this field name's submessage represents the index of the current node +# in the message created by StorageReflectSession::SaveNodeTreeToMessage() +PR_NAME_NODEINDEX = "index" + +# This is a specialization of AbstractReflectSession that adds several +# useful capabilities to the Reflect Server. Abilities include: +# - Messages can specify (via wildcard path matching) which other +# clients they should be reflected to. If the message doesn't specify +# a path, then the session's default path can be used. +# - Clients can upload and store data on the server (in the server's RAM only). +# This data will remain accessible to all sessions until the client disconnects. +# - Clients can "subscribe" to server state information and be automatically +# notified when the information has changed. +# +# CLIENT-TO-SERVER MESSAGE FORMAT SPECIFICATION: +# +# if 'what' is PR_COMMAND_SETPARAMETERS: +# All fields of the message are placed into the session's parameter set. +# Any fields in the previous parameters set with matching names are replaced. +# Currently parsed parameter names are as follows: +# +# "SUBSCRIBE:" : Any parameter name that begins with the prefix SUBSCRIBE: +# is taken to represent a request to monitor the contents of +# all nodes whose pathnames match the path that follows. So, +# for example, the parameter name +# +# SUBSCRIBE:/*/*/Joe +# +# Indicates a request to watch all nodes with paths that match +# the regular expression /*/*/Joe. The value +# these parameters are unimportant, and are not looked at. +# Thus, these parameters may be of any type. Once a SUBSCRIBE +# parameter has been added, any data nodes that match the specified +# path will be returned immediately to the client in a PR_RESULT_DATAITEMS +# message. Furthermore, any time these nodes are modified or deleted, +# or any time a new node is added that matches the path, another +# PR_RESULT_DATAITEMS message will be sent to notify the client of +# the change. +# +# PR_NAME_KEYS : If set, any non-"special" messages without a +# PR_NAME_KEYS field will be reflected to clients +# who match at least one of the set of key-paths +# listed here. (Should be one or more string values) +# +# PR_NAME_REFLECT_TO_SELF : If set, wildcard matches can match the current session. +# Otherwise, wildcard matches with the current session will +# be suppressed (on the grounds that your client already knows +# the value of anything that it uploaded, and doesn't need to +# be reminded of it). This field may be of any type, only +# its existence/non-existence is relevant. +# +# +# if 'what' is PR_COMMAND_GETPARAMETERS: +# Causes a PR_RESULT_PARAMETERS message to be returned to the client. The returned message +# contains the entire current parameter set. +# +# if 'what' is PR_COMMAND_REMOVEPARAMETERS: +# The session looks for PR_NAME_KEYS string entrys. For each string found +# under this entry, any matching field in the parameters message are deleted. +# Wildcards are permitted in these strings. (So e.g. "*" would remove ALL parameters) +# +# if 'what' is PR_COMMAND_SETDATA: +# Scans the message for all fields of type message. Each message field +# should contain only one message. The field's name is parsed as a local +# key-path of the data item (e.g. "myData", or "imageInfo/colors/red"). +# Each contained message will be stored in the local session's data tree under +# that key path. (Note: fields that start with a '/' are not allowed, and +# will be ignored!) +# +# if 'what' is PR_COMMAND_REMOVEDATA: +# Removes all data nodes that match the path(s) in the PR_NAME_KEYS string field. +# Paths should be specified relative to this session's root node (i.e. they should +# not start with a slash) +# +# if 'what' is PR_COMMAND_GETDATA: +# The session looks for one or more strings in the PR_NAME_KEYS field. Each +# string represents a key-path indicating which information the client is +# interested in retrieving. If there is no leading slash, "/*/*/" will be +# implicitely prepended. Here are some valid example key-paths: +# /*/*/color (gets "color" from all hostnames, all session IDs) +# /joe.blow.com/*/shape (gets "shape" from all sessions connected from joe.blow.com) +# /joe.blow.com/19435935093/sound (gets "sound" from a single session) +# /*/*/vehicleTypes/* (gets all vehicle types from all clients) +# j* (equivalent to "/*/*/j*") +# shape/* (equivalent to "/*/*/shape/*") +# The union of all the sets of matching data nodes specified by these paths will be +# added to a single PR_RESULT_DATAITEMS message which is then passed back to the client. +# Each matching message is added with its full path as a field name. +# +# if 'what' is PR_COMMAND_INSERTORDEREDDATA: +# The session looks for one or more messages in the PR_NAME_KEYS field. Each +# string represents a wildpath, rooted at this session's node (read: no leadin +# slash should be present) that specifies zero or more data nodes to insert ordered/ +# indexed children under. Each node in the union of these node sets will have new +# ordered/indexed child nodes created underneath it. The names of these new child +# nodes will be chosen algorithmically by the server. There will be one child node +# created for each sub-message in this message. Sub-messages may be added under any +# field name if the field name happens to be the name of a currently indexed child, +# the new message node will be be inserted *before* the specified child in the index. +# Otherwise, it will be appended to the end of the index. Clients who have subscribed +# to the specified nodes will see the updates to the index clients who have subscribed +# to the children will get updates of the actual data as well. +# +# if 'what' is PR_COMMAND_PING: +# The session will change the message's 'what' code to PR_RESULT_PONG, and send +# it right back to the client. In this way, the client can find out (a) that +# the server is still alive, (b) how long it takes the server to respond, and +# (c) that any previously sent operations have finished. +# +# if 'what' is PR_COMMAND_KICK: +# The server will look for one or more strings in the PR_NAME_KEYS field. It will +# do a search of the database for any nodes matching one or more of the node paths +# specified by these strings, and will kick any session with matching nodes off +# of the server. Of course, this will only be done if the client who sent the +# PR_COMMAND_KICK field has PR_PRIVILEGE_KICK access. +# +# if 'what' is PR_COMMAND_ADDBANS: +# The server will look for one or more strings in the PR_NAME_KEYS field. Any +# strings that are found will be added to the server's "banned IP list", and +# subsequent connection attempts from IP addresses matching any of these ban strings +# will be denied. Of course, this will only be done if the client who sent the +# PR_COMMAND_ADDBANS field has PR_PRIVILEGE_ADDBANS access. +# +# if 'what' is PR_COMMAND_REMOVEBANS: +# The server will look for one or more strings in the PR_NAME_KEYS field. Any +# strings that are found will be used a pattern-matching strings against the +# current set of "ban" patterns. Any "ban" patterns that are matched by any +# of the PR_NAME_KEYS strings will be removed from the "banned IP patterns" set, +# so that IP addresses who matched those patterns will be able to connect to +# the server again. Of course, this will only be done if the client who sent the +# PR_COMMAND_REMOVEBANS field has PR_PRIVILEGE_REMOVEBANS access. +# +# if 'what' is PR_COMMAND_RESERVED_*: +# The server will change the 'what' code of your message to PR_RESULT_UNIMPLEMENTED, +# and send it back to your client. +# +# if 'what' is PR_RESULT_*: +# The message will be silently dropped. You are not allowed to send PR_RESULT_(*) +# messages to the server, and should be very ashamed of yourself for even thinking +# about it. +# +# All other 'what' codes +# Messages with other 'what' codes are simply reflected to other clients verbatim. +# If a PR_NAME_KEYS string field is found in the message, then it will be parsed as a +# set of key-paths, and only other clients who can match at least one of these key-paths +# will receive the message. If no PR_NAME_KEYS field is found, then the parameter +# named PR_NAME_KEYS will be used as a default value. If that parameter isn't +# found, then the message will be reflected to all clients (except this one). +# +# SERVER-TO-CLIENT MESSAGE FORMAT SPECIFICATION: +# +# if 'what' is PR_RESULT_PARAMETERS: +# The message contains the complete parameter set that is currently associated with +# this client on the server. Parameters may have any field name, and be of any type. +# Certain parameter names are recognized by the StorageReflectSession as having special +# meaning see the documentation on PR_COMMAND_SETPARAMETERS for information about these. +# +# if 'what' is PR_RESULT_DATAITEMS: +# The message contains information about data that is stored on the server. All stored +# data is stored in the form of Messages. Thus, all data in this message will be +# in the form of a message field, with the field's name being the fully qualified path +# of the node it represents (e.g. "/my.computer.com/5/MyNodeName") and the value bein +# the stored data itself. Occasionally it is necessary to inform the client that a data +# node has been deleted this is done by adding the deceased node's path name as a string +# to the PR_NAME_REMOVED_DATAITEM field. If multiple nodes were removed, there may be +# more than one string present in the PR_NAME_REMOVED_DATAITEM field. +# +# if 'what' is PR_RESULT_INDEXUPDATED: +# The message contains information about index entries that were added (via PR_COMMAND_INSERTORDERREDDATA) +# to a node that the client is subscribed to. Each entry's field name is the fully qualified +# path of a subscribed node, and the value(s) are strings of this format: "%c%lu:%s", %c is +# a single character that is one of the INDEX_OP_* values, the %lu is an index the item was added to +# or removed from, and %s is the key string for the child in question. +# Note that there may be more than one value per field! +# +# if 'what' is PR_RESULT_ERRORUNIMPLEMENTED: +# Your message is being returned to you because it tried to use functionality that +# hasn't been implemented on the server side. This usually happens if you are trying +# to use a new MUSCLE feature with an old MUSCLE server. +# +# if 'what' is PR_RESULT_PONG: +# Your PR_COMMAND_PING message has been returned to you as a PR_RESULT_PONG message. +# +# if 'what' is PR_RESULT_ERRORACCESSDENIED: +# You tried to do something that you don't have permission to do (such as kick, ban, +# or unban another user). +# +# if 'what' is anything else: +# This message was reflected to your client by a neighboring client session. The content +# of the message is not specified by the StorageReflectSession it just passes any message +# on verbatim. diff --git a/python/zlib_utility_functions.py b/python/zlib_utility_functions.py new file mode 100644 index 00000000..37e883d7 --- /dev/null +++ b/python/zlib_utility_functions.py @@ -0,0 +1,72 @@ +import zlib +import struct +import cStringIO + +import message + +ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219 # 'zlic' +MUSCLE_ZLIB_FIELD_NAME_STRING = "_zlib" +MUSCLE_ZLIB_HEADER_SIZE = (2*4) + +def DeflateMessage(msg, compressionLevel=6, force=1): + """ Given a Message object, returns an equivalent Message object with all the data compressed. + The returned Messages's what-code will be the same as the original Message. + If the passed-in Message is already compressed, it will be returned unaltered. + @param msg The Message to compress + @param compressionLevel Optional zlib compression-level. May be between 0 and 9; default value is 6. + @param force If set to 0, compression will not occur if the compressed Message turns out to be larger + than the original Message. (instead the original Message will be returned). Defaults to 1. + """ + if (msg.GetFieldItem(MUSCLE_ZLIB_FIELD_NAME_STRING, message.B_RAW_TYPE) == None): + defMsg = message.Message(msg.what) + origFlatSize = msg.FlattenedSize() + headerBuf = cStringIO.StringIO() + headerBuf.write(struct.pack("<2L", ZLIB_CODEC_HEADER_INDEPENDENT, origFlatSize)) + bodyBuf = cStringIO.StringIO() + msg.Flatten(bodyBuf) + cobj = zlib.compressobj(compressionLevel) + cdata = cobj.compress(bodyBuf.getvalue()) + cdata = cdata + cobj.flush(zlib.Z_SYNC_FLUSH) + defMsg.PutFieldContents(MUSCLE_ZLIB_FIELD_NAME_STRING, message.B_RAW_TYPE, headerBuf.getvalue()+cdata) + if (force == 1) or (defMsg.FlattenedSize() < origFlatSize): + return defMsg + return msg + +def InflateMessage(msg): + """ If the passed-in Message object was compressed with DeflateMessage(), this method will + do the inverse operation: It will decompress it and return the original, uncompressed Message. + Otherwise, the passed-in Message will be returned. + @param msg The Message to decompress, if it is compressed. + """ + compStr = msg.GetFieldItem(MUSCLE_ZLIB_FIELD_NAME_STRING, message.B_RAW_TYPE) + if compStr != None and len(compStr) >= MUSCLE_ZLIB_HEADER_SIZE: + header, rawLen = struct.unpack("<2L", compStr[0:MUSCLE_ZLIB_HEADER_SIZE]) + if header == ZLIB_CODEC_HEADER_INDEPENDENT: + infMsg = message.Message() + dobj = zlib.decompressobj() + idata = dobj.decompress(compStr[MUSCLE_ZLIB_HEADER_SIZE:], rawLen) + idata = idata + dobj.flush() + infMsg.Unflatten(cStringIO.StringIO(idata)) + return infMsg + return msg + +# unit test +if __name__ == "__main__": + msg = message.Message(12345) + msg.PutString("A String", "Yes it is") + msg.PutInt32("A Number", 666) + msg.PutFloat("Some Floats", [1.0, 2.2, 3.3, 4.4, 5.5, 6.6]) + msg.PutInt8("Some Ints", [1,1,1,1,1,1,1,1,1,1,1,1,1]) + msg.PutInt8("More Ints", [1,1,1,1,1,1,1,1,1,1,1,1,1]) + msg.PutInt8("Yet More Ints", [1,1,1,1,1,1,1,1,1,1,1,1,1]) + msg.PutInt8("Still More Ints", [1,1,1,1,1,1,1,1,1,1,1,1,1]) + print "---------------------\nOriginal flatSize=", msg.FlattenedSize() + msg.PrintToStream() + + msg = DeflateMessage(msg) + print "---------------------\nDeflated flatSize=", msg.FlattenedSize() + msg.PrintToStream() + + msg = InflateMessage(msg) + print "---------------------\nReinflated flatSize=", msg.FlattenedSize() + msg.PrintToStream() diff --git a/qtsupport/QAcceptSocketsThread.cpp b/qtsupport/QAcceptSocketsThread.cpp new file mode 100644 index 00000000..38357dfe --- /dev/null +++ b/qtsupport/QAcceptSocketsThread.cpp @@ -0,0 +1,66 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "qtsupport/QAcceptSocketsThread.h" + +namespace muscle { + +static const uint32 QMTT_SIGNAL_EVENT = QEvent::User+14836; // why yes, this is a completely arbitrary number + +#if QT_VERSION >= 0x040000 +QAcceptSocketsThread :: QAcceptSocketsThread(QObject * parent, const char * name) : QObject(parent) +{ + if (name) setObjectName(name); +} +#else +QAcceptSocketsThread :: QAcceptSocketsThread(QObject * parent, const char * name) : QObject(parent, name) +{ + // empty +} +#endif + +QAcceptSocketsThread :: ~QAcceptSocketsThread() +{ + ShutdownInternalThread(); // just in case (note this assumes the user isn't going to subclass this class!) +} + +void QAcceptSocketsThread :: SignalOwner() +{ +#if QT_VERSION >= 0x040000 + QEvent * evt = newnothrow QEvent((QEvent::Type)QMTT_SIGNAL_EVENT); +#else + QCustomEvent * evt = newnothrow QCustomEvent(QMTT_SIGNAL_EVENT); +#endif + if (evt) QCoreApplication::postEvent(this, evt); + else WARN_OUT_OF_MEMORY; +} + +bool QAcceptSocketsThread :: event(QEvent * event) +{ + if (event->type() == QMTT_SIGNAL_EVENT) + { + MessageRef next; + + // Check for any new messages from our HTTP thread + while(GetNextReplyFromInternalThread(next) >= 0) + { + switch(next()->what) + { + case AST_EVENT_NEW_SOCKET_ACCEPTED: + { + RefCountableRef tag; + if (next()->FindTag(AST_NAME_SOCKET, tag) == B_NO_ERROR) + { + ConstSocketRef sref(tag, false); + if (sref()) emit ConnectionAccepted(sref); + } + } + break; + } + } + return true; + } + else return QObject::event(event); +} + +}; // end namespace muscle diff --git a/qtsupport/QAcceptSocketsThread.h b/qtsupport/QAcceptSocketsThread.h new file mode 100644 index 00000000..daf65d83 --- /dev/null +++ b/qtsupport/QAcceptSocketsThread.h @@ -0,0 +1,55 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQAcceptSocketsThread_h +#define MuscleQAcceptSocketsThread_h + +#include +#include +#include "system/AcceptSocketsThread.h" + +namespace muscle { + +/** + * This is a Qt-specific subclass of AcceptSocketsThread. + * It will listen on a port, and emit a ConnectionAccepted signal + * whenever a new TCP connection is received on that port. In all + * other respects it works like an AcceptSocketsThread object. + */ +class QAcceptSocketsThread : public QObject, public AcceptSocketsThread +{ + Q_OBJECT + +public: + /** Constructor. + * @param parent Passed on to the QObject constructor + * @param name Passed on to the QObject constructor + */ + QAcceptSocketsThread(QObject * parent = NULL, const char * name = NULL); + + /** + * Destructor. This constructor will call ShutdownInternalThread() itself, + * so you don't need to call ShutdownInternalThread() explicitly UNLESS you + * have subclassed this class and overridden virtual methods that can get + * called from the internal thread -- in that case you should call + * ShutdownInternalThread() yourself to avoid potential race conditions between + * the internal thread and your own destructor method. + */ + virtual ~QAcceptSocketsThread(); + +signals: + /** Emitted when a new TCP connection is accepted + * @param socketRef Reference to the newly accepted socket. + */ + void ConnectionAccepted(const ConstSocketRef & socketRef); + +protected: + /** Overridden to send a QEvent */ + virtual void SignalOwner(); + +private slots: + virtual bool event(QEvent * event); +}; + +}; // end namespace muscle + +#endif diff --git a/qtsupport/QDataIODevice.h b/qtsupport/QDataIODevice.h new file mode 100644 index 00000000..43206753 --- /dev/null +++ b/qtsupport/QDataIODevice.h @@ -0,0 +1,46 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQDataIODevice_h +#define MuscleQDataIODevice_h + +#include +#include +#include "dataio/DataIO.h" + +namespace muscle { + +/** This adapter class allows you to use a MUSCLE DataIO object as a Qt QIODevice. */ +class QDataIODevice : public QIODevice +{ +public: + /** Class constructor. + * @param dataIO a QSocket object that was allocated off the heap. This object becomes owner of newSocket. + * @param parent Passed to the QIODevice constructor + */ + QDataIODevice(const DataIORef & dataIO, QObject * parent) : QIODevice(parent), _dataIO(dataIO), _dataSize(dataIO()->GetLength()), _readReady(dataIO()->GetReadSelectSocket().GetFileDescriptor(), QSocketNotifier::Read), _isHosed(false) + { + connect(&_readReady, SIGNAL(activated(int)), this, SIGNAL(readyRead())); + } + + /** Destructor */ + virtual ~QDataIODevice() {_readReady.setEnabled(false);} + + virtual bool isSequential() const {return (_dataSize < 0);} + virtual qint64 pos() const {return muscleMax((qint64)_dataIO()->GetPosition(), (qint64)0);} + virtual qint64 size() const {return isSequential() ? bytesAvailable() : _dataSize;} + virtual bool atEnd() const {return isSequential() ? _isHosed : ((_isHosed)||(QIODevice::atEnd()));} + + virtual qint64 readData( char * data, qint64 maxSize) {int32 ret = _dataIO()->Read( data, (uint32) muscleMin(maxSize, (qint64)MUSCLE_NO_LIMIT)); if (ret < 0) _isHosed = true; return muscleMax(ret, (int32)0);} + virtual qint64 writeData(const char * data, qint64 maxSize) {int32 ret = _dataIO()->Write(data, (uint32) muscleMin(maxSize, (qint64)MUSCLE_NO_LIMIT)); if (ret < 0) _isHosed = true; return muscleMax(ret, (int32)0);} + +private: + DataIORef _dataIO; + qint64 _dataSize; // will be -1 if the I/O is sequential + + QSocketNotifier _readReady; + bool _isHosed; +}; + +}; // end namespace muscle + +#endif diff --git a/qtsupport/QMessageTransceiverThread.cpp b/qtsupport/QMessageTransceiverThread.cpp new file mode 100644 index 00000000..4804551c --- /dev/null +++ b/qtsupport/QMessageTransceiverThread.cpp @@ -0,0 +1,373 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "qtsupport/QMessageTransceiverThread.h" + +namespace muscle { + +static const uint32 QMTT_SIGNAL_EVENT = QEvent::User+14837; // why yes, this is a completely arbitrary number + +#if QT_VERSION >= 0x040000 +QMessageTransceiverThread :: QMessageTransceiverThread(QObject * parent, const char * name) : QObject(parent), _firstSeenHandler(NULL), _lastSeenHandler(NULL) +{ + if (name) setObjectName(name); +} +#else +QMessageTransceiverThread :: QMessageTransceiverThread(QObject * parent, const char * name) : QObject(parent, name), _firstSeenHandler(NULL), _lastSeenHandler(NULL) +{ + // empty +} +#endif + +QMessageTransceiverThread :: ~QMessageTransceiverThread() +{ + ShutdownInternalThread(); // just in case (note this assumes the user isn't going to subclass this class!) + + // Make sure our handlers don't try to reference us anymore + for (HashtableIterator iter(_handlers); iter.HasData(); iter++) iter.GetValue()->ClearRegistrationFields(); +} + +status_t QMessageTransceiverThread :: SendMessageToSessions(const MessageRef & msgRef, const char * optDistPath) +{ + // This method is reimplemented here so it can be a Qt "slot" method + return MessageTransceiverThread :: SendMessageToSessions(msgRef, optDistPath); +} + +void QMessageTransceiverThread :: SignalOwner() +{ +#if QT_VERSION >= 0x040000 + QEvent * evt = newnothrow QEvent((QEvent::Type)QMTT_SIGNAL_EVENT); +#else + QCustomEvent * evt = newnothrow QCustomEvent(QMTT_SIGNAL_EVENT); +#endif + if (evt) QCoreApplication::postEvent(this, evt); + else WARN_OUT_OF_MEMORY; +} + +bool QMessageTransceiverThread :: event(QEvent * event) +{ + if (event->type() == QMTT_SIGNAL_EVENT) + { + HandleQueuedIncomingEvents(); + return true; + } + else return QObject::event(event); +} + +void QMessageTransceiverThread :: HandleQueuedIncomingEvents() +{ + uint32 code; + MessageRef next; + String sessionID; + uint32 factoryID; + bool seenIncomingMessage = false; + IPAddressAndPort iap; + + // Check for any new messages from our internal thread + while(GetNextEventFromInternalThread(code, &next, &sessionID, &factoryID, &iap) >= 0) + { + switch(code) + { + case MTT_EVENT_INCOMING_MESSAGE: default: + if (seenIncomingMessage == false) + { + seenIncomingMessage = true; + emit BeginMessageBatch(); + } + emit MessageReceived(next, sessionID); + break; + + case MTT_EVENT_SESSION_ACCEPTED: emit SessionAccepted(sessionID, factoryID, iap); break; + case MTT_EVENT_SESSION_ATTACHED: emit SessionAttached(sessionID); break; + case MTT_EVENT_SESSION_CONNECTED: emit SessionConnected(sessionID, iap); break; + case MTT_EVENT_SESSION_DISCONNECTED: emit SessionDisconnected(sessionID); break; + case MTT_EVENT_SESSION_DETACHED: emit SessionDetached(sessionID); break; + case MTT_EVENT_FACTORY_ATTACHED: emit FactoryAttached(factoryID); break; + case MTT_EVENT_FACTORY_DETACHED: emit FactoryDetached(factoryID); break; + case MTT_EVENT_OUTPUT_QUEUES_DRAINED: emit OutputQueuesDrained(next); break; + case MTT_EVENT_SERVER_EXITED: emit ServerExited(); break; + } + emit InternalThreadEvent(code, next, sessionID, factoryID); // these get emitted for any event + + const char * id = _handlers.HasItems() ? strchr(sessionID()+1, '/') : NULL; + if (id) + { + QMessageTransceiverHandler * handler; + if (_handlers.Get(atoi(id+1), handler) == B_NO_ERROR) + { + // If it's not already in the list, prepend it to the list and tell it to emit its BeginMessageBatch() signal + if ((code == MTT_EVENT_INCOMING_MESSAGE)&&(handler != _lastSeenHandler)&&(handler->_nextSeen == NULL)) + { + if (_firstSeenHandler == NULL) _firstSeenHandler = _lastSeenHandler = handler; + else + { + _firstSeenHandler->_prevSeen = handler; + handler->_nextSeen = _firstSeenHandler; + _firstSeenHandler = handler; + } + handler->EmitBeginMessageBatch(); + } + handler->HandleIncomingEvent(code, next, iap); + } + } + } + + FlushSeenHandlers(true); + + if (seenIncomingMessage) emit EndMessageBatch(); +} + +void QMessageTransceiverThread :: RemoveFromSeenList(QMessageTransceiverHandler * h, bool doEmit) +{ + if ((h == _lastSeenHandler)||(h->_nextSeen)) // make sure (h) is actually in the list + { + if (h->_prevSeen) h->_prevSeen->_nextSeen = h->_nextSeen; + else _firstSeenHandler = h->_nextSeen; + if (h->_nextSeen) h->_nextSeen->_prevSeen = h->_prevSeen; + else _lastSeenHandler = h->_prevSeen; + h->_prevSeen = h->_nextSeen = NULL; + if (doEmit) h->EmitEndMessageBatch(); // careful: lots of interesting things could happen inside this call! + } +} + +void QMessageTransceiverThread :: Reset() +{ + FlushSeenHandlers(true); + for (HashtableIterator iter(_handlers); iter.HasData(); iter++) iter.GetValue()->ClearRegistrationFields(); + _handlers.Clear(); + + MessageTransceiverThread::Reset(); +} + +status_t QMessageTransceiverThread :: RegisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sessionRef) +{ + if (this != &thread) return thread.RegisterHandler(thread, handler, sessionRef); // paranoia + else + { + int32 id = sessionRef() ? (int32)sessionRef()->GetSessionID() : -1; + if ((id >= 0)&&(_handlers.Put(id, handler) == B_NO_ERROR)) + { + handler->_master = this; + handler->_mtt = this; + handler->_sessionID = id; + handler->_sessionTargetString = String("/*/") + sessionRef()->GetSessionIDString(); + return B_NO_ERROR; + } + else handler->ClearRegistrationFields(); // paranoia + + return B_ERROR; + } +} + +void QMessageTransceiverThread :: UnregisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, bool emitEndMessageBatchIfNecessary) +{ + if (this != &thread) thread.UnregisterHandler(thread, handler, emitEndMessageBatchIfNecessary); // paranoia + else + { + if (_handlers.Remove(handler->_sessionID) == B_NO_ERROR) + { + // paranoia: in case we are doing this in the middle of our last-seen traversal, we need + // to safely remove (handler) from the traversal so that the traversal doesn't break + if ((emitEndMessageBatchIfNecessary)&&((handler->_nextSeen)||(handler == _lastSeenHandler))) RemoveFromSeenList(handler, emitEndMessageBatchIfNecessary); + + (void) RemoveSessions(handler->_sessionTargetString()); + } + + handler->ClearRegistrationFields(); + } +} + +QMessageTransceiverThreadPool :: QMessageTransceiverThreadPool(uint32 maxSessionsPerThread) : _maxSessionsPerThread(maxSessionsPerThread) +{ + // empty +} + +QMessageTransceiverThreadPool :: ~QMessageTransceiverThreadPool() +{ + ShutdownAllThreads(); +} + +void QMessageTransceiverThreadPool :: ShutdownAllThreads() +{ + for (HashtableIterator iter(_threads); iter.HasData(); iter++) + { + QMessageTransceiverThread * mtt = iter.GetKey(); + mtt->ShutdownInternalThread(); + delete mtt; + } + _threads.Clear(); +} + +QMessageTransceiverThread * QMessageTransceiverThreadPool :: CreateThread() +{ + QMessageTransceiverThread * newThread = newnothrow QMessageTransceiverThread; + if (newThread == NULL) WARN_OUT_OF_MEMORY; + return newThread; +} + +QMessageTransceiverThread * QMessageTransceiverThreadPool :: ObtainThread() +{ + // If any room is available, it will be in the last item in the table + QMessageTransceiverThread * const * lastThread = _threads.GetLastKey(); + if ((lastThread)&&((*lastThread)->GetHandlers().GetNumItems() < _maxSessionsPerThread)) return *lastThread; + + // If we got here, we need to create a new thread + QMessageTransceiverThread * newThread = CreateThread(); + if ((newThread == NULL)||(newThread->StartInternalThread() != B_NO_ERROR)||(_threads.PutWithDefault(newThread) != B_NO_ERROR)) + { + WARN_OUT_OF_MEMORY; + newThread->ShutdownInternalThread(); // in case Put() failed + delete newThread; + return NULL; + } + return newThread; +} + +status_t QMessageTransceiverThreadPool :: RegisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sessionRef) +{ + if (thread.RegisterHandler(thread, handler, sessionRef) == B_NO_ERROR) + { + handler->_master = this; // necessary since QMessageTransceiverThread::RegisterHandler will have overwritten it + if (thread.GetHandlers().GetNumItems() >= _maxSessionsPerThread) (void) _threads.MoveToFront(&thread); + return B_NO_ERROR; + } + return B_ERROR; +} + +void QMessageTransceiverThreadPool :: UnregisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, bool emitEndMessageBatchIfNecessary) +{ + thread.UnregisterHandler(thread, handler, emitEndMessageBatchIfNecessary); + if (thread.GetHandlers().GetNumItems() < _maxSessionsPerThread) (void) _threads.MoveToBack(&thread); +} + +#if QT_VERSION >= 0x040000 +QMessageTransceiverHandler :: QMessageTransceiverHandler(QObject * parent, const char * name) : QObject(parent), _master(NULL), _mtt(NULL), _prevSeen(NULL), _nextSeen(NULL) +{ + if (name) setObjectName(name); +} +#else +QMessageTransceiverHandler :: QMessageTransceiverHandler(QObject * parent, const char * name) : QObject(parent, name), _master(NULL), _mtt(NULL), _prevSeen(NULL), _nextSeen(NULL) +{ + // empty +} +#endif + +QMessageTransceiverHandler :: ~QMessageTransceiverHandler() +{ + (void) Reset(false); // yes, it's virtual... but that's okay, it will just call our implementation +} + +status_t QMessageTransceiverHandler :: SetupAsNewSession(IMessageTransceiverMaster & master, const ConstSocketRef & sock, const ThreadWorkerSessionRef & optSessionRef) +{ + Reset(); + QMessageTransceiverThread * thread = master.ObtainThread(); + if (thread) + { + ThreadWorkerSessionRef sRef = optSessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(*thread); // gotta do this now so we can know its ID + if ((sRef())&&(master.RegisterHandler(*thread, this, sRef) == B_NO_ERROR)) + { + if (thread->AddNewSession(sock, sRef) == B_NO_ERROR) return B_NO_ERROR; + master.UnregisterHandler(*thread, this, true); + } + } + return B_ERROR; +} + +status_t QMessageTransceiverHandler :: SetupAsNewConnectSession(IMessageTransceiverMaster & master, const ip_address & targetIPAddress, uint16 port, const ThreadWorkerSessionRef & optSessionRef, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + Reset(); + QMessageTransceiverThread * thread = master.ObtainThread(); + if (thread) + { + ThreadWorkerSessionRef sRef = optSessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(*thread); // gotta do this now so we can know its ID + if ((sRef())&&(master.RegisterHandler(*thread, this, sRef) == B_NO_ERROR)) + { + if (thread->AddNewConnectSession(targetIPAddress, port, sRef, autoReconnectDelay, maxAsyncConnectPeriod) == B_NO_ERROR) return B_NO_ERROR; + master.UnregisterHandler(*thread, this, true); + } + } + return B_ERROR; +} + +status_t QMessageTransceiverHandler :: SetupAsNewConnectSession(IMessageTransceiverMaster & master, const String & targetHostName, uint16 port, const ThreadWorkerSessionRef & optSessionRef, bool expandLocalhost, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + Reset(); + QMessageTransceiverThread * thread = master.ObtainThread(); + if (thread) + { + ThreadWorkerSessionRef sRef = optSessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(*thread); // gotta do this now so we can know its ID + if ((sRef())&&(master.RegisterHandler(*thread, this, sRef) == B_NO_ERROR)) + { + if (thread->AddNewConnectSession(targetHostName, port, sRef, expandLocalhost, autoReconnectDelay, maxAsyncConnectPeriod) == B_NO_ERROR) return B_NO_ERROR; + master.UnregisterHandler(*thread, this, true); + } + } + return B_ERROR; +} + +status_t QMessageTransceiverHandler :: RequestOutputQueueDrainedNotification(const MessageRef & notificationMsg, DrainTag * optDrainTag) +{ + return _mtt ? _mtt->RequestOutputQueuesDrainedNotification(notificationMsg, _sessionTargetString(), optDrainTag) : B_ERROR; +} + +status_t QMessageTransceiverHandler :: SetNewInputPolicy(const AbstractSessionIOPolicyRef & pref) +{ + return _mtt ? _mtt->SetNewInputPolicy(pref, _sessionTargetString()) : B_ERROR; +} + +status_t QMessageTransceiverHandler :: SetNewOutputPolicy(const AbstractSessionIOPolicyRef & pref) +{ + return _mtt ? _mtt->SetNewOutputPolicy(pref, _sessionTargetString()) : B_ERROR; +} + +status_t QMessageTransceiverHandler :: SetOutgoingMessageEncoding(int32 encoding) +{ + return _mtt ? _mtt->SetOutgoingMessageEncoding(encoding, _sessionTargetString()) : B_ERROR; +} + +status_t QMessageTransceiverHandler :: SendMessageToSession(const MessageRef & msgRef) +{ + return _mtt ? _mtt->SendMessageToSessions(msgRef, _sessionTargetString()) : B_ERROR; +} + +void QMessageTransceiverHandler :: Reset(bool emitEndBatchIfNecessary) +{ + if (_mtt) + { + (void) _mtt->RemoveSessions(_sessionTargetString()); + _master->UnregisterHandler(*_mtt, this, emitEndBatchIfNecessary); + } +} + +void QMessageTransceiverHandler :: HandleIncomingEvent(uint32 code, const MessageRef & next, const IPAddressAndPort & iap) +{ + switch(code) + { + case MTT_EVENT_INCOMING_MESSAGE: emit MessageReceived(next); break; + case MTT_EVENT_SESSION_ATTACHED: emit SessionAttached(); break; + case MTT_EVENT_SESSION_CONNECTED: emit SessionConnected(iap); break; + case MTT_EVENT_SESSION_DISCONNECTED: emit SessionDisconnected(); break; + case MTT_EVENT_SESSION_DETACHED: emit SessionDetached(); break; + case MTT_EVENT_OUTPUT_QUEUES_DRAINED: emit OutputQueueDrained(next); break; + } + emit InternalHandlerEvent(code, next); // these get emitted for any event +} + +void QMessageTransceiverHandler :: ClearRegistrationFields() +{ + _master = NULL; + _mtt = NULL; + _sessionID = -1; + _sessionTargetString.Clear(); + _prevSeen = _nextSeen = NULL; +} + +ThreadWorkerSessionRef QMessageTransceiverHandler :: CreateDefaultWorkerSession(QMessageTransceiverThread & thread) +{ + return thread.CreateDefaultWorkerSession(); +} + +}; // end namespace muscle; diff --git a/qtsupport/QMessageTransceiverThread.h b/qtsupport/QMessageTransceiverThread.h new file mode 100644 index 00000000..0f00e6c1 --- /dev/null +++ b/qtsupport/QMessageTransceiverThread.h @@ -0,0 +1,544 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQMessageTransceiverThread_h +#define MuscleQMessageTransceiverThread_h + +#include "support/MuscleSupport.h" // placed first to avoid ordering problems with MUSCLE_FD_SETSIZE + +#include +#include +#include "system/MessageTransceiverThread.h" + +namespace muscle { + +class QMessageTransceiverHandler; +class QMessageTransceiverThread; +class QMessageTransceiverThreadPool; + +/** + * This is an interface that identifies an object that QMessageTransceiverHandlers + * can attach themselves to. It is implemented by both the QMessageTransceiverThread + * and QMessageTransceiverThreadPool classes. + */ +class IMessageTransceiverMaster +{ +public: + /** Default constructor */ + IMessageTransceiverMaster() {/* empty */} + + /** Destructor */ + virtual ~IMessageTransceiverMaster() {/* empty */} + +protected: + /** Must be implemented to return an available QMessageTransceiverThread object, + * or return NULL on failure. + */ + virtual QMessageTransceiverThread * ObtainThread() = 0; + + /** Must be implemented to attach (handler) to the specified thread. + * @param thread the QMessageTransceiverThread to attach the handler to + * @param handler the QMessageTransceiverHandler to attach to the thread + * @param sessionRef the AbstractReflectSession that will represent the handler inside the thread. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t RegisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sessionRef) = 0; + + /** Must be implemented to detach (handler) from (thread) + * @param thread the QMessageTransceiverThread to detach the handler from + * @param handler the QMessageTransceiverHandler to detach from the thread + * @param emitEndMessageBatchIfNecessary If true, and (handler) is currently in the middle of + * a message-batch, then this method will cause (handler) to emit an + * EndMessageBatch() signal before dissasociating itself + * from this IMessageTransceiverMaster. That way the + * un-registration won't break the rule that says that one + * EndMessageBatch() signal must always be emitted for + * every one BeginMessageBatch() signal. + */ + virtual void UnregisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, bool emitEndMessageBatchIfNecessary) = 0; + +private: + friend class QMessageTransceiverHandler; +}; + +/** + * This is a Qt-specific subclass of MessageTransceiverThread. + * It hooks all the standard MessageTransceiverThread events + * up to Qt signals, so you can just connect() your QMessageTransceiverThread + * to the various slots in your application, instead of having to worry + * about event loops and such. In all other ways it works the + * same as any MessageTransceiverThread object. + */ +class QMessageTransceiverThread : public QObject, public MessageTransceiverThread, public IMessageTransceiverMaster, private CountedObject +{ + Q_OBJECT + +public: + /** Constructor. + * @param parent Passed on to the QObject constructor + * @param name Passed on to the QObject constructor + */ + QMessageTransceiverThread(QObject * parent = NULL, const char * name = NULL); + + /** + * Destructor. This constructor will call ShutdownInternalThread() itself, + * so you don't need to call ShutdownInternalThread() explicitly UNLESS you + * have subclassed this class and overridden virtual methods that can get + * called from the internal thread -- in that case you should call + * ShutdownInternalThread() yourself to avoid potential race conditions between + * the internal thread and your own destructor method. + */ + virtual ~QMessageTransceiverThread(); + + /** Overridden to handle signal events from our internal thread */ + virtual bool event(QEvent * event); + + /** Returns a read-only reference to our table of registered QMessageTransceiverHandler objects. */ + const Hashtable GetHandlers() const {return _handlers;} + +signals: + /** Emitted when MessageReceived() is about to be emitted one or more times. */ + void BeginMessageBatch(); + + /** Emitted when a new Message has been received by one of the sessions being operated by our internal thread. + * @param msg Reference to the Message that was received. + * @param sessionID Session ID string of the session that received the message + */ + void MessageReceived(const MessageRef & msg, const String & sessionID); + + /** Emitted when we are done emitting MessageReceived, for the time being. */ + void EndMessageBatch(); + + /** Emitted when a new Session object is accepted by one of the factories being operated by our internal thread + * @param sessionID Session ID string of the newly accepted Session object. + * @param factoryID Factory ID of the ReflectSessionFactory that accepted the new session. + * @param iap The location of the peer that we are accepting a connection from. + */ + void SessionAccepted(const String & sessionID, uint32 factoryID, const IPAddressAndPort & iap); + + /** Emitted when a session object is attached to the internal thread's ReflectServer */ + void SessionAttached(const String & sessionID); + + /** Emitted when a session object connects to its remote peer (only used by sessions that were + * created using AddNewConnectSession()) + * @param sessionID Session ID string of the newly connected Session object. + * @param connectedTo the IP address and port that the session is connected to. + */ + void SessionConnected(const String & sessionID, const IPAddressAndPort & connectedTo); + + /** Emitted when a session object is disconnected from its remote peer + * @param sessionID Session ID string of the newly disconnected Session object. + */ + void SessionDisconnected(const String & sessionID); + + /** Emitted when a session object is removed from the internal thread's ReflectServer + * @param sessionID Session ID string of the newly disconnected Session object. + */ + void SessionDetached(const String & sessionID); + + /** Emitted when a factory object is attached to the internal thread's ReflectServer. + * @param factoryID Factory ID of the ReflectSessionFactory that accepted the new session. + */ + void FactoryAttached(uint32 factoryID); + + /** Emitted when a factory object is removed from the internal thread's ReflectServer. + * @param factoryID Factory ID of the ReflectSessionFactory that accepted the new session. + */ + void FactoryDetached(uint32 factoryID); + + /** Emitted when the thread's internal ReflectServer object exits. */ + void ServerExited(); + + /** Emitted when the output-queues of the sessions specified in a previous call to + * RequestOutputQueuesDrainedNotification() have drained. Note that this signal only + * gets emitted once per call to RequestOutputQueuesDrainedNotification(); + * it is not emitted spontaneously. + * @param ref MessageRef that you previously specified in RequestOutputQueuesDrainedNotification(). + */ + void OutputQueuesDrained(const MessageRef & ref); + + /** This signal is called for all events send by the internal thread. You can use this + * to catch custom events that don't have their own signal defined above, or if you want to + * receive all thread events via a single slot. + * @param code the MTT_EVENT_* code of the new event. + * @param optMsg If a Message is relevant, this will contain it; else it's a NULL reference. + * @param optFromSession If a session ID is relevant, this is the session ID; else it will be "". + * @param optFromFactory If a factory is relevant, this will be the factory's ID number; else it will be zero. + */ + void InternalThreadEvent(uint32 code, const MessageRef & optMsg, const String & optFromSession, uint32 optFromFactory); + +public slots: + /** + * This method is the same as the MessageTransceiverThread::SendMessageToSessions(); + * it's reimplemented here as a pass-through merely so it can be a slot. + * Enqueues the given message for output by one or more of our attached sessions. + * @param msgRef a reference to the Message to send out. + * @param optDistPath if non-NULL, then only sessions that contain at least one node that matches this + * path will receive the Message. Otherwise all sessions will receive the Message. + * @return B_NO_ERROR on success, B_ERROR if out of memory. + */ + status_t SendMessageToSessions(const MessageRef & msgRef, const char * optDistPath = NULL); + + /** Parses through the incoming-events queue and emits signals as appropriate. + * Typically this method is called when appropriate by the event() method, + * so you don't need to call it yourself unless you are handling event notification + * in some custom fashion. + */ + virtual void HandleQueuedIncomingEvents(); + + /** Overridden to also call Reset() on any QMessageTransceiverHandlers we have registered. */ + virtual void Reset(); + +protected: + /** Overridden to send a QEvent */ + virtual void SignalOwner(); + + virtual QMessageTransceiverThread * ObtainThread() {return this;} + virtual status_t RegisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sessionRef); + virtual void UnregisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, bool emitEndMessageBatchIfNecessary); + +private: + friend class QMessageTransceiverHandler; + friend class QMessageTransceiverThreadPool; + + void FlushSeenHandlers(bool doEmit) {while(_firstSeenHandler) RemoveFromSeenList(_firstSeenHandler, doEmit);} + void RemoveFromSeenList(QMessageTransceiverHandler * h, bool doEmit); + + IMessageTransceiverMaster * RegisterHandler(QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sref); + void UnregisterHandler(IMessageTransceiverMaster * handler); + + Hashtable _handlers; // registered handlers, by ID + + QMessageTransceiverHandler * _firstSeenHandler; + QMessageTransceiverHandler * _lastSeenHandler; +}; + +/** This class represents a demand-allocated pool of QMessageTransceiverThread objects. + * By instantiating a QMessageTransceiverThreadPool object and passing it into your + * QMessageTransceiverHandler::SetupAs*() methods instead of passing in a QMessageTransceiverThread + * directly, you will have a system where threads are demand-allocated as necessary. + * You can specify how many QMessageTransceiverHandler objects may share a single + * QMessageTransceiverThread at once. + */ +class QMessageTransceiverThreadPool : public IMessageTransceiverMaster, private CountedObject +{ +public: + /** + * Constructor. Creates a thread pool where each thread will be allowed to have + * at most (maxSessionsPerThread) sessions associated with it. If more than that + * number of sessions are attached to the pool, another thread will be created to + * handle the extra sessions (and so on). + * @param maxSessionsPerThread The maximum number of sessions to add to each + * thread in the pool. Defaults to 32. + */ + QMessageTransceiverThreadPool(uint32 maxSessionsPerThread = 32); + + /** Destructor. Deletes all QMessageTransceiverThread objects in the pool. + * Any QMessageTransceiverHandlers still attached to those threads will be detached. + */ + virtual ~QMessageTransceiverThreadPool(); + + /** Call this to shut down and delete all QMessageTransceiverThread objects in this pool. + * Any QMessageTransceiverHandlers still attached to those threads will be detached. + */ + void ShutdownAllThreads(); + +protected: + virtual QMessageTransceiverThread * ObtainThread(); + virtual status_t RegisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, const ThreadWorkerSessionRef & sessionRef); + virtual void UnregisterHandler(QMessageTransceiverThread & thread, QMessageTransceiverHandler * handler, bool emitEndMessageBatchIfNecessary); + + /** Should create and return a new QMessageTransceiverThread object. + * Broken out into a virtual method so that subclasses may alter its behavior if necessary. + * Default implementation creates and returns a new QMessageTransceiverThread object. + */ + virtual QMessageTransceiverThread * CreateThread(); + +private: + uint32 _maxSessionsPerThread; + Hashtable _threads; +}; + +/** + * This class can be used in conjunction with the QMessageTransceiverThread class to make + * it easier to manage multiple sessions inside a single I/O thread. You can create multiple + * QMessageTransceiverHandler objects that are associated with the same QMessageTransceiverThread, + * and they will each act similarly to a QMessageTransceiverThread, except that their + * I/O operations will all execute within the single, shared QMessageTransceiverThread context. + * The QMessageTransceiverHandler class handles all the necessary multiplexing/demultiplexing + * of commands so that your code doesn't have to worry about it. This class is useful if you + * are creating lots and lots of network connections and want to limit the number of + * I/O threads you create... however it is still perfectly possible to use the QMessageTransceiverThread + * class directly for everything and ignore this class, if you prefer -- this class is here just as + * an optimization. + */ +class QMessageTransceiverHandler : public QObject, private CountedObject +{ + Q_OBJECT + +public: + /** Constructor. After creating this object, you'll want to call one of the SetupAs*() methods + * on this object to associate it with a QMessageTransceiverThread object and assign it a role. + * @param parent Passed on to the QObject constructor + * @param name Passed on to the QObject constructor + */ + QMessageTransceiverHandler(QObject * parent = NULL, const char * name = NULL); + + /** + * Destructor. This destructor will unregister us with our QMessageTransceiverThread object. + */ + virtual ~QMessageTransceiverHandler(); + + /** + * Associates this handler with a specified IMessageTransceiverMaster, and tells it to use + * the specified socket for its I/O. If the handler was already associated with a IMessageTransceiverMaster, + * the previous association will be removed first. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is immediately passed on through to ReflectServer::AddNewSession(). + * @param master the IMessageTransceiverMaster to bind this handler to. + * @param socket The TCP socket that the new session will be using, or -1, if the new session is to have no + * associated TCP connection. This socket becomes property of this object on success. + * @param optSessionRef Optional reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify a session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or else things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t SetupAsNewSession(IMessageTransceiverMaster & master, const ConstSocketRef & socket, const ThreadWorkerSessionRef & optSessionRef); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t SetupAsNewSession(IMessageTransceiverMaster & master, const ConstSocketRef & socket) {return SetupAsNewSession(master, socket, ThreadWorkerSessionRef());} + + /** + * Associates this handler with a specified IMessageTransceiverMaster, and tells it to connect to + * the specified host IP address and port. If the handler was already associated with a IMessageTransceiverMaster, + * the previous association will be removed first. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is immediately passed on through to ReflectServer::AddNewConnectSession(). + * @param master the IMessageTransceiverMaster to bind this handler to. + * @param targetIPAddress IP address to connect to + * @param port Port to connect to at that IP address. + * @param optSessionRef optional Reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify a session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (optSessionRef). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t SetupAsNewConnectSession(IMessageTransceiverMaster & master, const ip_address & targetIPAddress, uint16 port, const ThreadWorkerSessionRef & optSessionRef, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t SetupAsNewConnectSession(IMessageTransceiverMaster & master, const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) {return SetupAsNewConnectSession(master, targetIPAddress, port, ThreadWorkerSessionRef(), autoReconnectDelay, maxAsyncConnectPeriod);} + + /** + * Associates this handler with a specified IMessageTransceiverMaster, and tells it to connect to + * the specified host name and port. If the handler was already associated with a IMessageTransceiverMaster, + * the previous association will be removed first. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is passed immediately on through to ReflectServer::AddNewConnectSession(). + * @param master the IMessageTransceiverMaster to bind this handler to. + * @param targetHostName ASCII hostname or ASCII IP address to connect to. (e.g. "blah.com" or "132.239.50.8") + * @param port Port to connect to at that IP address. + * @param optSessionRef optional Reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @param expandLocalhost Passed to GetHostByName(). See GetHostByName() documentation for details. Defaults to false. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (optSessionRef). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t SetupAsNewConnectSession(IMessageTransceiverMaster & master, const String & targetHostName, uint16 port, const ThreadWorkerSessionRef & optSessionRef, bool expandLocalhost = false, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t SetupAsNewConnectSession(IMessageTransceiverMaster & master, const String & targetHostName, uint16 port, bool expandLocalhost = false, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) {return SetupAsNewConnectSession(master, targetHostName, port, ThreadWorkerSessionRef(), expandLocalhost, autoReconnectDelay, maxAsyncConnectPeriod);} + + /** + * This method is that Requests that the MessageTranceiverThread object send us a MTT_EVENT_OUTPUT_QUEUES_DRAINED event + * when this handler's outgoing message queues has become empty. + * @param notificationMsg MessageRef to return with the MTT_EVENT_OUTPUT_QUEUES_DRAINED event. May be a NULL ref. + * @param optDrainTag If non-NULL, this DrainTag will be used to track drainage, instead of a + * default one. Don't supply a value for this argument unless you think you + * know what you are doing. ;^) On success, (optDrainTag) becomes property + * of this MessageTransceiverThread. + * @returns B_NO_ERROR on success (in which case an MTT_EVENT_OUTPUT_QUEUES_DRAINED event will be + * forthcoming) or B_ERROR on error (out of memory). + */ + status_t RequestOutputQueueDrainedNotification(const MessageRef & notificationMsg, DrainTag * optDrainTag = NULL); + + /** + * Tells this handler's worker session to install a new input IOPolicy. + * @param pref Reference to the new IOPolicy object. Since IOPolicies are generally + * not thread safe, the referenced IOPolicy should not be used after it has + * been successfully passed in via this call. May be a NULL ref to remove + * the existing input policy. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetNewInputPolicy(const AbstractSessionIOPolicyRef & pref); + + /** + * Tells this handler's worker session to install a new output IOPolicy. + * @param pref Reference to the new IOPolicy object. Since IOPolicies are generally + * not thread safe, the referenced IOPolicy should not be used after it has + * been successfully passed in via this call. May be a NULL ref to remove + * the existing output policy. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetNewOutputPolicy(const AbstractSessionIOPolicyRef & pref); + + /** + * Tells this handler's worker session to switch to a different message encoding + * for the Messages they are sending to the network. Note that this only works if + * the worker session is using the usual MessageIOGateways for their I/O. + * Note that ZLIB encoding is only enabled if your program is compiled with the + * -DMUSCLE_ENABLE_ZLIB_ENCODING compiler flag set. + * @param encoding one of the MUSCLE_MESSAGE_ENCODING_* constant declared in MessageIOGateway.h + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetOutgoingMessageEncoding(int32 encoding); + + /** Returns this handler's current session ID, or -1 if this handler is not currently associated with a thread. */ + int32 GetSessionID() const {return _sessionID;} + + /** Returns a pointer to this handler's associated QMessageTransceiverThread, or NULL if the handler + * is not currently associated with any thread. + */ + QMessageTransceiverThread * GetThread() {return _mtt;} + + /** Returns a pointer to this handler's associated QMessageTransceiverThread, or NULL if the handler + * is not currently associated with any thread. + */ + const QMessageTransceiverThread * GetThread() const {return _mtt;} + + /** Returns a pointer to this handler's associated IMessageTransceiverMaster, or NULL if the handler + * is not currently associated with any master. + */ + IMessageTransceiverMaster * GetMaster() {return _master;} + + /** Returns a pointer to this handler's associated IMessageTransceiverMaster, or NULL if the handler + * is not currently associated with any master. + */ + const IMessageTransceiverMaster * GetMaster() const {return _master;} + +signals: + /** Emitted when MessageReceived() is about to be emitted one or more times by this handler. */ + void BeginMessageBatch(); + + /** Emitted when a new Message has been received by this handler. + * @param msg Reference to the Message that was received. + */ + void MessageReceived(const MessageRef & msg); + + /** Emitted when this handler is done emitting MessageReceived(), for the time being. */ + void EndMessageBatch(); + + /** Emitted when this handler's session object has attached to the I/O thread's ReflectServer */ + void SessionAttached(); + + /** Emitted when this handler's session object has connected to its remote peer + * (only used by sessions that were created in connect-mode) + * The IP address and port that the session is connected to. + */ + void SessionConnected(const IPAddressAndPort & connectedTo); + + /** Emitted when this handler's session object has disconnected from its remote peer */ + void SessionDisconnected(); + + /** Emitted when this handler's session object has been removed from the I/O thread's ReflectServer */ + void SessionDetached(); + + /** Emitted when this handler's output queue has been drained, after a call to RequestOutputQueueDrainedNotification() */ + void OutputQueueDrained(const MessageRef & drainMsg); + + /** This signal is called for all events associated with this handler. You can use this + * to catch custom events that don't have their own signal defined above, or if you want to + * receive all thread events via a single slot. + * @param code the MTT_EVENT_* code of the new event. + * @param optMsg If a Message is relevant, this will contain it; else it's a NULL reference. + */ + void InternalHandlerEvent(uint32 code, const MessageRef & optMsg); + +public slots: + /** + * Sends the specified Message to our session inside the I/O thread. + * @param msgRef a reference to the Message to send out. + * @return B_NO_ERROR on success, B_ERROR if out of memory. + */ + status_t SendMessageToSession(const MessageRef & msgRef); + + /** Reverts this handler to its default state (as if it had just been constructed). + * If the handler was associated with any IMessageTransceiverMaster, this will cleanly + * disassociate it from the thread and close any connection that it represented. + * @param emitEndMessageBatchIfNecessary If true, and we are currently in the middle of + * a message-batch, then this method will emit an + * EndMessageBatch() signal before dissasociating itself + * from the IMessageTransceiverMaster. That way the + * Reset() call won't break the rule that says that one + * EndMessageBatch() signal must always be emitted for + * every one BeginMessageBatch() signal. + */ + virtual void Reset(bool emitEndMessageBatchIfNecessary = true); + +protected: + /** Called when we need a worker session inside one of the SetupAs*() methods. (i.e. if the + * user didn't provide a worker session manually). Default implementation simply calls + * thread.CreateDefaultWorkerSession(), but the call has been broken out into a separate + * virtual method so that subclasses can override if they need to. + * @param thread the QMessageTransceiverThread object we are going to be associated with. + * @returns a new AbstractReflectSession object on success, or NULL on failure. + */ + virtual ThreadWorkerSessionRef CreateDefaultWorkerSession(QMessageTransceiverThread & thread); + +private: + friend class QMessageTransceiverThread; + friend class QMessageTransceiverThreadPool; + + // These methods will be called by our QMessageTransceiverThread only + void HandleIncomingEvent(uint32 code, const MessageRef & msgRef, const IPAddressAndPort & iap); + inline void EmitBeginMessageBatch() {emit BeginMessageBatch();} + inline void EmitEndMessageBatch() {emit EndMessageBatch();} + + void ClearRegistrationFields(); + + IMessageTransceiverMaster * _master; + QMessageTransceiverThread * _mtt; // _mtt and _master may or may not point to the same object + + int32 _sessionID; // will be set by our _mtt when we register with it + String _sessionTargetString; // cached for convenience... "/*/[_sessionID]" + + QMessageTransceiverHandler * _prevSeen; // used by _mtt for a quickie linked list + QMessageTransceiverHandler * _nextSeen; // used by _mtt for a quickie linked list +}; + +}; // end namespace muscle; + +#endif diff --git a/qtsupport/QMuscleSupport.h b/qtsupport/QMuscleSupport.h new file mode 100644 index 00000000..ce495c5d --- /dev/null +++ b/qtsupport/QMuscleSupport.h @@ -0,0 +1,40 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef QMuscleSupport_h +#define QMuscleSupport_h + +#include "support/MuscleSupport.h" // for PODHashFunctor, etc + +#if QT_VERSION >= 0x040000 +# include +#else +# include "util/String.h" +#endif + +namespace muscle { + +/** Enables the use of QStrings as keys in a MUSCLE Hashtable. */ +template <> +class PODHashFunctor +{ +public: + /** Returns a hash code for the given QString object. + * @param str The QString to calculate a hash code for. + */ + uint32 operator () (const QString & str) const + { +#if QT_VERSION >= 0x040000 + return qHash(str); +#else + QByteArray ba = str.utf8(); // Yes, in Qt 3.x it's called utf8(), not toUtf8() + return muscle::CalculateHashCode(ba.data(), ba.size()); +#endif + } + + /** Returns true iff the two QStrings are equal. */ + bool AreKeysEqual(const QString & k1, const QString & k2) const {return (k1==k2);} +}; + +}; // end namespace muscle + +#endif diff --git a/qtsupport/QSignalHandler.cpp b/qtsupport/QSignalHandler.cpp new file mode 100644 index 00000000..459073be --- /dev/null +++ b/qtsupport/QSignalHandler.cpp @@ -0,0 +1,54 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "qtsupport/QSignalHandler.h" + +namespace muscle { + +QSignalHandler :: QSignalHandler(QObject * parent, const char * name) : QObject(parent), _socketNotifier(NULL) +{ + if (name) setObjectName(name); + if ((CreateConnectedSocketPair(_mainThreadSocket, _handlerFuncSocket) == B_NO_ERROR)&&(SignalMultiplexer::GetSignalMultiplexer().AddHandler(this) == B_NO_ERROR)) + { + _socketNotifier = new QSocketNotifier(_mainThreadSocket.GetFileDescriptor(), QSocketNotifier::Read, this); + connect(_socketNotifier, SIGNAL(activated(int)), this, SLOT(SocketDataReady())); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "QSignalHandler %p could not register with the SignalMultiplexer!\n", this); +} + +QSignalHandler :: ~QSignalHandler() +{ + if (_socketNotifier) _socketNotifier->setEnabled(false); // prevent occasional CPU-spins under Lion + SignalMultiplexer::GetSignalMultiplexer().RemoveHandler(this); +} + +void QSignalHandler :: SocketDataReady() +{ + while(1) + { + char buf[64]; + int32 bytesReceived = ReceiveData(_mainThreadSocket, buf, sizeof(buf), false); + if (bytesReceived <= 0) break; + for (int32 i=0; i +#include +#include "system/SignalMultiplexer.h" +#include "util/NetworkUtilityFunctions.h" + +class QSocketNotifier; + +namespace muscle { + +/** + * This is a useful Qt class that will catch system signals + * (e.g. SIGINT/SIGHUP/SIGTERM.... *not* Qt signals) and emit + * a signalReceived(int) Qt signal in response. It uses the + * SignalReflectSession MUSCLE class in its implementation so it + * will have the same signal-handling semantics as that class. + * It will listen on a port, and emit a ConnectionAccepted signal + * whenever a new TCP connection is received on that port. In all + * other respects it works like an SignalHandler object. + */ +class QSignalHandler : public QObject, public ISignalHandler, private CountedObject +{ + Q_OBJECT + +public: + /** Constructor. + * @param parent Passed on to the QObject constructor + * @param name Passed on to the QObject constructor + */ + QSignalHandler(QObject * parent = NULL, const char * name = NULL); + + /** Destructor */ + virtual ~QSignalHandler(); + + virtual void SignalHandlerFunc(int sigNum); + +signals: + /** Emitted when a signal is received. + * @param sigNum The signal number received (e.g. SIGHUP, SIGINT, etc) + */ + void SignalReceived(int sigNum); + +private slots: + void SocketDataReady(); + +private: + ConstSocketRef _mainThreadSocket; + ConstSocketRef _handlerFuncSocket; + QSocketNotifier * _socketNotifier; +}; + +}; // end namespace muscle + +#endif diff --git a/qtsupport/QSocketDataIO.h b/qtsupport/QSocketDataIO.h new file mode 100644 index 00000000..7172dfb1 --- /dev/null +++ b/qtsupport/QSocketDataIO.h @@ -0,0 +1,99 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQSocketDataIO_h +#define MuscleQSocketDataIO_h + +#include +#include "dataio/DataIO.h" + +namespace muscle { + +/** This class was contributed to the MUSCLE archive by Jonathon Padfield + * (jpadfield@hotkey.net.au). It's a good alternative to the QMessageTransceiverThread + * class if you don't want to use the multi-threaded version of Qt. + * + * The QSocket object emits signals whenever it receives data, which you + * can connect to your Muscle client. eg. + * + * connect(muscleSocket, SIGNAL(hostFound()), SLOT(slotHostFound())); + * connect(muscleSocket, SIGNAL(connected()), SLOT(slotConnected())); + * connect(muscleSocket, SIGNAL(readyRead()), SLOT(slotReadyRead())); + * connect(muscleSocket, SIGNAL(bytesWritten(int)), SLOT(slotBytesWritten(int))); + * + * You can then use the normal QSocket routines to connect to the server + * while running all the input through the MessageIO interface. + * + * @author Jonathon Padfield + */ +class QSocketDataIO : public DataIO +{ +public: + /** Class constructor. + * @param newSocket a QSocket object that was allocated off the heap. This object becomes owner of newSocket. + */ + QSocketDataIO(QSocket * newSocket) : _socket(newSocket), _socketRef(newSocket?GetConstSocketRefFromPool(newSocket, false):ConstSocketRef()) {/* empty */} + + /** Destructor - deletes the held QSocket (if any) */ + virtual ~QSocketDataIO() {Shutdown();} + + /** Reads up to (size) bytes of new data from the QSocket into (buffer). + * Returns the actual number of bytes placed, or a negative value if there + * was an error. + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + */ + virtual int32 Read(void * buffer, uint32 size) {return _socket ? _socket->readBlock((char *) buffer, size) : -1;} + + /** Takes (size) bytes from (buffer) and pushes them in to the + * outgoing I/O stream of the QSocket. Returns the actual number of bytes + * read from (buffer) and pushed, or a negative value if there + * was an error. + * @param buffer Buffer to read the bytes from. + * @param size Number of bytes in the buffer. + * @return Number of bytes written, or -1 on error. + */ + virtual int32 Write(const void * buffer, uint32 size) {return _socket ? _socket->writeBlock((char *) buffer, size) : -1;} + + /** + * Always returns B_ERROR (you can't seek a QSocket!) + */ + virtual status_t Seek(int64 /*offset*/, int /*whence*/) {return B_ERROR;} + + /** Always returns -1, since a socket has no position to speak of */ + virtual int64 GetPosition() const {return -1;} + + /** + * Flushes the output buffer, if possible. + */ + virtual void FlushOutput() {if (_socket) _socket->flush();} + + /** + * Closes the connection. After calling this method, the + * DataIO object should not be used any more. + */ + virtual void Shutdown() {if (_socket) _socket->close(); delete _socket; _socket = NULL; _socketRef.Reset();} + + virtual const ConstSocketRef & GetReadSelectSocket() const {return _socket ? _socketRef : GetNullSocket();} + virtual const ConstSocketRef & GetWriteSelectSocket() const {return _socket ? _socketRef : GetNullSocket();} + + /** + * Returns the held QSocket object (in case you need to access it directly for some reason) + */ + QSocket * GetSocket() {return _socket;} + + /** + * Releases the held QSocket object into your custody. After calling this, this + * QSocketDataIObject no longer has anything to do with the QSocket object and + * it becomes your responsibility to delete the QSocket. + */ + void ReleaseSocket() {_socket = NULL; _socketRef.Neutralize();} + +private: + ConstSocketRef _socketRef; + QSocket * _socket; +}; + +}; // end namespace muscle + +#endif diff --git a/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.cpp b/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.cpp new file mode 100644 index 00000000..9455295a --- /dev/null +++ b/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.cpp @@ -0,0 +1,78 @@ +#include + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/SignalMessageIOGateway.h" + +#include "AdvancedQMessageTransceiverThread.h" +#include "ThreadedInternalSession.h" + +using namespace muscle; + +// Overridden to handle messages coming from the ThreadSupervisorSession (and therefore +// by extension, from the GUI thread) +void AdvancedThreadWorkerSession :: MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData) +{ + switch(msgRef()->what) + { + case ADVANCED_COMMAND_ENDSESSION: + printf("AdvancedThreadWorkerSession %p got ADVANCED_COMMAND_ENDSESSION Message, ending this session!\n", this); + EndSession(); + break; + + default: + ThreadWorkerSession::MessageReceivedFromSession(from, msgRef, userData); + break; + } +} + +// This factory knows how to create an AdvancedThreadWorkerSession object whenever +// the MUSCLE server receives a TCP connection on our port +class AdvancedThreadWorkerSessionFactory : public ThreadWorkerSessionFactory +{ +public: + AdvancedThreadWorkerSessionFactory() {/* empty */} + + virtual ThreadWorkerSessionRef CreateThreadWorkerSession(const String & loc, const IPAddressAndPort & iap) + { + AdvancedThreadWorkerSession * ret = newnothrow AdvancedThreadWorkerSession(); + if (ret) printf("AdvancedThreadWorkerSessionFactory created AdvancedThreadWorkerSession %p for client at loc=[%s] iap=[%s]\n", ret, loc(), iap.ToString()()); + else WARN_OUT_OF_MEMORY; + return ThreadWorkerSessionRef(ret); + } +}; + +AdvancedQMessageTransceiverThread :: AdvancedQMessageTransceiverThread() +{ + // We want our ThreadWorkerSessions to handle Messages from their remote peers themselves, not just + // forward all incoming Messages to the GUI thread, so we'll tell the MessageTransceiverThread that. + SetForwardAllIncomingMessagesToSupervisor(false); + + // Set up a factory to accept incoming TCP connections on our port, for remote sessions to use to connect to us + if (PutAcceptFactory(ADVANCED_EXAMPLE_PORT, ThreadWorkerSessionFactoryRef(newnothrow AdvancedThreadWorkerSessionFactory)) != B_NO_ERROR) printf("AdvancedQMessageTransceiverThread ctor: Error, couldn't create accept-factory on port %i!\n", ADVANCED_EXAMPLE_PORT); +} + +status_t AdvancedQMessageTransceiverThread :: AddNewThreadedInternalSession(const MessageRef & args) +{ + return AddNewSession(ThreadWorkerSessionRef(new ThreadedInternalSession(args))); +} + +ReflectServerRef AdvancedQMessageTransceiverThread :: CreateReflectServer() +{ + ReflectServerRef rsRef = QMessageTransceiverThread::CreateReflectServer(); + if (rsRef()) rsRef()->SetDoLogging(true); // so we can see sessions being created/deleted with displaylevel=trace + return rsRef; +} + +ThreadSupervisorSessionRef AdvancedQMessageTransceiverThread :: CreateSupervisorSession() +{ + char buf[20]; + printf("AdvancedQMessageTransceiverThread::CreateSupervisorSession() called in thread %s\n", muscle_thread_id::GetCurrentThreadID().ToString(buf)); + return ThreadSupervisorSessionRef(new AdvancedThreadSupervisorSession); +} + +ThreadWorkerSessionRef AdvancedQMessageTransceiverThread :: CreateDefaultWorkerSession() +{ + char buf[20]; + printf("AdvancedQMessageTransceiverThread::CreateDefaultWorkerSession() called in thread %s\n", muscle_thread_id::GetCurrentThreadID().ToString(buf)); + return ThreadWorkerSessionRef(new AdvancedThreadWorkerSession); +} diff --git a/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.h b/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.h new file mode 100644 index 00000000..64641c7e --- /dev/null +++ b/qtsupport/qt_advanced_example/AdvancedQMessageTransceiverThread.h @@ -0,0 +1,90 @@ +#ifndef AdvancedQMessageTransceiverThread_h +#define AdvancedQMessageTransceiverThread_h + +#include "qtsupport/QMessageTransceiverThread.h" + +using namespace muscle; + +// The port number this example program will receive TCP connections on +enum { + ADVANCED_EXAMPLE_PORT = 2961 +}; + +// what-codes in this range are intended to be processed by +// the sessions in the MUSCLE thread +enum { + FIRST_ADVANCED_COMMAND = 1097102947, // 'Advc' + + ADVANCED_COMMAND_ENDSESSION = FIRST_ADVANCED_COMMAND, + // [... more commands for the MUSCLE thread to process could be added here...] + + AFTER_LAST_ADVANCED_COMMAND +}; + +// what-codes in this range are intended to be processed by +// the internal/slave threads that were spawned by the +// ThreadedInternalSessions that are held by the MUSCLE thread. +enum { + FIRST_INTERNAL_THREAD_COMMAND = 1231975540, // 'Intt' + + INTERNAL_THREAD_COMMAND_HURRYUP = FIRST_INTERNAL_THREAD_COMMAND, + // [... more commands for the internal/slave threads to process could be added here...] + + AFTER_LAST_INTERNAL_THREAD_COMMAND +}; + +// This is a subclass of the MUSCLE QMessageTransceiverThread object that knows +// how to do the custom qt_advanced_example logic we requiret +class AdvancedQMessageTransceiverThread : public QMessageTransceiverThread +{ +public: + AdvancedQMessageTransceiverThread(); + + /** Tells the MUSCLE thread to add a new internal session to + * the server. + * @param args This Message can contain whatever initialization + * arguments need to be passed to the new session. + * @returns B_NO_ERROR on success, of B_ERROR on failure. + */ + status_t AddNewThreadedInternalSession(const MessageRef & args); + +protected: + // Overridden just to re-enable logging messages for debugging purposes + virtual ReflectServerRef CreateReflectServer(); + + // Overridden to make the supervisor session a + // AdvancedThreadSupervisorSession so we can include our logic + virtual ThreadSupervisorSessionRef CreateSupervisorSession(); + + // Overridden to make the supervisor session a + // AdvancedThreadWorkerSession so we can include our logic + virtual ThreadWorkerSessionRef CreateDefaultWorkerSession(); +}; + +/** This subclass of ThreadWorkerSession is specialized to give us regular StorageReflectSession semantics, + * rather than the usual ThreadWorkerSession semantics (which are more appropriate for regular client/server + * workflows, where the server is not being run inside a GUI app). + */ +class AdvancedThreadWorkerSession : public ThreadWorkerSession +{ +public: + AdvancedThreadWorkerSession() {/* empty */} + + virtual const char * GetTypeName() const {return "AdvancedThreadWorker";} + + // Overridden to specially handle messages coming from the ThreadSupervisorSession (and therefore by extension, from the GUI thread) + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData); +}; + +/** This supervisor session is subclassed so we can add our own logic to it + * if we want to (currently we don't need to do that) + */ +class AdvancedThreadSupervisorSession : public ThreadSupervisorSession +{ +public: + AdvancedThreadSupervisorSession() {/* empty */} + + virtual const char * GetTypeName() const {return "AdvancedThreadSupervisor";} +}; + +#endif diff --git a/qtsupport/qt_advanced_example/README.TXT b/qtsupport/qt_advanced_example/README.TXT new file mode 100644 index 00000000..c13132ed --- /dev/null +++ b/qtsupport/qt_advanced_example/README.TXT @@ -0,0 +1,55 @@ +This program demonstrates a trick with the QMessageTransceiverThread class. + +Note that this example is rather complex, so if you are looking for a simple example +of using Qt, check out the qt_example or qt_muscled or qt_muscled_browser folders instead. + +Since the MessageTransceiverThread class contains a full-fledged ReflectServer object, that +it uses for its event loop, it can technically do anything that a muscled server can do. + +That means that it is possible to have your Qt+MUSCLE GUI program run what is for all +intents and purposes a muscled server in a thread, and communicate with the muscled +thread via MessageRef objects, track which sessions it has connected, start your own +sessions on the muscled server, and so on. + +This example program demonstrates how to do that. It also demonstrates another trick: +how to create a session object that has its own internal thread inside it, which can be +used (with care) to do asynchronous processing or a third-party event loop that doesn't +block the rest of the muscled thread while it runs. + +To build and run this Qt example: + +1) Make sure you have the Qt development packages installed + +2) In this folder, build the qt_advanced_example ("qmake; make") + +3) Run the qt_advanced_example program (Under Linux, "./qt_advanced_example"; under MacOS/X, "./qt_advanced_example.app/Contents/MacOS/qt_advanced_example", under Windows, "Debug\qt_advanced_example.exe"), or click on its icon. + +4) Follow the instructions shown in the window. + +Some notes about the files in this folder: + +qt_advanced_example.{cpp,h} + These files contain the Qt GUI code used to implement a minimal user interface. + Since the GUI isn't really the point of the example, the user interface is fairly rudimentary. + +AdvancedQMessageTransceiverThread.{cpp,h} + These files implement some slight customization of the QMessageTransceiverThread class and its + related session classes, so that you can use a AdvancedQMessageTransceiverThread object as a + local (in-GUI-program) MUSCLE server itself, rather than just as a mechanism for connecting + to a MUSCLE server that is running somewhere else. + +ThreadedInternalSession.{cpp,h} + These files implement a session class that spawns its own internal thread. The internal + thread doesn't do much -- it just responds to Messages sent to it by the MUSCLE thread, + and also increments a counter once a second. When it increments its counter, it sends + a PR_COMMAND_SETDATA Message back to the MUSCLE thread so that anyone subscribed to the + path "/*/*/thread_status" (such as the QtGUI code, or any other MUSCLE client you care + to connect to the server) can see the updates occur. This is just stand-in behavior; a + real program would do something more useful in the internal thread. + +If you want to see where the various C++ objects are located within the various threads +in this program and how they communicate, be sure to check out the diagram +qt_advanced_example_code_layout_diagram.png that is in this folder. + +-Jeremy +9/17/2011 diff --git a/qtsupport/qt_advanced_example/ThreadedInternalSession.cpp b/qtsupport/qt_advanced_example/ThreadedInternalSession.cpp new file mode 100644 index 00000000..b4bb7a8b --- /dev/null +++ b/qtsupport/qt_advanced_example/ThreadedInternalSession.cpp @@ -0,0 +1,162 @@ +#include + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/SignalMessageIOGateway.h" + +#include "ThreadedInternalSession.h" + +using namespace muscle; + +ThreadedInternalSession :: ThreadedInternalSession(const MessageRef & args) : _args(args), _count(0), _nextStatusPostTime(0) +{ + // Set up our communication mechanism with our internally held I/O thread + // Must be done in the constructor so that the ReflectServer's event loop + // will have access to our signalling socket. + _gatewayOK = (SetupNotifierGateway() == B_NO_ERROR); +} + +/** Called in the MUSCLE thread during setup. Overridden to start the internal thread running also. */ +status_t ThreadedInternalSession :: AttachedToServer() +{ + return ((_gatewayOK)&&(AdvancedThreadWorkerSession::AttachedToServer() == B_NO_ERROR)) ? StartInternalThread() : B_ERROR; +} + +// Our SignalMessageIOGateway gateway sends us an empty dummy Message whenever it wants us to check our +// internal thread's reply-messages-queue. We respond here (in the MUSCLE thread) by grabbing all of the Messages +// from the internal thread's queue, and handing them over to the superclass's MesageReceivedFromGateway() +// method, as if they came from a regular old (TCP-connected) AdvancedThreadWorkerSession's client process. +void ThreadedInternalSession :: MessageReceivedFromGateway(const MessageRef & /*dummyMsg*/, void * userData) +{ + MessageRef ref; + while(GetNextReplyFromInternalThread(ref) >= 0) AdvancedThreadWorkerSession::MessageReceivedFromGateway(ref, userData); +} + +/** Called (in the MUSCLE thread) whenever this session receives a Message from one of our neighboring sessions. + * Typically it would be the AdvancedThreadSupervisorSession that is sending us Messages. + */ +void ThreadedInternalSession :: MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData) +{ + if (muscleInRange(msgRef()->what, (uint32)FIRST_ADVANCED_COMMAND, (uint32)ADVANCED_COMMAND_ENDSESSION)) + { + // We send ADVANCED_COMMAND_* Messages up to our superclass to process in the current (MUSCLE) thread + AdvancedThreadWorkerSession::MessageReceivedFromSession(from, msgRef, userData); + } + else SendMessageToInternalThread(msgRef); // all other commands get forwarded to our internal thread for it to process +} + +/** Called in the MUSCLE thread when we are about to go away -- overridden to the internal slave thread first */ +void ThreadedInternalSession :: AboutToDetachFromServer() +{ + (void) ShutdownInternalThread(); // important, to avoid race conditions! This won't return until the internal thread is gone + AdvancedThreadWorkerSession::AboutToDetachFromServer(); +} + +/** Called in the internal/slave thread by InternalThreadEntry(), whenever the main thread has sent a + * Message to the internal/slave thread for it to process. + * @param msgRef the MessageRef that was handed to us from the MUSCLE thread. + * @param numLeft the number of Messages left for us to process in the FIFO queue after this one. + */ +status_t ThreadedInternalSession :: MessageReceivedFromOwner(const MessageRef & msgRef, uint32 /*numLeft*/) +{ + if (msgRef()) + { + char buf[20]; + switch(msgRef()->what) + { + case INTERNAL_THREAD_COMMAND_HURRYUP: + printf("internal-slave-thread-%s received the following SAYHELLO Message from the MUSCLE thread:\n", muscle_thread_id::GetCurrentThreadID().ToString(buf)); + _count += msgRef()->GetInt32("count"); + _nextStatusPostTime = 0; // so we'll increment RIGHT AWAY + InvalidatePulseTime(); + break; + + default: + printf("internal-slave-thread-%s received the following unknown Message from the MUSCLE thread:\n", muscle_thread_id::GetCurrentThreadID().ToString(buf)); + msgRef()->PrintToStream(); + break; + } + return B_NO_ERROR; // message processed, indicate this thread should keep running + } + else return B_ERROR; // indicate that it's time for this thread to terminate now +} + +/** Entry point for the internal/slave thread -- overridden to customize the event loop with a poll timeout. + * So in addition to handling any Messages received from the MUSCLE thread, this event loop will also + * send a PR_COMMAND_SETDATA to the MUSCLE thread once every 5 seconds, so that the Qt GUI and other + * subscribed MUSCLE clients can track our thread's progress in counting up from zero. + */ +void ThreadedInternalSession :: InternalThreadEntry() +{ + char threadIDString[20]; + printf("internal-slave-thread %s is now ALIVE!!!\n", muscle_thread_id::GetCurrentThreadID().ToString(threadIDString)); + + if (_args()) + { + printf("Startup arguments for internal-slave-thread %s are:\n", threadIDString); + _args()->PrintToStream(); // a real program would probably use some data from here, not just print it out + } + + while(true) + { + // Wait until the next Message from the MUSCLE thread, or until (_nextStatusPostTime), whichever comes first. + // This demonstrates how to implement a polling loop -- if you don't need to poll, you can pass + // in MUSCLE_TIME_NEVER as the second argument to WaitForNextMessageFromOwner() (or just use + // the default implementation of Thread::InternalThreadEntry(), which does that same thing) + // + // Alternatively, if you never want to block in WaitForNextMessageFromOwner() you could pass 0 as + // the second argument, and that would cause WaitForNextMessageFromOwner() to always return immediately. + MessageRef msgRef; + int32 numLeftInQueue = WaitForNextMessageFromOwner(msgRef, _nextStatusPostTime); + if ((numLeftInQueue >= 0)&&(MessageReceivedFromOwner(msgRef, numLeftInQueue) != B_NO_ERROR)) + { + // MessageReceivedFromOwner() returned an error, that means this thread needs to exit! + break; + } + + uint64 now = GetRunTime64(); + if (now >= _nextStatusPostTime) + { + printf("Internal-slave-thread %s is sending a PR_COMMAND_SETDATA Message to the MUSCLE thread.\n", threadIDString); + + // Send a message to the MUSCLE thread, telling him to update our session's database node with our new count value + MessageRef dataMsg = GetMessageFromPool(); + dataMsg()->AddInt32("count", _count); + + MessageRef sendMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + sendMsg()->AddMessage("thread_status", dataMsg); + SendMessageToOwner(sendMsg); + + _nextStatusPostTime = now + SecondsToMicros(1); // we'll update our status once every 1 second + _count++; // in real life this would be something more interesting, e.g. temperature or something + } + } + printf("Internal-slave-thread %s is exiting!!!\n", threadIDString); +} + +status_t ThreadedInternalSession :: SetupNotifierGateway() +{ + // Set up the sockets, DataIO, and SignalMessageIOGateway that we will use to signal + // the MUSCLE thread that the internal thread has Messages ready for it to receive, and vice versa. + const ConstSocketRef & socket = GetOwnerWakeupSocket(); + if (socket()) + { + // This is a socket connection between the internal thread and the MUSCLE thread, used for signalling only + // We don't send the actual Messages over it, we only use it to wake up the other thread by sending + // a single byte across the TCP connection. This makes the messaging efficient even when the Messages + // are large, because (once created) the Messages never need to be copied or flattened/unflattened. + DataIORef dataIORef(newnothrow TCPSocketDataIO(socket, false)); + if (dataIORef()) + { + AbstractMessageIOGatewayRef gw(newnothrow SignalMessageIOGateway()); + if (gw()) + { + gw()->SetDataIO(dataIORef); + SetGateway(gw); + return B_NO_ERROR; + } + else WARN_OUT_OF_MEMORY; + } + else WARN_OUT_OF_MEMORY; + } + return B_ERROR; +} diff --git a/qtsupport/qt_advanced_example/ThreadedInternalSession.h b/qtsupport/qt_advanced_example/ThreadedInternalSession.h new file mode 100644 index 00000000..ccb663ed --- /dev/null +++ b/qtsupport/qt_advanced_example/ThreadedInternalSession.h @@ -0,0 +1,66 @@ +#ifndef ThreadedInternalSession_h +#define ThreadedInternalSession_h + +#include "AdvancedQMessageTransceiverThread.h" + +using namespace muscle; + +/** This class is an example of a MUSCLE session object that spawns its own internal thread to do + * asynchronous processing. Note that the internal thread is NOT allowed to touch any of the MUSCLE + * database or ReflectServer nodes directly, or it will suffer from race conditions. The internal thread + * can only do its own separate work, and comunicate with the MUSCLE thread by sending or receiving + * Message objects to/from the MUSCLE thread. + */ +class ThreadedInternalSession : public AdvancedThreadWorkerSession, private Thread +{ +public: + /** Constructor + * @param args This can contain whatever information the thread will find useful when it starts up. + */ + ThreadedInternalSession(const MessageRef & args); + + /** Called during setup. Overridden to start the internal thread running. */ + virtual status_t AttachedToServer(); + + /** Our SignalMessageIOGateway gateway sends us an empty dummy Message whenever it wants us to check our + * internal thread's reply-messages-queue. We respond by grabbing all of the Messages from the internal thread's queue, + * and handing them over to the superclass's MesageReceivedFromGateway() method, as if they came from a regular + * old (TCP-connected) AdvancedThreadWorkerSession's client process. + */ + virtual void MessageReceivedFromGateway(const MessageRef & /*dummyMsg*/, void * userData); + + /** Called (in the MUSCLE thread) whenever this session receives a Message from one of our neighboring sessions. + * Typically it would be the AdvancedThreadSupervisorSession that is sending us Messages. + */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData); + + /** Called when we are about to go away -- overridden to the internal slave thread first */ + virtual void AboutToDetachFromServer(); + +protected: + virtual const char * GetTypeName() const {return "ThreadedInternalSession";} + + /** Called by InternalThreadEntry(), in the internal/slave thread, whenever the main thread has a Message to give the slave Thread + * @param msgRef the MessageRef that was handed to us from the MUSCLE thread. + * @param numLeft the number of Messages left for us to process in the FIFO queue after this one. + */ + virtual status_t MessageReceivedFromOwner(const MessageRef & msgRef, uint32 numLeft); + +private: + /** Entry point for the internal thread -- overridden to customize the event loop with a poll timeout. + * So in addition to handling any Messages received from the MUSCLE thread, this event loop will also + * send a PR_COMMAND_SETDATA to the MUSCLE thread once every 5 seconds, so that the Qt GUI and other + * subscribed MUSCLE clients can track our thread's progress in counting up from zero. + */ + virtual void InternalThreadEntry(); + + status_t SetupNotifierGateway(); + + MessageRef _args; + bool _gatewayOK; + + uint32 _count; + uint64 _nextStatusPostTime; +}; + +#endif diff --git a/qtsupport/qt_advanced_example/qt_advanced_example.cpp b/qtsupport/qt_advanced_example/qt_advanced_example.cpp new file mode 100644 index 00000000..268234ef --- /dev/null +++ b/qtsupport/qt_advanced_example/qt_advanced_example.cpp @@ -0,0 +1,307 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" // for HandleStandardDaemonArgs() +#include "util/StringTokenizer.h" + +#include "qt_advanced_example.h" + +// This class holds information about a MUSCLE session, for display in the GUI +class SessionListViewItem : public QListWidgetItem +{ +public: + SessionListViewItem(QListWidget * parent, const String & sessionID) : QListWidgetItem(parent), _sessionID(sessionID) + { + Update(); + setText(_sessionID()); + } + + /** Returns the string that the MUSCLE thread uses to identify the session we correspond to */ + const String & GetSessionID() const {return _sessionID;} + + /** Called when data relevant to this node has been received from the MUSCLE thread + * @param subPath relative path of the data item (underneath our session node) + * @param optData If non-NULL, the new value of the sub-item; if a NULL reference it means the sub-item was deleted. + */ + void DataReceived(const String & subPath, const MessageRef & optData) + { + if (optData() != NULL) _data.Put(subPath, optData); + else _data.Remove(subPath); + Update(); + } + +private: + void Update() + { + String s = _sessionID + String(": "); + + // This is a super-minimalist display of what nodes our session has; a proper + // program would do a better job of this (and would probably also be more specific + // regarding what information it was looking for) + for (HashtableIterator iter(_data); iter.HasData(); iter++) + { + // Show the sub-path + s += iter.GetKey(); + + // And if there is a "count" integer in the Message data, show it too + int32 count; + if (iter.GetValue()()->FindInt32("count", count) == B_NO_ERROR) s += String("=%1").Arg(count); + s += ' '; + } + + setText(s()); + } + + String _sessionID; + Hashtable _data; +}; + +AdvancedExampleWindow :: AdvancedExampleWindow() +{ + setWindowTitle("Qt Advanced QMessageTransceiverThread Example"); + setAttribute(Qt::WA_DeleteOnClose); + resize(640, 400); + + QBoxLayout * vbl = new QBoxLayout(QBoxLayout::TopToBottom, this); + vbl->setSpacing(3); + vbl->setMargin(2); + + _sessionsView = new QListWidget; + _sessionsView->setSelectionMode(QAbstractItemView::ExtendedSelection); + connect(_sessionsView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(UpdateButtons())); + vbl->addWidget(_sessionsView, 1); + + QLabel * l1 = new QLabel("Note: Connect one or more MUSCLE clients (e.g. portablereflectclient or qt_muscled_browser)"); + l1->setAlignment(Qt::AlignCenter); + vbl->addWidget(l1); + + QLabel * l2 = new QLabel(QString("to 127.0.0.1:%1, or create InternalThreadSessions using the buttons below").arg(ADVANCED_EXAMPLE_PORT)); + l2->setAlignment(Qt::AlignCenter); + vbl->addWidget(l2); + + QWidget * buttonRow = new QWidget; + { + QBoxLayout * buttonRowLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonRow); + buttonRowLayout->setSpacing(6); + buttonRowLayout->setMargin(2); + + _addInternalSessionButton = new QPushButton("Add InternalThreadSession"); + connect(_addInternalSessionButton, SIGNAL(clicked()), this, SLOT(AddInternalSessionButtonClicked())); + buttonRowLayout->addWidget(_addInternalSessionButton); + + _removeSelectedSessionsButton = new QPushButton("Remove Selected Sessions"); + connect(_removeSelectedSessionsButton, SIGNAL(clicked()), this, SLOT(RemoveSelectedSessionsButtonClicked())); + buttonRowLayout->addWidget(_removeSelectedSessionsButton); + + _grabCurrentStateButton = new QPushButton("Grab Current State"); + connect(_grabCurrentStateButton, SIGNAL(clicked()), this, SLOT(GrabCurrentStateButtonClicked())); + buttonRowLayout->addWidget(_grabCurrentStateButton); + + _sendMessageToSelectedSessionsButton = new QPushButton("Send Message to Selected Sessions"); + connect(_sendMessageToSelectedSessionsButton, SIGNAL(clicked()), this, SLOT(SendMessageToSelectedSessionsButtonClicked())); + buttonRowLayout->addWidget(_sendMessageToSelectedSessionsButton); + } + vbl->addWidget(buttonRow); + + connect(&_serverThread, SIGNAL(MessageReceived(const MessageRef &, const String &)), this, SLOT(MessageReceivedFromServer(const MessageRef &, const String &))); + UpdateButtons(); + + // start the MUSCLE thread running + if (_serverThread.StartInternalThread() == B_NO_ERROR) + { + // Tell the ThreadSuperVisorSession in the MUSCLE thread that we want to know about updates to session status nodes + MessageRef subscribeMsg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + subscribeMsg()->AddBool("SUBSCRIBE:/*/*", true); // so we can see all sessions that are created or destroyed + subscribeMsg()->AddBool("SUBSCRIBE:/*/*/*", true); // so we can watch any nodes any session places in its "home directory" also + if (_serverThread.SendMessageToInternalThread(subscribeMsg) != B_NO_ERROR) printf("Error, couldn't send subcribe message to MUSCLE thread!\n"); + } + else printf("Error, couldn't start MUSCLE thread!\n"); +} + +AdvancedExampleWindow :: ~AdvancedExampleWindow() +{ + _serverThread.ShutdownInternalThread(); +} + +void AdvancedExampleWindow :: UpdateButtons() +{ + _removeSelectedSessionsButton->setEnabled(_sessionsView->selectionModel()->hasSelection()); + _sendMessageToSelectedSessionsButton->setEnabled(_sessionsView->selectionModel()->hasSelection()); +} + +// Parse out a string like "/_unknown_/7/status/float" into a session string and a sub-string, +// like "/_unknown_/7" and "status/float" +static status_t ParsePath(const String & path, String & retSessionString, String & retSubString) +{ + if (path.StartsWith('/') == false) return B_ERROR; // paranoia + + StringTokenizer tok(path(), "/"); + retSessionString = "/"; + retSessionString += tok(); + retSessionString += '/'; + retSessionString += tok(); + + retSubString = tok.GetRemainderOfString(); + return B_NO_ERROR; +} + +void AdvancedExampleWindow :: MessageReceivedFromServer(const MessageRef & msg, const String & sessionID) +{ + printf("AdvancedExampleWindow::MessageReceivedFromServer called in GUI thread! msg->what="UINT32_FORMAT_SPEC" sessionID=[%s]\n", msg()->what, sessionID()); + + switch(msg()->what) + { + case PR_RESULT_DATATREES: + { + // Just show the gathered data, to prove that we have it + QPlainTextEdit * te = new QPlainTextEdit; + te->setAttribute(Qt::WA_DeleteOnClose); + te->setMinimumWidth(640); + te->setWindowTitle("Grabbed MUSCLE server state"); + te->setPlainText(msg()->ToString()()); + te->show(); + } + break; + + case PR_RESULT_DATAITEMS: + { + // Look for strings that indicate that subscribed nodes were removed from the tree + const String * removedNodePath; + for (int i=0; (msg()->FindString(PR_NAME_REMOVED_DATAITEMS, i, &removedNodePath) == B_NO_ERROR); i++) + { + // (removedNodePath) will be something like "/_unknown_/7/status/float" + String sessionStr, subPath; + if (ParsePath(*removedNodePath, sessionStr, subPath) == B_NO_ERROR) + { + if (subPath.HasChars()) + { + // If there is a sub-path, that means a node inside a session has been deleted, so we'll tell our corresponding SessionListViewItem about that + SessionListViewItem * item; + if (_sessionLookup.Get(sessionStr, item) == B_NO_ERROR) + { + printf("GUI Thread removing subPath [%s] from SessionListViewItem %p (aka [%s])\n", subPath(), item, sessionStr()); + item->DataReceived(subPath, MessageRef()); + } + else printf("GUI Thread error: We got a notification that sub-node [%s] under session [%s] had been deleted, but we have no record for that session!\n", subPath(), sessionStr()); + } + else + { + // No sub-path means the session itself has gone away + SessionListViewItem * item; + if (_sessionLookup.Remove(sessionStr, item) == B_NO_ERROR) + { + printf("GUI Thread removing SessionListViewItem %p (for session [%s])\n", item, removedNodePath->Cstr()); + delete item; + } + else printf("GUI Thread error: We got a notification that session [%s] had been deleted, but we have no record for that session!\n", sessionStr()); + } + } + else printf("GUI Thread error: Unexpected PR_NAME_REMOVED_DATAITEMS path [%s]\n", removedNodePath->Cstr()); + } + + // And then look for sub-messages that represent subscribed nodes that were added to the tree or updated in-place + for (MessageFieldNameIterator iter = msg()->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + const String & pathStr = iter.GetFieldName(); // e.g. "/_unknown_/7/status/float" + String sessionStr, subPath; + if (ParsePath(pathStr, sessionStr, subPath) == B_NO_ERROR) + { + MessageRef data; + for (int32 i=0; msg()->FindMessage(iter.GetFieldName(), i, data) == B_NO_ERROR; i++) + { + // Create a SessionListViewItem for this sessionStr, if we don't already have one + SessionListViewItem * item; + if (_sessionLookup.Get(sessionStr, item) != B_NO_ERROR) + { + item = new SessionListViewItem(_sessionsView, sessionStr); + _sessionLookup.Put(sessionStr, item); + printf("GUI Thread adding SessionListViewItem %p (for session [%s])\n", item, sessionStr()); + } + if (subPath.HasChars()) item->DataReceived(subPath, data); + } + } + else printf("GUI Thread error: Unexpected sub-Message path [%s]\n", pathStr()); + } + } + break; + } +} + +void AdvancedExampleWindow :: AddInternalSessionButtonClicked() +{ + // tell the AdvancedQMessageTransceiverThread to create a new AdvancedWorkerSession for us. + printf("AddInternalSessionButtonClicked: GUI Thread is asking the MUSCLE thread to create a new internal session...\n"); + + MessageRef args = GetMessageFromPool(); + args()->AddString("Your startup instructions/parameters to pass to the internal thread", "could go here"); + args()->AddInt32("Really", 1234); + + _serverThread.AddNewThreadedInternalSession(args); +} + +void AdvancedExampleWindow :: RemoveSelectedSessionsButtonClicked() +{ + QList selectedItems = _sessionsView->selectedItems(); + for (int i=0; i(selectedItems[i]); + + MessageRef endSession = GetMessageFromPool(ADVANCED_COMMAND_ENDSESSION); + endSession()->AddString(PR_NAME_KEYS, slvi->GetSessionID()); // make sure it only goes to the session we want to go away + _serverThread.SendMessageToInternalThread(endSession); + + printf("RemoveSelectedSessionsButtonClicked: GUI Thread is asking the MUSCLE thread to remove session [%s]\n", slvi->GetSessionID()()); + } +} + +// Sends a Message to all the selected sessions, telling them to hurry up! +void AdvancedExampleWindow :: SendMessageToSelectedSessionsButtonClicked() +{ + QList selectedItems = _sessionsView->selectedItems(); + for (int i=0; i(selectedItems[i]); + + MessageRef pokeSession = GetMessageFromPool(INTERNAL_THREAD_COMMAND_HURRYUP); + pokeSession()->AddString(PR_NAME_KEYS, slvi->GetSessionID()); // make sure it only goes to the session we want it to go to + pokeSession()->AddString("hurry up", "already!"); + pokeSession()->AddInt32("count", 9); // this will cause the internal thread to increment their counters by 9, plus one for the timeout + _serverThread.SendMessageToInternalThread(pokeSession); + + printf("SendMessageToSelectedSessionsButtonClicked: GUI Thread is asking the MUSCLE thread to hurry up session [%s]\n", slvi->GetSessionID()()); + } +} + + +void AdvancedExampleWindow :: GrabCurrentStateButtonClicked() +{ + printf("GUI Thread sending a request for a snapshot of the current state from the MUSCLE thread...\n"); + + MessageRef msg = GetMessageFromPool(PR_COMMAND_GETDATATREES); + msg()->AddString(PR_NAME_KEYS, "/*"); // ask for the entire tree! + _serverThread.SendMessageToInternalThread(msg); +} + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; // necessary for MUSCLE initialization + + { + Message args; (void) ParseArgs(argc, argv, args); + HandleStandardDaemonArgs(args); + } + + QApplication app(argc, argv); + + AdvancedExampleWindow * fw = new AdvancedExampleWindow; + fw->show(); + return app.exec(); +} diff --git a/qtsupport/qt_advanced_example/qt_advanced_example.h b/qtsupport/qt_advanced_example/qt_advanced_example.h new file mode 100644 index 00000000..60906da9 --- /dev/null +++ b/qtsupport/qt_advanced_example/qt_advanced_example.h @@ -0,0 +1,49 @@ +#ifndef qt_advanced_example_h +#define qt_advanced_example_h + +#include + +#include "util/Hashtable.h" +#include "util/String.h" + +#include "AdvancedQMessageTransceiverThread.h" + +class QListWidget; +class QPushButton; + +using namespace muscle; + +class SessionListViewItem; + +class AdvancedExampleWindow : public QWidget +{ +Q_OBJECT + +public: + AdvancedExampleWindow(); + virtual ~AdvancedExampleWindow(); + +private slots: + // Called whenever the GUI thread receives any Message from the MUSCLE server thread + void MessageReceivedFromServer(const MessageRef & msg, const String & sessionID); + +private slots: + // Slots for GUI actions + void AddInternalSessionButtonClicked(); + void RemoveSelectedSessionsButtonClicked(); + void GrabCurrentStateButtonClicked(); + void SendMessageToSelectedSessionsButtonClicked(); + void UpdateButtons(); + +private: + AdvancedQMessageTransceiverThread _serverThread; + QListWidget * _sessionsView; + QPushButton * _addInternalSessionButton; + QPushButton * _removeSelectedSessionsButton; + QPushButton * _grabCurrentStateButton; + QPushButton * _sendMessageToSelectedSessionsButton; + + Hashtable _sessionLookup; +}; + +#endif diff --git a/qtsupport/qt_advanced_example/qt_advanced_example.pro b/qtsupport/qt_advanced_example/qt_advanced_example.pro new file mode 100644 index 00000000..ea91369e --- /dev/null +++ b/qtsupport/qt_advanced_example/qt_advanced_example.pro @@ -0,0 +1,54 @@ +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} +win32:LIBS += shlwapi.lib ws2_32.lib winmm.lib User32.lib Advapi32.lib shell32.lib iphlpapi.lib version.lib +unix:!mac:LIBS += -lutil -lrt +mac:LIBS += -framework IOKit -framework SystemConfiguration -framework Carbon +OBJECTS_DIR = objects +MOC_DIR = moc +MUSCLE_DIR = ../.. +win32:DEFINES += _WIN32_WINNT=0x0501 +INCLUDEPATH += $$MUSCLE_DIR + +win32:INCLUDEPATH += $$MUSCLE_DIR/regex/regex + +MUSCLE_SOURCES = $$MUSCLE_DIR/iogateway/AbstractMessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/MessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/RawDataMessageIOGateway.cpp \ + $$MUSCLE_DIR/message/Message.cpp \ + $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.cpp \ + $$MUSCLE_DIR/reflector/AbstractReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DataNode.cpp \ + $$MUSCLE_DIR/reflector/DumbReflectSession.cpp \ + $$MUSCLE_DIR/reflector/SignalHandlerSession.cpp \ + $$MUSCLE_DIR/reflector/StorageReflectSession.cpp \ + $$MUSCLE_DIR/reflector/FilterSessionFactory.cpp \ + $$MUSCLE_DIR/reflector/ReflectServer.cpp \ + $$MUSCLE_DIR/reflector/ServerComponent.cpp \ + $$MUSCLE_DIR/regex/SegmentedStringMatcher.cpp \ + $$MUSCLE_DIR/regex/StringMatcher.cpp \ + $$MUSCLE_DIR/regex/PathMatcher.cpp \ + $$MUSCLE_DIR/regex/QueryFilter.cpp \ + $$MUSCLE_DIR/syslog/SysLog.cpp \ + $$MUSCLE_DIR/system/MessageTransceiverThread.cpp \ + $$MUSCLE_DIR/system/SetupSystem.cpp \ + $$MUSCLE_DIR/system/SignalMultiplexer.cpp \ + $$MUSCLE_DIR/system/Thread.cpp \ + $$MUSCLE_DIR/util/ByteBuffer.cpp \ + $$MUSCLE_DIR/util/Directory.cpp \ + $$MUSCLE_DIR/util/FilePathInfo.cpp \ + $$MUSCLE_DIR/util/MiscUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/NetworkUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/SocketMultiplexer.cpp \ + $$MUSCLE_DIR/util/String.cpp \ + $$MUSCLE_DIR/util/PulseNode.cpp + +win32:MUSCLE_SOURCES += $$MUSCLE_DIR/regex/regex/regcomp.c \ + $$MUSCLE_DIR/regex/regex/regerror.c \ + $$MUSCLE_DIR/regex/regex/regexec.c \ + $$MUSCLE_DIR/regex/regex/regfree.c + +MUSCLE_INCLUDES = $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.h + +SOURCES = qt_advanced_example.cpp AdvancedQMessageTransceiverThread.cpp ThreadedInternalSession.cpp $$MUSCLE_SOURCES +HEADERS = qt_advanced_example.h $$MUSCLE_INCLUDES diff --git a/qtsupport/qt_advanced_example/qt_advanced_example_code_layout_diagram.png b/qtsupport/qt_advanced_example/qt_advanced_example_code_layout_diagram.png new file mode 100644 index 00000000..18eb1859 Binary files /dev/null and b/qtsupport/qt_advanced_example/qt_advanced_example_code_layout_diagram.png differ diff --git a/qtsupport/qt_example/README.TXT b/qtsupport/qt_example/README.TXT new file mode 100644 index 00000000..462941d8 --- /dev/null +++ b/qtsupport/qt_example/README.TXT @@ -0,0 +1,17 @@ +To build and run this Qt example: + +1) Make sure you have the Qt development packages installed + +2) Make sure that you have muscled compiled and running ("cd muscle/server; make; ./muscled displaylevel=trace") + +3) In this folder, build the qt_example ("qmake; make") + +4) Run the qt_example program (Under Linux, "./qt_example"; under MacOS/X, "./qt_example.app/Contents/MacOS/qt_example", under Windows, "Debug\qt_example.exe") + +5) (optional) run other copies of qt_example and connect them to the same server + +6) Click and drag in the upper area to set your client's state; you can see other clients' state in the areas as well. + +7) You can use the bottom half of the window to chat with other clients. + +-Jeremy diff --git a/qtsupport/qt_example/qt_example.cpp b/qtsupport/qt_example/qt_example.cpp new file mode 100644 index 00000000..a4d84738 --- /dev/null +++ b/qtsupport/qt_example/qt_example.cpp @@ -0,0 +1,484 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dataio/FileDataIO.h" +#include "message/Message.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" // for ParseConnectArg() +#include "util/Hashtable.h" + +#include "qt_example.h" + +ExampleWidget :: ExampleWidget(ExampleWindow * master, bool animate) : _master(master), _isMousePressed(false), _updatePos(rand()%10000), _xRatio(0.7f), _yRatio(0.7f) +{ + setMinimumSize(200, 20); + connect(&_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(AutoUpdate())); + UpdateLocalPosition(); + SetAnimateEnabled(animate); +} + +void ExampleWidget :: SetAnimateEnabled(int s) +{ + if (s) _autoUpdateTimer.start(50); + else _autoUpdateTimer.stop(); +} + +void ExampleWidget :: paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::TextAntialiasing); + + if (_master->_isConnected) + { + p.fillRect(QRect(0,0,width(),height()), Qt::darkGray); + + p.setPen(Qt::gray); + DrawText(p, NormalizedToQtCoords(Point(0.5f, 0.25f)), "Each connected qt_example client can", Qt::gray, false); + DrawText(p, NormalizedToQtCoords(Point(0.5f, 0.50f)), "click and drag in this area", Qt::gray, false); + DrawText(p, NormalizedToQtCoords(Point(0.5f, 0.75f)), "and the other clients will all see it", Qt::gray, false); + + // First, draw lines from our local position to all the other user's positions, just cause it looks cool + QPoint myPt = NormalizedToQtCoords(_master->_localState()->GetPoint("position")); + p.setPen(Qt::black); + for (HashtableIterator iter(_master->_states); iter.HasData(); iter++) p.drawLine(myPt, NormalizedToQtCoords(iter.GetValue()()->GetPoint("position"))); + + // And finally draw everyone's positio-indicator + for (HashtableIterator iter(_master->_states); iter.HasData(); iter++) DrawUser(p, iter.GetValue()); + DrawUser(p, _master->_localState); + } + else + { + p.fillRect(QRect(0,0,width(),height()), Qt::lightGray); + DrawText(p, NormalizedToQtCoords(Point(0.5f, 0.5f)), "(Not currently connected to server)", Qt::darkGray, false); + } +} + +void ExampleWidget :: DrawUser(QPainter & p, const MessageRef & data) +{ + if (data()) DrawText(p, NormalizedToQtCoords(data()->GetPoint("position")), data()->GetString("username")(), QColor(QRgb(data()->GetInt32("color"))), true); +} + +void ExampleWidget :: DrawText(QPainter & p, const QPoint & pt, const QString & text, const QColor & c, bool inBox) +{ + QFontMetrics fm = p.fontMetrics(); + int tw = fm.width(text); + int th = fm.ascent()+fm.descent(); + QRect r(pt.x()-(tw/2), pt.y()-(th/2), tw, th); + if (inBox) + { + p.setPen(Qt::black); + p.setBrush(c); + p.drawRoundedRect(r.adjusted(-5,-3,5,3), 10.0, 10.0); + } + + // draw the text centered about (pt) + p.setPen(inBox?Qt::black:c); + p.setBrush(Qt::NoBrush); + p.drawText(r, Qt::AlignCenter, text); +} + +void ExampleWidget :: AutoUpdate() +{ + if (_isMousePressed == false) + { + _updatePos += 0.05f; + UpdateLocalPosition(); + } +} + +static float xform(bool isX, float updatePos, float ratio) {return 0.5f+(ratio*(((isX?cos(updatePos):sin(updatePos)))/2.0f));} +static float unxform(bool isX, float updatePos, float x) {return ((2.0f*x)-1.0f)/((isX?cos(updatePos):sin(updatePos)));} + +void ExampleWidget :: UpdateLocalPosition() +{ + SetLocalPosition(Point(xform(true, _updatePos, _xRatio), xform(false, _updatePos, _yRatio))); +} + +void ExampleWidget :: mousePressEvent(QMouseEvent * e) +{ + QWidget::mousePressEvent(e); + _isMousePressed = true; + SetLocalPosition(QtCoordsToNormalized(e->pos())); +} + +void ExampleWidget :: mouseMoveEvent(QMouseEvent * e) +{ + QWidget::mouseMoveEvent(e); + if (_isMousePressed) SetLocalPosition(QtCoordsToNormalized(e->pos())); +} + +void ExampleWidget :: mouseReleaseEvent(QMouseEvent * e) +{ + QWidget::mouseReleaseEvent(e); + _isMousePressed = false; + + Point p((((float)e->x())/width())-0.5f, (((float)e->y())/height())-0.5f); + _updatePos = atan2(p.y(), p.x()); + _xRatio = unxform(true, _updatePos, ((float)e->x())/width()); + _yRatio = unxform(false, _updatePos, ((float)e->y())/height()); + UpdateLocalPosition(); +} + +void ExampleWidget :: SetLocalPosition(const Point & normPt) +{ + _localPosition = normPt; + emit LocalPositionChanged(); + update(); +} + +Point ExampleWidget :: QtCoordsToNormalized(const QPoint & pt) const +{ + return Point(((float)pt.x())/width(), ((float)pt.y())/height()); +} + +QPoint ExampleWidget :: NormalizedToQtCoords(const Point & pt) const +{ + return QPoint((int)((pt.x()*width())+0.5f), (int)((pt.y()*height())+0.5f)); +} + +static QColor GetRandomBrightColor() +{ + const uint32 colorFloor = 150; + const uint32 colorRange = (256-colorFloor); + return QColor((rand()%colorRange)+colorFloor, (rand()%colorRange)+colorFloor, (rand()%colorRange)+colorFloor); +} + +ExampleWindow :: ExampleWindow(const QString & serverName, const QString & userName, const ConstByteBufferRef & publicKey, bool animate) : _isConnected(false), _curUserName(userName), _localColor(GetRandomBrightColor()), _publicKey(publicKey) +{ +#ifdef MUSCLE_ENABLE_SSL + if (_publicKey()) _mtt.SetSSLPublicKeyCertificate(_publicKey); +#endif + + QPalette p = palette(); + p.setColor(QPalette::Window, _localColor); + setPalette(p); + + setAttribute(Qt::WA_DeleteOnClose); + resize(640, 400); + + QBoxLayout * vbl = new QBoxLayout(QBoxLayout::TopToBottom, this); + vbl->setSpacing(3); + vbl->setMargin(2); + + QWidget * topRow = new QWidget; + { + QBoxLayout * topRowLayout = new QBoxLayout(QBoxLayout::LeftToRight, topRow); + topRowLayout->setSpacing(6); + topRowLayout->setMargin(2); + + topRowLayout->addWidget(new QLabel("Server:")); + + _serverName = new QLineEdit; + _serverName->setText(serverName); + connect(_serverName, SIGNAL(returnPressed()), this, SLOT(ConnectToServer())); + topRowLayout->addWidget(_serverName, 1); + + _connectButton = new QPushButton("Connect to Server"); + connect(_connectButton, SIGNAL(clicked()), this, SLOT(ConnectToServer())); + topRowLayout->addWidget(_connectButton); + + _disconnectButton = new QPushButton("Disconnect from Server"); + connect(_disconnectButton, SIGNAL(clicked()), this, SLOT(DisconnectFromServer())); + topRowLayout->addWidget(_disconnectButton); + + _cloneButton = new QPushButton("Clone Window"); + connect(_cloneButton, SIGNAL(clicked()), this, SLOT(CloneWindow())); + topRowLayout->addWidget(_cloneButton); + + _animate = new QCheckBox("Animate"); + _animate->setChecked(animate); + topRowLayout->addWidget(_animate); + } + vbl->addWidget(topRow); + + QSplitter * splitter = new QSplitter; + splitter->setOrientation(Qt::Vertical); + { + _exampleWidget = new ExampleWidget(this, animate); + connect(_exampleWidget, SIGNAL(LocalPositionChanged()), this, SLOT(UploadLocalState())); + connect(_animate, SIGNAL(stateChanged(int)), _exampleWidget, SLOT(SetAnimateEnabled(int))); + splitter->addWidget(_exampleWidget); + + QWidget * splitBottom = new QWidget; + { + QBoxLayout * splitBottomLayout = new QBoxLayout(QBoxLayout::TopToBottom, splitBottom); + splitBottomLayout->setMargin(2); + splitBottomLayout->setSpacing(2); + + _chatText = new QTextEdit; + _chatText->setReadOnly(true); + splitBottomLayout->addWidget(_chatText, 1); + + QWidget * botRow = new QWidget; + { + QBoxLayout * botRowLayout = new QBoxLayout(QBoxLayout::LeftToRight, botRow); + botRowLayout->setSpacing(3); + botRowLayout->setMargin(3); + + _userName = new QLineEdit; + _userName->setText(_curUserName); + _userName->setMinimumWidth(100); + connect(_userName, SIGNAL(editingFinished()), this, SLOT(UserChangedName())); + connect(_userName, SIGNAL(returnPressed()), this, SLOT(UserChangedName())); + botRowLayout->addWidget(_userName); + botRowLayout->addWidget(new QLabel(":")); + + _chatEntry = new QLineEdit; + connect(_chatEntry, SIGNAL(returnPressed()), this, SLOT(SendChatText())); + botRowLayout->addWidget(_chatEntry, 1); + } + splitBottomLayout->addWidget(botRow); + } + splitter->addWidget(splitBottom); + } + vbl->addWidget(splitter); + + connect(&_mtt, SIGNAL(SessionConnected(const String &, const IPAddressAndPort &)), this, SLOT(SessionConnected())); + connect(&_mtt, SIGNAL(MessageReceived(const MessageRef &, const String &)), this, SLOT(MessageReceived(const MessageRef &))); + connect(&_mtt, SIGNAL(SessionDisconnected(const String &)), this, SLOT(SessionDisconnected())); + UpdateButtons(); + + ConnectToServer(); // we might as well connect automatically on startup +} + +ExampleWindow :: ~ExampleWindow() +{ + _mtt.ShutdownInternalThread(); +} + +void ExampleWindow :: UserChangedName() +{ + if (_userName->text() != _curUserName) + { + _curUserName = _userName->text(); + UploadLocalState(); + _exampleWidget->update(); + } +} + +void ExampleWindow :: UpdateButtons() +{ + _chatEntry->setEnabled(_isConnected); + _connectButton->setEnabled(!_isConnected); + _disconnectButton->setEnabled(_isConnected); + _serverName->setEnabled(!_isConnected); + _exampleWidget->setEnabled(_isConnected); + _exampleWidget->update(); +} + +static String FromQ(const QString & s) +{ + return String(s.toUtf8().constData()); +} + +void ExampleWindow :: CloneWindow() +{ + String newUserName = FromQ(_userName->text()); + + const char * p = newUserName()+newUserName.Length(); // points to the NUL byte + while((p>newUserName())&&(muscleInRange(*(p-1), '0', '9'))) p--; + + // Now (p) is pointing to the first digit at the end of the string + if (muscleInRange(*p, '0', '9')) + { + int i = atoi(p); + newUserName = newUserName.Substring(0, p-newUserName())+String("%1").Arg(i+1); + } + else newUserName += " #2"; + + ExampleWindow * clone = new ExampleWindow(_serverName->text(), newUserName(), _publicKey, _exampleWidget->IsAnimateEnabled()); + clone->move(pos().x()+30, pos().y()+30); + clone->show(); +} + +void ExampleWindow :: ConnectToServer() +{ + _isConnected = false; + _mtt.Reset(); + + String hostname; + uint16 port = 2960; // default port for muscled + if (ParseConnectArg(FromQ(_serverName->text()), hostname, port) != B_NO_ERROR) + { + AddChatText(QString("Unable to parse server name %1.").arg(_serverName->text())); + } + else if ((_mtt.AddNewConnectSession(hostname, port, false, SecondsToMicros(1)) == B_NO_ERROR)&&(_mtt.StartInternalThread() == B_NO_ERROR)) + { + AddChatText(QString("Connecting to server %1...").arg(_serverName->text())); + } + else AddChatText(QString("Could not initiate connection to server %1.").arg(_serverName->text())); + + UpdateButtons(); +} + +void ExampleWindow :: DisconnectFromServer() +{ + _isConnected = false; + _mtt.Reset(); + AddChatText("Disconnected from server."); + UpdateButtons(); +} + +void ExampleWindow :: SessionConnected() +{ + _isConnected = true; + UpdateButtons(); + + // Subscribe to the data published by other ExampleClients + MessageRef subscribeMsg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + subscribeMsg()->AddBool("SUBSCRIBE:qt_example/state", true); + _mtt.SendMessageToSessions(subscribeMsg); + + // And upload our current state to the server + UploadLocalState(); + + AddChatText(QString("Connected to server %1").arg(_serverName->text())); +} + +void ExampleWindow :: AddChatText(const QString & text) +{ + _chatText->append(text); + _chatText->verticalScrollBar()->setValue(_chatText->verticalScrollBar()->maximum()); +} + +void ExampleWindow :: UploadLocalState() +{ + MessageRef stateMsg = GetMessageFromPool(); + stateMsg()->AddString("username", FromQ(_curUserName)); + stateMsg()->AddPoint("position", _exampleWidget->GetLocalPosition()); + stateMsg()->AddInt32("color", (int32) (_localColor.rgb())); + + MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + uploadMsg()->AddMessage("qt_example/state", stateMsg); + + _localState = stateMsg; + if (_isConnected) _mtt.SendMessageToSessions(uploadMsg); +} + +enum { + QT_EXAMPLE_CHAT_TEXT = 6666 // arbitrary +}; + +void ExampleWindow :: MessageReceived(const MessageRef & msg) +{ + switch(msg()->what) + { + case QT_EXAMPLE_CHAT_TEXT: + { + String fromUser = msg()->GetString("username"); + String text = msg()->GetString("text"); + AddChatText(QString("[%1] said: %2").arg(fromUser()).arg(text())); + } + break; + + case PR_RESULT_DATAITEMS: + { + // Look for strings that indicate that subscribed nodes were removed from the tree + { + const String * nodePath; + for (int i=0; (msg()->FindString(PR_NAME_REMOVED_DATAITEMS, i, &nodePath) == B_NO_ERROR); i++) + { + MessageRef existingState; + if (_states.Remove(*nodePath, existingState) == B_NO_ERROR) + { + AddChatText(QString("[%1] has disconnected from the server.").arg(existingState()->GetString("username")())); + _exampleWidget->update(); + } + } + } + + // And then look for sub-messages that represend subscribed nodes that were added to the tree, or updated in-place + { + for (MessageFieldNameIterator iter = msg()->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + MessageRef data; + for (uint32 i=0; msg()->FindMessage(iter.GetFieldName(), i, data) == B_NO_ERROR; i++) + { + if (_states.ContainsKey(iter.GetFieldName()) == false) AddChatText(QString("[%1] has connected to the server.").arg(data()->GetString("username")())); + _states.Put(iter.GetFieldName(), data); + } + _exampleWidget->update(); + } + } + } + break; + } +} + +void ExampleWindow :: SendChatText() +{ + QString text = _chatEntry->text(); + _chatEntry->setText(QString::null); + + MessageRef chatMsg = GetMessageFromPool(QT_EXAMPLE_CHAT_TEXT); + chatMsg()->AddString("username", FromQ(_curUserName)); // tag Message with who sent it + chatMsg()->AddString("text", FromQ(text)); + chatMsg()->AddString(PR_NAME_KEYS, "qt_example"); // make sure the chat text only goes to qt_example clients on the server + MessageReceived(chatMsg); // handle it locally also (the server won't send it back to us, by default) + + _mtt.SendMessageToSessions(chatMsg); +} + +void ExampleWindow :: SessionDisconnected() +{ + _isConnected = false; + UpdateButtons(); + + _states.Clear(); + _exampleWidget->update(); + + AddChatText("Disconnected from server!"); +} + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; // necessary for MUSCLE initialization + QApplication app(argc, argv); + + srand(time(NULL)); + + ByteBufferRef publicKey; + for (int i=1; iGetBuffer(), fileData()->GetNumBytes()) == fileData()->GetNumBytes())) + { + LogTime(MUSCLE_LOG_INFO, "Using public key file [%s] to register with server\n", a); + publicKey = fileData; + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't load public key file [%s] (file not found?)\n", a); + return 10; + } +#else + LogTime(MUSCLE_LOG_CRITICALERROR, "Can't load public key file [%s], SSL support is not enabled!\n", a); + return 10; +#endif + } + } + + ExampleWindow * fw = new ExampleWindow("localhost:2960", "Anonymous", publicKey, false); + fw->show(); + return app.exec(); +} diff --git a/qtsupport/qt_example/qt_example.h b/qtsupport/qt_example/qt_example.h new file mode 100644 index 00000000..9e51c9ee --- /dev/null +++ b/qtsupport/qt_example/qt_example.h @@ -0,0 +1,114 @@ +#ifndef QT_EXAMPLE_H +#define QT_EXAMPLE_H + +#include +#include +#include "qtsupport/QMessageTransceiverThread.h" +#include "support/Point.h" + +using namespace muscle; + +class QCheckBox; +class QLineEdit; +class QTextEdit; +class QPainter; +class QPushButton; +class ExampleWindow; + +class ExampleWidget : public QWidget +{ +Q_OBJECT + +public: + ExampleWidget(ExampleWindow * master, bool animate); + + virtual void paintEvent(QPaintEvent * e); + virtual void mousePressEvent(QMouseEvent * e); + virtual void mouseMoveEvent(QMouseEvent * e); + virtual void mouseReleaseEvent(QMouseEvent * e); + + const Point & GetLocalPosition() const {return _localPosition;} + bool IsAnimateEnabled() const {return _autoUpdateTimer.isActive();} + +signals: + void LocalPositionChanged(); + +public slots: + void SetAnimateEnabled(int); + +private slots: + void AutoUpdate(); + +private: + Point QtCoordsToNormalized(const QPoint & pt) const; + QPoint NormalizedToQtCoords(const Point & pt) const; + void DrawUser(QPainter & p, const MessageRef & data); + void DrawText(QPainter & p, const QPoint & pt, const QString & text, const QColor & color, bool inBox); + void SetLocalPosition(const Point & p); + void UpdateLocalPosition(); + + ExampleWindow * _master; + Point _localPosition; + bool _isMousePressed; + + QTimer _autoUpdateTimer; + float _updatePos; + + float _xRatio; + float _yRatio; +}; + +class ExampleWindow : public QWidget +{ +Q_OBJECT + +public: + ExampleWindow(const QString & serverName, const QString & userName, const ConstByteBufferRef & publicKey, bool animate); + virtual ~ExampleWindow(); + +private slots: + // slots for responding to GUI actions + void ConnectToServer(); + void DisconnectFromServer(); + void UpdateButtons(); + void UploadLocalState(); + void UserChangedName(); + void AddChatText(const QString & text); + void SendChatText(); + + // slots called by signals from the QMessageTransceiverThread object + void SessionConnected(); + void MessageReceived(const MessageRef & msg); + void SessionDisconnected(); + + void CloneWindow(); + +private: + friend class ExampleWidget; + + bool _isConnected; + + QPushButton * _connectButton; + QPushButton * _disconnectButton; + QPushButton * _cloneButton; + QLineEdit * _serverName; + + ExampleWidget * _exampleWidget; + + QTextEdit * _chatText; + QLineEdit * _userName; + QLineEdit * _chatEntry; + + QString _curUserName; + QColor _localColor; + QCheckBox * _animate; + + QMessageTransceiverThread _mtt; + + MessageRef _localState; // contains our local state, as recently uploaded + Hashtable _states; // contains info we gathered about other clients, from our subscription + + ConstByteBufferRef _publicKey; +}; + +#endif diff --git a/qtsupport/qt_example/qt_example.pro b/qtsupport/qt_example/qt_example.pro new file mode 100644 index 00000000..c965b697 --- /dev/null +++ b/qtsupport/qt_example/qt_example.pro @@ -0,0 +1,72 @@ +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} +win32:LIBS += shlwapi.lib ws2_32.lib winmm.lib User32.lib Advapi32.lib shell32.lib iphlpapi.lib version.lib +unix:!mac:LIBS += -lutil -lrt +mac:LIBS += -framework IOKit -framework SystemConfiguration -framework Carbon +OBJECTS_DIR = objects +MUSCLE_DIR = ../.. +FLAGSDIR = . + +exists($$FLAGSDIR/muscle_enable_ssl) { + DEFINES += MUSCLE_ENABLE_SSL + unix:LIBS += -lssl -lcrypto + win32:LIBS += libeay32.lib ssleay32.lib + win32:QMAKE_FLAGS += /LIBPATH:../../../openssl/out32dll + win32:INCLUDEPATH += ../../../openssl/include +} + +win32:DEFINES += _WIN32_WINNT=0x0501 + +INCLUDEPATH += $$MUSCLE_DIR + +#DEFINES += MUSCLE_USE_CPLUSPLUS11 +#unix:mac:QMAKE_CXXFLAGS += -std=c++11 -stdlib=libc++ + +win32:INCLUDEPATH += $$MUSCLE_DIR/regex/regex + +MUSCLE_SOURCES = $$MUSCLE_DIR/iogateway/AbstractMessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/MessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/RawDataMessageIOGateway.cpp \ + $$MUSCLE_DIR/message/Message.cpp \ + $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.cpp \ + $$MUSCLE_DIR/reflector/AbstractReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DataNode.cpp \ + $$MUSCLE_DIR/reflector/DumbReflectSession.cpp \ + $$MUSCLE_DIR/reflector/SignalHandlerSession.cpp \ + $$MUSCLE_DIR/reflector/StorageReflectSession.cpp \ + $$MUSCLE_DIR/reflector/FilterSessionFactory.cpp \ + $$MUSCLE_DIR/reflector/ReflectServer.cpp \ + $$MUSCLE_DIR/reflector/ServerComponent.cpp \ + $$MUSCLE_DIR/regex/SegmentedStringMatcher.cpp \ + $$MUSCLE_DIR/regex/StringMatcher.cpp \ + $$MUSCLE_DIR/regex/PathMatcher.cpp \ + $$MUSCLE_DIR/regex/QueryFilter.cpp \ + $$MUSCLE_DIR/syslog/SysLog.cpp \ + $$MUSCLE_DIR/system/MessageTransceiverThread.cpp \ + $$MUSCLE_DIR/system/SetupSystem.cpp \ + $$MUSCLE_DIR/system/SignalMultiplexer.cpp \ + $$MUSCLE_DIR/system/Thread.cpp \ + $$MUSCLE_DIR/util/ByteBuffer.cpp \ + $$MUSCLE_DIR/util/Directory.cpp \ + $$MUSCLE_DIR/util/FilePathInfo.cpp \ + $$MUSCLE_DIR/util/MiscUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/NetworkUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/SocketMultiplexer.cpp \ + $$MUSCLE_DIR/util/String.cpp \ + $$MUSCLE_DIR/util/PulseNode.cpp + +win32:MUSCLE_SOURCES += $$MUSCLE_DIR/regex/regex/regcomp.c \ + $$MUSCLE_DIR/regex/regex/regerror.c \ + $$MUSCLE_DIR/regex/regex/regexec.c \ + $$MUSCLE_DIR/regex/regex/regfree.c + +MUSCLE_INCLUDES = $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.h + +SOURCES = qt_example.cpp $$MUSCLE_SOURCES +HEADERS = qt_example.h $$MUSCLE_INCLUDES + +exists($$FLAGSDIR/muscle_enable_ssl) { + SOURCES += $$MUSCLE_DIR/dataio/SSLSocketDataIO.cpp + SOURCES += $$MUSCLE_DIR/iogateway/SSLSocketAdapterGateway.cpp +} diff --git a/qtsupport/qt_muscled/README.txt b/qtsupport/qt_muscled/README.txt new file mode 100644 index 00000000..2783f674 --- /dev/null +++ b/qtsupport/qt_muscled/README.txt @@ -0,0 +1,5 @@ +The .pro file in this folder just builds a version of muscled +that runs as a GUI app (via Qt) rather than as a Terminal application. + +All the app does is run its compiled-in muscled code, and show +muscled's output in a Qt QTextEdit widget. diff --git a/qtsupport/qt_muscled/qt_muscled.cpp b/qtsupport/qt_muscled/qt_muscled.cpp new file mode 100644 index 00000000..e8306aa1 --- /dev/null +++ b/qtsupport/qt_muscled/qt_muscled.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#include "qt_muscled.h" +#include "system/SetupSystem.h" +#include "util/Queue.h" +#include "util/String.h" + +using namespace muscle; + +extern int muscledmain(int, char **); // from muscled.cpp + +MuscledWindow :: MuscledWindow(const char * argv0) : _cpdio(false), _notifier(NULL) +{ + resize(800, 400); + setWindowTitle("MUSCLEd Server Process"); + + QBoxLayout * bl = new QBoxLayout(QBoxLayout::TopToBottom, this); + bl->setMargin(0); + bl->setSpacing(0); + + _muscledStdoutText = new QPlainTextEdit; + bl->addWidget(_muscledStdoutText); + + Queue argv; + argv.AddTail(argv0); + argv.AddTail("muscled"); + argv.AddTail("displaylevel=trace"); + argv.AddTail("catchsignals"); + if (_cpdio.LaunchChildProcess(argv) == B_NO_ERROR) + { + _gateway.SetDataIO(DataIORef(&_cpdio, false)); + _notifier = new QSocketNotifier(_cpdio.GetReadSelectSocket().GetFileDescriptor(), QSocketNotifier::Read, this); + connect(_notifier, SIGNAL(activated(int)), this, SLOT(TextAvailableFromChildProcess())); + } + else _muscledStdoutText->appendPlainText("\r\n"); +} + +MuscledWindow :: ~MuscledWindow() +{ + if (_notifier) _notifier->setEnabled(false); // avoid CPU-spins under Lion + _gateway.SetDataIO(DataIORef()); // paranoia +} + +void MuscledWindow :: TextAvailableFromChildProcess() +{ + if (_gateway.DoInput(*this) < 0) + { + _muscledStdoutText->appendPlainText("\r\n"); + delete _notifier; + _notifier = NULL; + } +} + +void MuscledWindow :: MessageReceivedFromGateway(const MessageRef & msg, void *) +{ + const String * s; + for (int32 i=0; msg()->FindString(PR_NAME_TEXT_LINE, i, &s) == B_NO_ERROR; i++) _muscledStdoutText->appendPlainText(s->Cstr()); +} + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + if ((argc > 1)&&(strcmp(argv[1], "muscled") == 0)) + { + // The muscled process (will be launched by the MuscledWindow constructor) + return muscledmain(argc, argv); + } + else + { + QApplication app(argc, argv); + + // The GUI process + MuscledWindow mw(argv[0]); + mw.show(); + return app.exec(); + } +} diff --git a/qtsupport/qt_muscled/qt_muscled.h b/qtsupport/qt_muscled/qt_muscled.h new file mode 100644 index 00000000..d008cc57 --- /dev/null +++ b/qtsupport/qt_muscled/qt_muscled.h @@ -0,0 +1,35 @@ +#ifndef MuscledWindow_h +#define MuscledWindow_h + +#include + +#include "dataio/ChildProcessDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" + +class QPlainTextEdit; +class QSocketNotifier; + +using namespace muscle; + +class MuscledWindow : public QWidget, public AbstractGatewayMessageReceiver +{ +Q_OBJECT + +public: + MuscledWindow(const char * argv0); + virtual ~MuscledWindow(); + +private slots: + void TextAvailableFromChildProcess(); + +protected: + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + +private: + QPlainTextEdit * _muscledStdoutText; + ChildProcessDataIO _cpdio; + PlainTextMessageIOGateway _gateway; // used to assemble full text lines + QSocketNotifier * _notifier; +}; + +#endif diff --git a/qtsupport/qt_muscled/qt_muscled.pro b/qtsupport/qt_muscled/qt_muscled.pro new file mode 100644 index 00000000..4e89f1ca --- /dev/null +++ b/qtsupport/qt_muscled/qt_muscled.pro @@ -0,0 +1,63 @@ +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} +win32:LIBS += shlwapi.lib ws2_32.lib winmm.lib User32.lib Advapi32.lib shell32.lib iphlpapi.lib version.lib +unix:!mac:LIBS += -lutil -lrt -lz +mac:LIBS += -lz -framework Carbon + +win32:DEFINES += _WIN32_WINNT=0x0501 + +OBJECTS_DIR = objects +MUSCLE_DIR = ../../ + +DEFINES += UNIFIED_DAEMON # so we can have our own main() function, without clashing with the one inside muscled.cpp +DEFINES += MUSCLE_ENABLE_ZLIB_ENCODING +DEFINES += MUSCLE_SINGLE_THREAD_ONLY + +#DEFINES += MUSCLE_USE_CPLUSPLUS11 +#unix:mac:QMAKE_CXXFLAGS += -std=c++11 -stdlib=libc++ + +INCLUDEPATH += $$MUSCLE_DIR +win32:INCLUDEPATH += $$MUSCLE_DIR/regex/regex $$MUSCLE_DIR/zlib/zlib/win32 + +SOURCES += $$MUSCLE_DIR/message/Message.cpp \ + $$MUSCLE_DIR/iogateway/MessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/AbstractMessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/PlainTextMessageIOGateway.cpp \ + $$MUSCLE_DIR/dataio/ChildProcessDataIO.cpp \ + $$MUSCLE_DIR/syslog/SysLog.cpp \ + $$MUSCLE_DIR/system/SetupSystem.cpp \ + $$MUSCLE_DIR/system/SignalMultiplexer.cpp \ + $$MUSCLE_DIR/system/GlobalMemoryAllocator.cpp \ + $$MUSCLE_DIR/zlib/ZLibCodec.cpp \ + $$MUSCLE_DIR/regex/StringMatcher.cpp \ + $$MUSCLE_DIR/regex/QueryFilter.cpp \ + $$MUSCLE_DIR/regex/PathMatcher.cpp \ + $$MUSCLE_DIR/regex/SegmentedStringMatcher.cpp \ + $$MUSCLE_DIR/reflector/AbstractReflectSession.cpp \ + $$MUSCLE_DIR/reflector/SignalHandlerSession.cpp \ + $$MUSCLE_DIR/reflector/StorageReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DumbReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DataNode.cpp \ + $$MUSCLE_DIR/reflector/ReflectServer.cpp \ + $$MUSCLE_DIR/reflector/FilterSessionFactory.cpp \ + $$MUSCLE_DIR/reflector/RateLimitSessionIOPolicy.cpp \ + $$MUSCLE_DIR/reflector/ServerComponent.cpp \ + $$MUSCLE_DIR/util/MemoryAllocator.cpp \ + $$MUSCLE_DIR/util/Directory.cpp \ + $$MUSCLE_DIR/util/FilePathInfo.cpp \ + $$MUSCLE_DIR/util/MiscUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/ByteBuffer.cpp \ + $$MUSCLE_DIR/util/String.cpp \ + $$MUSCLE_DIR/util/NetworkUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/PulseNode.cpp \ + $$MUSCLE_DIR/util/SocketMultiplexer.cpp \ + $$MUSCLE_DIR/server/muscled.cpp \ + qt_muscled.cpp + +win32:SOURCES += $$MUSCLE_DIR/regex/regex/regcomp.c \ + $$MUSCLE_DIR/regex/regex/regerror.c \ + $$MUSCLE_DIR/regex/regex/regexec.c \ + $$MUSCLE_DIR/regex/regex/regfree.c + +HEADERS += qt_muscled.h diff --git a/qtsupport/qt_muscled_browser/Browser.cpp b/qtsupport/qt_muscled_browser/Browser.cpp new file mode 100644 index 00000000..4786f30c --- /dev/null +++ b/qtsupport/qt_muscled_browser/Browser.cpp @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Browser.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" // for ParseConnectArg() + +// Convenience macro +#define FromQ(qs) ((qs).toUtf8().constData()) + +using namespace muscle; + +// Items of this class each represent one node in the server-nodes treeview. +class NodeTreeWidgetItem : public QTreeWidgetItem +{ +public: + NodeTreeWidgetItem(QTreeWidget * parent) : QTreeWidgetItem(parent, QStringList("/")) {setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);} + NodeTreeWidgetItem(NodeTreeWidgetItem * parent, const String & name) : QTreeWidgetItem(parent, QStringList(name())), _name(name) {setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);} + + NodeTreeWidgetItem * GetChildByName(const String & name) + { + for (int i=childCount()-1; i>=0; i--) + { + NodeTreeWidgetItem * ch = static_cast(child(i)); + if (ch->GetName() == name) return ch; + } + return NULL; + } + + const String & GetName() const {return _name;} + + String GetPath() const + { + const NodeTreeWidgetItem * p = static_cast(parent()); + return p ? (p->GetPath()+"/"+_name) : GetEmptyString(); + } + + void DeleteChildren() + { + for (int i=childCount()-1; i>=0; i--) delete child(i); + } + +private: + String _name; +}; + +BrowserWindow :: BrowserWindow() : _isConnecting(false), _isConnected(false) +{ + setWindowTitle("MUSCLE Database Browser"); + + const int defaultWindowWidth = 640; + const int defaultWindowHeight = 400; + resize(defaultWindowWidth, defaultWindowHeight); + + QBoxLayout * vLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); + vLayout->setMargin(2); + vLayout->setSpacing(2); + + QWidget * topRow = new QWidget; + { + QBoxLayout * topRowLayout = new QBoxLayout(QBoxLayout::LeftToRight, topRow); + topRowLayout->setMargin(2); + + _stateLabel = new QLabel; + topRowLayout->addWidget(_stateLabel); + + _serverName = new QLineEdit; + _serverName->setText("localhost:2960"); + topRowLayout->addWidget(_serverName, 1); + + _connectButton = new QPushButton("Connect to Server"); + connect(_connectButton, SIGNAL(clicked()), this, SLOT(ConnectButtonClicked())); + topRowLayout->addWidget(_connectButton); + } + vLayout->addWidget(topRow); + + QSplitter * splitter = new QSplitter(Qt::Horizontal); + { + _nodeTree = new QTreeWidget; + _nodeTree->setHeaderLabels(QStringList("Server Node Tree")); + _nodeTree->setIndentation(15); + connect(_nodeTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(NodeExpanded(QTreeWidgetItem *))); + connect(_nodeTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(NodeCollapsed(QTreeWidgetItem *))); + connect(_nodeTree, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(SetMessageContentsViewContents(QTreeWidgetItem *))); + splitter->addWidget(_nodeTree); + + _messageContents = new QTextEdit; + _messageContents->setReadOnly(true); + splitter->addWidget(_messageContents); + + QList sizeList; + const int defaultTreeWidth = 200; + sizeList.push_back(defaultTreeWidth); + sizeList.push_back(defaultWindowWidth-defaultTreeWidth); + splitter->setSizes(sizeList); + } + vLayout->addWidget(splitter, 1); + + connect(&_mtt, SIGNAL(SessionConnected(const String &, const IPAddressAndPort &)), this, SLOT(ConnectedToServer())); + connect(&_mtt, SIGNAL(MessageReceived(const MessageRef &, const String &)), this, SLOT(MessageReceivedFromServer(const MessageRef &))); + connect(&_mtt, SIGNAL(SessionDisconnected(const String &)), this, SLOT(DisconnectedFromServer())); + + ConnectButtonClicked(); // might as well try to autoconnect on startup.... +} + +void BrowserWindow :: ConnectedToServer() +{ + _isConnected = true; + _isConnecting = false; + UpdateState(); + + ClearState(); + + // Tell the server we want to see our own nodes as well as everyone else's + MessageRef msg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + msg()->AddBool(PR_NAME_REFLECT_TO_SELF, true); + _mtt.SendMessageToSessions(msg); + + // Also upload a node to the server, just for fun + MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + { + MessageRef dataMsg = GetMessageFromPool(); + dataMsg()->AddString("timestamp", GetHumanReadableTimeString(GetRunTime64())); + uploadMsg()->AddMessage("connected_at", dataMsg); + } + _mtt.SendMessageToSessions(uploadMsg); + + // Start by subscribing to the children of the root node only + _nodeRoot = new NodeTreeWidgetItem(_nodeTree); // add the root node to the node tree + _nodeRoot->setExpanded(true); // calls SetNodeSubscribed("", true); +} + +void BrowserWindow :: SetMessageContentsViewContents(QTreeWidgetItem * item) +{ + QString t; + if (item) + { + String itemPath = (static_cast(item))->GetPath(); + MessageRef * m = _pathToMessage.Get(itemPath); + if (m) + { + t = QString("Message at path [%1] is:\n\n").arg(itemPath()); + t += m->GetItemPointer()->ToString()(); + } + else t = QString("Message at path [%1] isn't known").arg(itemPath()); + + _messageContentsPath = itemPath; + } + else _messageContentsPath.Clear(); + + _messageContents->setText(t); +} + +void BrowserWindow :: SetNodeSubscribed(const String & nodePath, bool isSubscribe) +{ + String subscribePath = nodePath+"/*"; + if (_subscriptions.ContainsKey(subscribePath) != isSubscribe) + { + if (isSubscribe) + { + MessageRef subMsg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + subMsg()->AddBool(subscribePath.Prepend("SUBSCRIBE:"), true); + _subscriptions.PutWithDefault(subscribePath); + printf("Subscribed to path [%s]\n", subscribePath()); + _mtt.SendMessageToSessions(subMsg); + + } + else + { + MessageRef unsubMsg = GetMessageFromPool(PR_COMMAND_REMOVEPARAMETERS); + unsubMsg()->AddString(PR_NAME_KEYS, EscapeRegexTokens(subscribePath).Prepend("SUBSCRIBE:")); + printf("Unsubscribed from path [%s]\n", subscribePath()); + _subscriptions.Remove(subscribePath); + + // Also remove from our tree of locally-cached data any nodes that start with this path + String removePath = nodePath + "/"; + for (HashtableIterator iter(_pathToMessage); iter.HasData(); iter++) if (iter.GetKey().StartsWith(removePath)) + { + _pathToMessage.Remove(iter.GetKey()); + printf(" Dropped node for [%s]\n", iter.GetKey()()); + } + + _mtt.SendMessageToSessions(unsubMsg); + } + } +} + +void BrowserWindow :: NodeExpanded(QTreeWidgetItem * node) +{ + String nodePath = static_cast(node)->GetPath(); + SetNodeSubscribed(nodePath, true); +} + +void BrowserWindow :: NodeCollapsed(QTreeWidgetItem * node) +{ + NodeTreeWidgetItem * ntwi = static_cast(node); + String subPath = ntwi->GetPath() + "/"; + for (HashtableIterator iter(_subscriptions); iter.HasData(); iter++) if (iter.GetKey().StartsWith(subPath)) SetNodeSubscribed(iter.GetKey().Substring(0, iter.GetKey().Length()-2), false); + ntwi->DeleteChildren(); +} + +void BrowserWindow :: ClearState() +{ + _nodeRoot = NULL; // paranoia + _subscriptions.Clear(); + _pathToMessage.Clear(); + _nodeTree->clear(); + _messageContents->clear(); +} + +NodeTreeWidgetItem * BrowserWindow :: GetNodeFromPath(const String & nodePath) +{ + if (nodePath.StartsWith("/") == false) return NULL; // all paths must start with "/" + return (nodePath.Length()>1) ? GetNodeFromPathAux(_nodeRoot, nodePath()+1) : _nodeRoot; +} + +NodeTreeWidgetItem * BrowserWindow :: GetNodeFromPathAux(NodeTreeWidgetItem * node, const char * path) +{ + const char * slash = strchr(path, '/'); + NodeTreeWidgetItem * child = node->GetChildByName(String(path, slash?(slash-path):MUSCLE_NO_LIMIT)); + return child ? (slash ? GetNodeFromPathAux(child, slash+1) : child) : NULL; +} + +// Add or remove the modified node from its parent in the tree view, as necessary +void BrowserWindow :: UpdateDataNodeInTreeView(const String & nodePath) +{ + int lastSlash = nodePath.LastIndexOf('/'); + if (lastSlash >= 0) + { + String parentPath = nodePath.Substring(0, lastSlash); + if (parentPath.Length() == 0) parentPath = "/"; + + NodeTreeWidgetItem * parentItem = GetNodeFromPath(parentPath); + if ((parentItem)&&(parentItem->isExpanded())) + { + NodeTreeWidgetItem * curItem = GetNodeFromPath(nodePath); + if (_pathToMessage.ContainsKey(nodePath)) + { + if (curItem == NULL) (void) new NodeTreeWidgetItem(parentItem, nodePath.Substring("/")); + } + else delete curItem; + } + } + if (nodePath == _messageContentsPath) SetMessageContentsViewContents(GetNodeFromPath(nodePath)); +} + +void BrowserWindow :: MessageReceivedFromServer(const MessageRef & msg) +{ + switch(msg()->what) + { + case PR_RESULT_DATAITEMS: + { + // Look for strings that indicate that subscribed nodes were removed from the tree + { + const String * nodePath; + for (int i=0; (msg()->FindString(PR_NAME_REMOVED_DATAITEMS, i, &nodePath) == B_NO_ERROR); i++) + { + if (_pathToMessage.Remove(*nodePath) == B_NO_ERROR) + { + UpdateDataNodeInTreeView(*nodePath); + printf("Removed node at [%s]\n", nodePath->Cstr()); + } + } + } + + // And then look for sub-messages that represend subscribed nodes that were added to the tree, or updated in-place + { + for (MessageFieldNameIterator iter = msg()->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + const String & nodePath = iter.GetFieldName(); + MessageRef data; + for (uint32 i=0; msg()->FindMessage(nodePath, i, data) == B_NO_ERROR; i++) + { + if (_pathToMessage.Put(nodePath, data) == B_NO_ERROR) + { + UpdateDataNodeInTreeView(nodePath); + printf(" Added/Updated node at [%s]\n", nodePath()); + } + } + } + } + } + break; + } +} + +void BrowserWindow :: DisconnectedFromServer() +{ + _isConnected = _isConnecting = false; + ClearState(); + UpdateState(); +} + +void BrowserWindow :: ConnectButtonClicked() +{ + bool wasConnecting = ((_isConnected)||(_isConnecting)); + _isConnected = _isConnecting = false; + _mtt.Reset(); + + if (wasConnecting == false) + { + String serverName; + uint16 port = 2960; + if ((ParseConnectArg(FromQ(_serverName->text()), serverName, port) == B_NO_ERROR)&&(_mtt.AddNewConnectSession(serverName, port) == B_NO_ERROR)&&(_mtt.StartInternalThread() == B_NO_ERROR)) _isConnecting = true; + } + UpdateState(); +} + +void BrowserWindow :: UpdateState() +{ + if (_isConnected) + { + _stateLabel->setText("Connected to:"); + _connectButton->setText("Disconnect from Server"); + _serverName->setReadOnly(true); + } + else if (_isConnecting) + { + _stateLabel->setText("Connecting to:"); + _connectButton->setText("Cancel Connection"); + _serverName->setReadOnly(true); + } + else + { + _stateLabel->setText("Not Connected to:"); + _connectButton->setText("Connect to Server"); + _serverName->setReadOnly(false); + } + + _nodeTree->setVisible(_isConnected); + _messageContents->setVisible(_isConnected); +} + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + QApplication app(argc, argv); + + BrowserWindow bw; + bw.show(); + return app.exec(); +} diff --git a/qtsupport/qt_muscled_browser/Browser.h b/qtsupport/qt_muscled_browser/Browser.h new file mode 100644 index 00000000..c6a85aed --- /dev/null +++ b/qtsupport/qt_muscled_browser/Browser.h @@ -0,0 +1,58 @@ +#ifndef Browser_h +#define Browser_h + +#include +#include "qtsupport/QMessageTransceiverThread.h" + +class QLabel; +class QPushButton; +class QTreeWidget; +class QLineEdit; +class NodeTreeWidgetItem; +class QTextEdit; +class QTreeWidgetItem; + +using namespace muscle; + +class BrowserWindow : public QWidget +{ +Q_OBJECT + +public: + BrowserWindow(); + +private slots: + void ConnectButtonClicked(); + void ConnectedToServer(); + void MessageReceivedFromServer(const MessageRef &); + void DisconnectedFromServer(); + void NodeExpanded(QTreeWidgetItem *); + void NodeCollapsed(QTreeWidgetItem *); + void SetMessageContentsViewContents(QTreeWidgetItem *); + +private: + void UpdateState(); + void ClearState(); + void SetNodeSubscribed(const String & nodePath, bool subscribe); + void UpdateDataNodeInTreeView(const String & nodePath); + + NodeTreeWidgetItem * GetNodeFromPathAux(NodeTreeWidgetItem * node, const char * nodePath); + NodeTreeWidgetItem * GetNodeFromPath(const String & nodePath); + + QLabel * _stateLabel; + QLineEdit * _serverName; + QPushButton * _connectButton; + QTreeWidget * _nodeTree; + QTextEdit * _messageContents; + NodeTreeWidgetItem * _nodeRoot; + String _messageContentsPath; + + Hashtable _subscriptions; // our current set of subscription strings + Hashtable _pathToMessage; // our current set of known Node states + QMessageTransceiverThread _mtt; + + bool _isConnecting; + bool _isConnected; +}; + +#endif diff --git a/qtsupport/qt_muscled_browser/README.txt b/qtsupport/qt_muscled_browser/README.txt new file mode 100644 index 00000000..5c795430 --- /dev/null +++ b/qtsupport/qt_muscled_browser/README.txt @@ -0,0 +1,17 @@ +This directory contains a Qt client program that lets you browse the contents of a muscled server +interactively in the GUI. The client uses live subscriptions to generate its display, so the +display will always show the current contents of the server. + +To compile under MacOS/X of Linux: + + qmake; make + +To compile under Windows: + + qmake; nmake + +Then run the application from its icon, or by executing it from Terminal with no arguments. + +-Jeremy + +ps Thanks to Jean-François Mullet for the idea for this program, and the original implementation of it. diff --git a/qtsupport/qt_muscled_browser/qt_muscled_browser.pro b/qtsupport/qt_muscled_browser/qt_muscled_browser.pro new file mode 100644 index 00000000..290beab0 --- /dev/null +++ b/qtsupport/qt_muscled_browser/qt_muscled_browser.pro @@ -0,0 +1,65 @@ +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} +win32:LIBS += shlwapi.lib ws2_32.lib winmm.lib User32.lib Advapi32.lib shell32.lib iphlpapi.lib version.lib +unix:!mac:LIBS += -lutil -lrt -lz +mac:LIBS += -lz -framework Carbon + +win32:DEFINES += _WIN32_WINNT=0x0501 + +!win32:LIBS += -lpthread + +OBJECTS_DIR = objects +MUSCLE_DIR = ../../ + +DEFINES += MUSCLE_ENABLE_ZLIB_ENCODING +DEFINES += MUSCLE_AVOID_NEWNOTHROW +DEFINES += MUSCLE_AVOID_SIGNAL_HANDLING + +!win32:DEFINES += MUSCLE_USE_PTHREADS + +INCLUDEPATH += $$MUSCLE_DIR +win32:INCLUDEPATH += $$MUSCLE_DIR/regex/regex $$MUSCLE_DIR/zlib/zlib/win32 + +SOURCES += $$MUSCLE_DIR/message/Message.cpp \ + $$MUSCLE_DIR/iogateway/MessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/AbstractMessageIOGateway.cpp \ + $$MUSCLE_DIR/iogateway/PlainTextMessageIOGateway.cpp \ + $$MUSCLE_DIR/syslog/SysLog.cpp \ + $$MUSCLE_DIR/system/SetupSystem.cpp \ + $$MUSCLE_DIR/system/MessageTransceiverThread.cpp \ + $$MUSCLE_DIR/system/Thread.cpp \ + $$MUSCLE_DIR/system/SignalMultiplexer.cpp \ + $$MUSCLE_DIR/system/GlobalMemoryAllocator.cpp \ + $$MUSCLE_DIR/zlib/ZLibCodec.cpp \ + $$MUSCLE_DIR/regex/StringMatcher.cpp \ + $$MUSCLE_DIR/regex/QueryFilter.cpp \ + $$MUSCLE_DIR/regex/PathMatcher.cpp \ + $$MUSCLE_DIR/regex/SegmentedStringMatcher.cpp \ + $$MUSCLE_DIR/reflector/AbstractReflectSession.cpp \ + $$MUSCLE_DIR/reflector/SignalHandlerSession.cpp \ + $$MUSCLE_DIR/reflector/StorageReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DumbReflectSession.cpp \ + $$MUSCLE_DIR/reflector/DataNode.cpp \ + $$MUSCLE_DIR/reflector/ReflectServer.cpp \ + $$MUSCLE_DIR/reflector/FilterSessionFactory.cpp \ + $$MUSCLE_DIR/reflector/RateLimitSessionIOPolicy.cpp \ + $$MUSCLE_DIR/reflector/ServerComponent.cpp \ + $$MUSCLE_DIR/util/MemoryAllocator.cpp \ + $$MUSCLE_DIR/util/Directory.cpp \ + $$MUSCLE_DIR/util/FilePathInfo.cpp \ + $$MUSCLE_DIR/util/MiscUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/ByteBuffer.cpp \ + $$MUSCLE_DIR/util/String.cpp \ + $$MUSCLE_DIR/util/NetworkUtilityFunctions.cpp \ + $$MUSCLE_DIR/util/PulseNode.cpp \ + $$MUSCLE_DIR/util/SocketMultiplexer.cpp \ + $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.cpp \ + Browser.cpp + +win32:SOURCES += $$MUSCLE_DIR/regex/regex/regcomp.c \ + $$MUSCLE_DIR/regex/regex/regerror.c \ + $$MUSCLE_DIR/regex/regex/regexec.c \ + $$MUSCLE_DIR/regex/regex/regfree.c + +HEADERS += $$MUSCLE_DIR/qtsupport/QMessageTransceiverThread.h Browser.h diff --git a/reflector/AbstractReflectSession.cpp b/reflector/AbstractReflectSession.cpp new file mode 100644 index 00000000..239a2071 --- /dev/null +++ b/reflector/AbstractReflectSession.cpp @@ -0,0 +1,462 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/AbstractReflectSession.h" +#include "reflector/AbstractSessionIOPolicy.h" +#include "reflector/ReflectServer.h" +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "system/Mutex.h" +#include "system/SetupSystem.h" + +#ifdef MUSCLE_ENABLE_SSL +# include "dataio/SSLSocketDataIO.h" +# include "iogateway/SSLSocketAdapterGateway.h" +#endif + +namespace muscle { + +static uint32 _sessionIDCounter = 0L; +static uint32 _factoryIDCounter = 0L; + +static uint32 GetNextGlobalID(uint32 & counter) +{ + uint32 ret; + + Mutex * ml = GetGlobalMuscleLock(); + MASSERT(ml, "Please instantiate a CompleteSetupSystem object on the stack before creating any session or session-factory objects (at beginning of main() is preferred)\n"); + + if (ml->Lock() == B_NO_ERROR) + { + ret = counter++; + ml->Unlock(); + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Could not lock global muscle lock while assigning new ID!!?!\n"); + ret = counter++; // do it anyway, I guess + } + return ret; +} + +ReflectSessionFactory :: ReflectSessionFactory() +{ + TCHECKPOINT; + _id = GetNextGlobalID(_factoryIDCounter); +} + +status_t ProxySessionFactory :: AttachedToServer() +{ + if (ReflectSessionFactory::AttachedToServer() != B_NO_ERROR) return B_ERROR; + + status_t ret = B_NO_ERROR; + if (_slaveRef()) + { + _slaveRef()->SetOwner(GetOwner()); + ret = _slaveRef()->AttachedToServer(); + if (ret == B_NO_ERROR) _slaveRef()->SetFullyAttachedToServer(true); + else _slaveRef()->SetOwner(NULL); + } + return ret; +} + +void ProxySessionFactory :: AboutToDetachFromServer() +{ + if (_slaveRef()) + { + _slaveRef()->SetFullyAttachedToServer(false); + _slaveRef()->AboutToDetachFromServer(); + _slaveRef()->SetOwner(NULL); + } + ReflectSessionFactory::AboutToDetachFromServer(); +} + +AbstractReflectSession :: +AbstractReflectSession() : _sessionID(GetNextGlobalID(_sessionIDCounter)), _connectingAsync(false), _isConnected(false), _maxAsyncConnectPeriod(MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS), _asyncConnectTimeoutTime(MUSCLE_TIME_NEVER), _lastByteOutputAt(0), _maxInputChunk(MUSCLE_NO_LIMIT), _maxOutputChunk(MUSCLE_NO_LIMIT), _outputStallLimit(MUSCLE_TIME_NEVER), _autoReconnectDelay(MUSCLE_TIME_NEVER), _reconnectTime(MUSCLE_TIME_NEVER), _wasConnected(false), _isExpendable(false) +{ + char buf[64]; sprintf(buf, UINT32_FORMAT_SPEC, _sessionID); + _idString = buf; +} + +AbstractReflectSession :: +~AbstractReflectSession() +{ + TCHECKPOINT; + SetInputPolicy(AbstractSessionIOPolicyRef()); // make sure the input policy knows we're going away + SetOutputPolicy(AbstractSessionIOPolicyRef()); // make sure the output policy knows we're going away +} + +const String & +AbstractReflectSession :: +GetHostName() const +{ + MASSERT(IsAttachedToServer(), "Can not call GetHostName() while not attached to the server"); + return _hostName; +} + +uint16 +AbstractReflectSession :: +GetPort() const +{ + MASSERT(IsAttachedToServer(), "Can not call GetPort() while not attached to the server"); + return _ipAddressAndPort.GetPort(); +} + +const ip_address & +AbstractReflectSession :: +GetLocalInterfaceAddress() const +{ + MASSERT(IsAttachedToServer(), "Can not call LocalInterfaceAddress() while not attached to the server"); + return _ipAddressAndPort.GetIPAddress(); +} + +status_t +AbstractReflectSession :: +AddOutgoingMessage(const MessageRef & ref) +{ + MASSERT(IsAttachedToServer(), "Can not call AddOutgoingMessage() while not attached to the server"); + return (_gateway()) ? _gateway()->AddOutgoingMessage(ref) : B_ERROR; +} + +status_t +AbstractReflectSession :: +Reconnect() +{ + TCHECKPOINT; + +#ifdef MUSCLE_ENABLE_SSL + ConstByteBufferRef publicKey; // If it's an SSL connection we'll grab its key into here so we can reuse it +#endif + + MASSERT(IsAttachedToServer(), "Can not call Reconnect() while not attached to the server"); + if (_gateway()) + { +#ifdef MUSCLE_ENABLE_SSL + SSLSocketDataIO * sdio = dynamic_cast(_gateway()->GetDataIO()()); + if (sdio) publicKey = sdio->GetPublicKeyCertificate(); +#endif + _gateway()->SetDataIO(DataIORef()); // get rid of any existing socket first + _gateway()->Reset(); // set gateway back to its virgin state + } + + _isConnected = _wasConnected = false; + SetConnectingAsync(false); + + bool doTCPConnect = ((_reconnectViaTCP)&&(_asyncConnectDest.GetIPAddress() != invalidIP)); + bool isReady = false; + ConstSocketRef sock = doTCPConnect ? ConnectAsync(_asyncConnectDest.GetIPAddress(), _asyncConnectDest.GetPort(), isReady) : CreateDefaultSocket(); + + // FogBugz #5256: If ConnectAsync() fails, we want to act as if it succeeded, so that the calling + // code still uses its normal asynchronous-connect-failure code path. That way the + // caller doesn't have to worry about synchronous failure as a separate case. + if ((doTCPConnect)&&(sock() == NULL)) + { + ConstSocketRef tempSockRef; // tempSockRef represents the closed remote end of the failed connection and is intentionally closed ASAP + if (CreateConnectedSocketPair(sock, tempSockRef) == B_NO_ERROR) doTCPConnect = false; + } + + if (sock()) + { + DataIORef io = CreateDataIO(sock); + if (io()) + { + if (_gateway() == NULL) + { + _gateway = CreateGateway(); + if (_gateway() == NULL) return B_ERROR; + } + +#ifdef MUSCLE_ENABLE_SSL + // auto-wrap the user's gateway and socket in the necessary SSL adapters! + if ((publicKey())&&(dynamic_cast(io()) != NULL)) + { + SSLSocketDataIO * ssio = newnothrow SSLSocketDataIO(sock, false, false); + if (ssio == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + io.SetRef(ssio); + if (ssio->SetPublicKeyCertificate(publicKey) != B_NO_ERROR) return B_ERROR; + + if (dynamic_cast(_gateway()) == NULL) + { + _gateway.SetRef(newnothrow SSLSocketAdapterGateway(_gateway)); + if (_gateway() == NULL) return B_ERROR; + } + } +#endif + + _gateway()->SetDataIO(io); + if (isReady) + { + _isConnected = _wasConnected = true; + AsyncConnectCompleted(); + } + else + { + _isConnected = false; + SetConnectingAsync(doTCPConnect); + } + _scratchReconnected = true; // tells ReflectServer not to shut down our new IO! + return B_NO_ERROR; + } + } + return B_ERROR; +} + +ConstSocketRef +AbstractReflectSession :: +CreateDefaultSocket() +{ + return ConstSocketRef(); // NULL Ref means run clientless by default +} + +DataIORef +AbstractReflectSession :: +CreateDataIO(const ConstSocketRef & socket) +{ + DataIORef dio(newnothrow TCPSocketDataIO(socket, false)); + if (dio() == NULL) WARN_OUT_OF_MEMORY; + return dio; +} + +AbstractMessageIOGatewayRef +AbstractReflectSession :: +CreateGateway() +{ + AbstractMessageIOGateway * gw = newnothrow MessageIOGateway(); + if (gw == NULL) WARN_OUT_OF_MEMORY; + return AbstractMessageIOGatewayRef(gw); +} + +bool +AbstractReflectSession :: +ClientConnectionClosed() +{ + if (_autoReconnectDelay == MUSCLE_TIME_NEVER) return true; // true == okay to remove this session + else + { + if (_wasConnected) LogTime(MUSCLE_LOG_DEBUG, "%s: Connection severed, will auto-reconnect in " UINT64_FORMAT_SPEC "mS\n", GetSessionDescriptionString()(), MicrosToMillis(_autoReconnectDelay)); + PlanForReconnect(); + return false; + } +} + +void +AbstractReflectSession :: +BroadcastToAllSessions(const MessageRef & msgRef, void * userData, bool toSelf) +{ + TCHECKPOINT; + + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + AbstractReflectSession * session = iter.GetValue()(); + if ((session)&&((toSelf)||(session != this))) session->MessageReceivedFromSession(*this, msgRef, userData); + } +} + +void +AbstractReflectSession :: +BroadcastToAllFactories(const MessageRef & msgRef, void * userData) +{ + TCHECKPOINT; + + for (HashtableIterator iter(GetFactories()); iter.HasData(); iter++) + { + ReflectSessionFactory * factory = iter.GetValue()(); + if (factory) factory->MessageReceivedFromSession(*this, msgRef, userData); + } +} + +void AbstractReflectSession :: AsyncConnectCompleted() +{ + // These sets are mostly redundant, since typically ReflectServer sets these variables + // directly before calling AsyncConnectCompleted()... but if third party code is calling + // AsyncConnectCompleted(), then we might as well make sure they are set from that context + // also. + _isConnected = _wasConnected = true; +} + +void AbstractReflectSession :: SetInputPolicy(const AbstractSessionIOPolicyRef & newRef) {SetPolicyAux(_inputPolicyRef, _maxInputChunk, newRef, true);} +void AbstractReflectSession :: SetOutputPolicy(const AbstractSessionIOPolicyRef & newRef) {SetPolicyAux(_outputPolicyRef, _maxOutputChunk, newRef, true);} +void AbstractReflectSession :: SetPolicyAux(AbstractSessionIOPolicyRef & myRef, uint32 & chunk, const AbstractSessionIOPolicyRef & newRef, bool isInput) +{ + TCHECKPOINT; + + if (newRef != myRef) + { + PolicyHolder ph(this, isInput); + if (myRef()) myRef()->PolicyHolderRemoved(ph); + myRef = newRef; + chunk = myRef() ? 0 : MUSCLE_NO_LIMIT; // sensible default to use until my policy gets its say about what we should do + if (myRef()) myRef()->PolicyHolderAdded(ph); + } +} + +status_t +AbstractReflectSession :: +ReplaceSession(const AbstractReflectSessionRef & replaceMeWithThis) +{ + MASSERT(IsAttachedToServer(), "Can not call ReplaceSession() while not attached to the server"); + return GetOwner()->ReplaceSession(replaceMeWithThis, this); +} + +void +AbstractReflectSession :: +EndSession() +{ + if (IsAttachedToServer()) GetOwner()->EndSession(this); +} + +bool +AbstractReflectSession :: +DisconnectSession() +{ + MASSERT(IsAttachedToServer(), "Can not call DisconnectSession() while not attached to the server"); + return GetOwner()->DisconnectSession(this); +} + +String +AbstractReflectSession :: +GetDefaultHostName() const +{ + return "_unknown_"; // This used to be "" but that interfered with MUSCLE's pattern matching that looks for a number range between angle brackets! --jaf +} + +String +AbstractReflectSession :: +GetSessionDescriptionString() const +{ + uint16 port = _ipAddressAndPort.GetPort(); + + String ret = GetTypeName(); + ret += " "; + ret += GetSessionIDString(); + ret += (port>0)?" at [":" to ["; + ret += _hostName; + char buf[64]; sprintf(buf, ":%u]", (port>0)?port:_asyncConnectDest.GetPort()); ret += buf; + return ret; +} + + +bool +AbstractReflectSession :: +HasBytesToOutput() const +{ + return _gateway() ? _gateway()->HasBytesToOutput() : false; +} + +bool +AbstractReflectSession :: +IsReadyForInput() const +{ + return _gateway() ? _gateway()->IsReadyForInput() : false; +} + +int32 +AbstractReflectSession :: +DoInput(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes) +{ + return _gateway() ? _gateway()->DoInput(receiver, maxBytes) : 0; +} + +int32 +AbstractReflectSession :: +DoOutput(uint32 maxBytes) +{ + TCHECKPOINT; + + return _gateway() ? _gateway()->DoOutput(maxBytes) : 0; +} + +void +ReflectSessionFactory :: +BroadcastToAllSessions(const MessageRef & msgRef, void * userData) +{ + TCHECKPOINT; + + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + AbstractReflectSession * session = iter.GetValue()(); + if (session) session->MessageReceivedFromFactory(*this, msgRef, userData); + } +} + +void +ReflectSessionFactory :: +BroadcastToAllFactories(const MessageRef & msgRef, void * userData, bool toSelf) +{ + TCHECKPOINT; + + for (HashtableIterator iter(GetFactories()); iter.HasData(); iter++) + { + ReflectSessionFactory * factory = iter.GetValue()(); + if ((factory)&&((toSelf)||(factory != this))) factory->MessageReceivedFromFactory(*this, msgRef, userData); + } +} + +void +AbstractReflectSession :: +PlanForReconnect() +{ + _reconnectTime = (_autoReconnectDelay == MUSCLE_TIME_NEVER) ? MUSCLE_TIME_NEVER : (GetRunTime64()+_autoReconnectDelay); + InvalidatePulseTime(); +} + +uint64 +AbstractReflectSession :: +GetPulseTime(const PulseArgs &) +{ + return muscleMin(_reconnectTime, _asyncConnectTimeoutTime); +} + +void +AbstractReflectSession :: +Pulse(const PulseArgs & args) +{ + PulseNode::Pulse(args); + if (args.GetCallbackTime() >= _reconnectTime) + { + if (_autoReconnectDelay == MUSCLE_TIME_NEVER) _reconnectTime = MUSCLE_TIME_NEVER; + else + { + // FogBugz #3810 + if (_wasConnected) LogTime(MUSCLE_LOG_DEBUG, "%s is attempting to auto-reconnect...\n", GetSessionDescriptionString()()); + _reconnectTime = MUSCLE_TIME_NEVER; + if (Reconnect() != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_DEBUG, "%s: Could not auto-reconnect, will try again later...\n", GetSessionDescriptionString()()); + PlanForReconnect(); // okay, we'll try again later! + } + } + } + else if ((IsConnectingAsync())&&(args.GetCallbackTime() >= _asyncConnectTimeoutTime)) (void) DisconnectSession(); // force us to terminate our async-connect now +} + +void AbstractReflectSession :: SetConnectingAsync(bool isConnectingAsync) +{ + _connectingAsync = isConnectingAsync; + _asyncConnectTimeoutTime = ((_connectingAsync)&&(_maxAsyncConnectPeriod != MUSCLE_TIME_NEVER)) ? (GetRunTime64()+_maxAsyncConnectPeriod) : MUSCLE_TIME_NEVER; + InvalidatePulseTime(); +} + +const DataIORef & +AbstractReflectSession :: +GetDataIO() const +{ + return _gateway() ? _gateway()->GetDataIO() : GetDefaultObjectForType(); +} + +const ConstSocketRef & +AbstractReflectSession :: +GetSessionReadSelectSocket() const +{ + const DataIORef & dio = GetDataIO(); + return dio() ? dio()->GetReadSelectSocket() : GetNullSocket(); +} + +const ConstSocketRef & +AbstractReflectSession :: +GetSessionWriteSelectSocket() const +{ + const DataIORef & dio = GetDataIO(); + return dio() ? dio()->GetWriteSelectSocket() : GetNullSocket(); +} + +}; // end namespace muscle diff --git a/reflector/AbstractReflectSession.h b/reflector/AbstractReflectSession.h new file mode 100644 index 00000000..547ff2ea --- /dev/null +++ b/reflector/AbstractReflectSession.h @@ -0,0 +1,520 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAbstractReflectSession_h +#define MuscleAbstractReflectSession_h + +#include "iogateway/AbstractMessageIOGateway.h" +#include "reflector/AbstractSessionIOPolicy.h" +#include "reflector/ServerComponent.h" +#include "support/TamperEvidentValue.h" +#include "util/Queue.h" +#include "util/RefCount.h" + +namespace muscle { + +/** This is an interface for an object that knows how to create new + * AbstractReflectSession objects when needed. It is used by the + * ReflectServer classes to generate sessions when connections are received. + */ +class ReflectSessionFactory : public ServerComponent, private CountedObject +{ +public: + /** Constructor. Our globally unique factory ID is assigned here. */ + ReflectSessionFactory(); + + /** Destructor */ + virtual ~ReflectSessionFactory() {/* empty */} + + /** Should be overriden to return a new ReflectSession object, or NULL on failure. + * @param clientAddress A string representing the connecting client's host (typically an IP address, e.g. "192.168.1.102") + * @param factoryInfo The IP address and port number of the local network interface on which this connection was received. + * @returns a reference to a freshly allocated AbstractReflectSession object on success, or a NULL reference on failure. + */ + virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & factoryInfo) = 0; + + /** + * Should return true iff this factory is currently ready to accept connections. + * This method is called each time through the event loop, and if this method + * is overridden to return false, then this factory will not be included in the + * select() set and any incoming TCP connections on this factory's port will + * not be acted upon (i.e. they will be forced to wait). + * Default implementation always returns true. + */ + virtual bool IsReadyToAcceptSessions() const {return true;} + + /** + * Returns an auto-assigned ID value that represents this factory. + * The returned value is guaranteed to be unique across all factories in the server. + */ + uint32 GetFactoryID() const {return _id;} + +protected: + /** + * Convenience method: Calls MessageReceivedFromFactory() on all session + * objects. Saves you from having to do your own iteration every time you + * want to broadcast something. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + */ + void BroadcastToAllSessions(const MessageRef & msgRef, void * userData = NULL); + + /** + * Convenience method: Calls MessageReceivedFromFactory() on all session + * objects of the specified type. Saves you from having to do your own iteration every time you + * want to broadcast something. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + */ + template void BroadcastToAllSessionsOfType(const MessageRef & msgRef, void * userData = NULL) + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + SessionType * session = dynamic_cast(iter.GetValue()()); + if (session) session->MessageReceivedFromFactory(*this, msgRef, userData); + } + } + + /** + * Convenience method: Calls MessageReceivedFromFactory() on all session-factory + * objects. Saves you from having to do your own iteration every time you + * want to broadcast something to the factories. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + * @param includeSelf Whether or not MessageReceivedFromFactory() should be called on 'this' factory. Defaults to true. + */ + void BroadcastToAllFactories(const MessageRef & msgRef, void * userData = NULL, bool includeSelf = true); + +private: + uint32 _id; +}; + +/** This is a partially specialized factory that knows how to act as a facade for a "slave" factory. + * In particular, it contains implementations of AttachedToServer() and AboutToDetachFromServer() + * that set up and tear down the slave factory appropriately. This way that logic doesn't have + * to be replicated in every ReflectSessionFactory subclass that wants to use the facade pattern. + */ +class ProxySessionFactory : public ReflectSessionFactory +{ +public: + /** Constructor. Makes this factory a facade for (slaveRef) + * @param slaveRef the child factor that we will pass our calls on to. + */ + ProxySessionFactory(const ReflectSessionFactoryRef & slaveRef) : _slaveRef(slaveRef) {/* empty */} + virtual ~ProxySessionFactory() {/* empty */} + + virtual status_t AttachedToServer(); + virtual void AboutToDetachFromServer(); + virtual bool IsReadyToAcceptSessions() const {return _slaveRef() ? _slaveRef()->IsReadyToAcceptSessions() : true;} + + /** Returns the reference to the "slave" factory that was passed in to our constructor. */ + const ReflectSessionFactoryRef & GetSlave() const {return _slaveRef;} + +private: + ReflectSessionFactoryRef _slaveRef; +}; + +/** This is the abstract base class that defines the server side logic for a single + * client-server connection. This class contains no message routing logic of its own, + * but defines the interface so that subclasses can do so. + */ +class AbstractReflectSession : public ServerComponent, public AbstractGatewayMessageReceiver, private CountedObject +{ +public: + /** Default Constructor. */ + AbstractReflectSession(); + + /** Destructor. */ + virtual ~AbstractReflectSession(); + + /** Returns the hostname of this client that is associated with this session. + * May only be called if this session is currently attached to a ReflectServer. + */ + const String & GetHostName() const; + + /** Returns the server-side port that this session was accepted on, or 0 if + * we weren't accepted from a port (e.g. we were created locally) + * May only be called if this session is currently attached to a ReflectServer. + */ + uint16 GetPort() const; + + /** Returns the server-side network interface IP that this session was accepted on, + * or 0 if we weren't created via accepting a network connection (e.g. we were created locally) + * May only be called if this session is currently attached to a ReflectServer. + */ + const ip_address & GetLocalInterfaceAddress() const; + + /** Returns a globally unique ID for this session. */ + uint32 GetSessionID() const {return _sessionID;} + + /** + * Returns an ID string to represent this session with. + * (This string is the ASCII representation of GetSessionID()) + */ + const String & GetSessionIDString() const {return _idString;} + + /** Marks this session for immediate termination and removal from the server. */ + virtual void EndSession(); + + /** Forces the disconnection of this session's TCP connection to its client. + * Calling this will cause ClientConnectionClosed() to be called, as if the + * TCP connection had been severed externally. + * @returns the value that ClientConnectionClosed() returned. + */ + bool DisconnectSession(); + + /** + * Causes this session to be terminated (similar to EndSession(), + * and the session specified in (newSessionRef) to take its + * place using the same socket connection & message IO gateway. + * @param newSession the new session object that is to take the place of this one. + * @return B_NO_ERROR on success, B_ERROR if the new session refused to be attached. + */ + status_t ReplaceSession(const AbstractReflectSessionRef & newSession); + + /** + * Called when the TCP connection to our client is broken. + * If this method returns true, then this session will be removed and + * deleted. + * @return If it returns false, then this session will continue, even + * though the client is no longer available. Default implementation + * always returns true, unless the automatic-reconnect feature has + * been enabled (via SetAutoReconnectDelay()), in which case this + * method will return false and try to Reconnect() again, instead. + */ + virtual bool ClientConnectionClosed(); + + /** + * For sessions that were added to the server with AddNewConnectSession(), + * or AddNewDormantConnectSession(), this method is called when the asynchronous + * connect process completes successfully. (if the asynchronous connect fails, + * ClientConnectionClosed() is called instead). Default implementation just sets + * an internal flag that governs whether an error message should be printed when + * the session is disconnected later on. + */ + virtual void AsyncConnectCompleted(); + + /** + * Set a new input I/O policy for this session. + * @param newPolicy Reference to the new policy to use to control the incoming byte stream + * for this session. May be a NULL reference if you just want to remove the existing policy. + */ + void SetInputPolicy(const AbstractSessionIOPolicyRef & newPolicy); + + /** Returns a reference to the current input policy for this session. + * May be a NULL reference, if there is no input policy installed (which is the default state) + */ + AbstractSessionIOPolicyRef GetInputPolicy() const {return _inputPolicyRef;} + + /** + * Set a new output I/O policy for this session. + * @param newPolicy Reference to the new policy to use to control the outgoing byte stream + * for this session. May be a NULL reference if you just want to remove the existing policy. + */ + void SetOutputPolicy(const AbstractSessionIOPolicyRef & newPolicy); + + /** Returns a reference to the current output policy for this session. May be a NULL reference. + * May be a NULL reference, if there is no output policy installed (which is the default state) + */ + AbstractSessionIOPolicyRef GetOutputPolicy() const {return _outputPolicyRef;} + + /** Installs the given AbstractMessageIOGateway as the gateway we should use for I/O. + * If this method isn't called, the ReflectServer will call our CreateGateway() method + * to set our gateway for us when we are attached. + * @param ref Reference to the I/O gateway to use, or a NULL reference to remove any gateway we have. + */ + void SetGateway(const AbstractMessageIOGatewayRef & ref) {_gateway = ref; _outputStallLimit = _gateway()?_gateway()->GetOutputStallLimit():MUSCLE_TIME_NEVER;} + + /** + * Returns a reference to our internally held message IO gateway object, + * or NULL reference if there is none. The returned gateway remains + * the property of this session. + */ + const AbstractMessageIOGatewayRef & GetGateway() const {return _gateway;} + + /** + * Convenience method: returns a reference DataIO object attached to this session's + * current gateway object, or NULL if there isn't one. + */ + const DataIORef & GetDataIO() const; + + /** Should return true iff we have data pending for output. + * Default implementation calls HasBytesToOutput() on our installed AbstractDataIOGateway object, + * if we have one, or returns false if we don't. + */ + virtual bool HasBytesToOutput() const; + + /** + * Should return true iff we are willing to read more bytes from our + * client connection at this time. Default implementation calls + * IsReadyForInput() on our install AbstractDataIOGateway object, if we + * have one, or returns false if we don't. + * + */ + virtual bool IsReadyForInput() const; + + /** Called by the ReflectServer when it wants us to read some more bytes from our client. + * Default implementation simply calls DoInput() on our Gateway object (if any). + * @param receiver Object to call CallMessageReceivedFromGateway() on when new Messages are ready to be looked at. + * @param maxBytes Maximum number of bytes to read before returning. + * @returns The total number of bytes read, or -1 if there was a fatal error. + */ + virtual int32 DoInput(AbstractGatewayMessageReceiver & receiver, uint32 maxBytes); + + /** Called by the ReflectServer when it wants us to push some more bytes out to our client. + * Default implementation simply calls DoOutput() on our Gateway object (if any). + * @param maxBytes Maximum number of bytes to write before returning. + * @returns The total number of bytes written, or -1 if there was a fatal error. + */ + virtual int32 DoOutput(uint32 maxBytes); + + /** Socket factory method. This method is called by AddNewSession() when + * no valid Socket was supplied as an argument to the AddNewSession() call. + * This method should either create and supply a default Socket, or return + * a NULL ConstSocketRef. In the latter case, the session will run without any + * connection to a client. + * Default implementation always returns a NULL ConstSocketRef. + */ + virtual ConstSocketRef CreateDefaultSocket(); + + /** DataIO factory method. Should return a new non-blocking DataIO + * object for our gateway to use, or NULL on failure. Called by + * ReflectServer when this session is added to the server. + * The default implementation returns a non-blocking TCPSocketDataIO + * object, which is the correct behaviour 99% of the time. + * @param socket The socket to provide the DataIO object for. + * On success, the DataIO object becomes owner of (socket). + * @return A newly allocated DataIO object, or NULL on failure. + */ + virtual DataIORef CreateDataIO(const ConstSocketRef & socket); + + /** + * Gateway factory method. Should return a reference to a new + * AbstractMessageIOGateway for this session to use for communicating + * with its remote peer. + * Called by ReflectServer when this session object is added to the + * server, but doesn't already have a valid gateway installed. + * The default implementation returns a MessageIOGateway object. + * @return a new message IO gateway object, or a NULL reference on failure. + */ + virtual AbstractMessageIOGatewayRef CreateGateway(); + + /** Overridden to support auto-reconnect via SetAutoReconnectDelay() */ + virtual uint64 GetPulseTime(const PulseArgs &); + + /** Overridden to support auto-reconnect via SetAutoReconnectDelay() */ + virtual void Pulse(const PulseArgs &); + + /** Should return a pretty, human readable string identifying this class. */ + virtual const char * GetTypeName() const = 0; + + /** May be overridden to return the host name string we should be assigned + * if no host name could be automatically determined by the ReflectServer + * (i.e. if we had no associated socket at the time). + * Default implementation returns "_unknown_". + */ + virtual String GetDefaultHostName() const; + + /** Convenience method -- returns a human-readable string describing our + * type, our hostname, our session ID, and what port we are connected to. + */ + String GetSessionDescriptionString() const; + + /** Returns the IP address we connected asynchronously to. + * The returned value is meaningful only if we were added + * with AddNewConnectSession() or AddNewDormantConnectSession(). + */ + const ip_address & GetAsyncConnectIP() const {return _asyncConnectDest.GetIPAddress();} + + /** Returns the remote port we connected asynchronously to. + * The returned value is meaningful only if we were added + * with AddNewConnectSession() or AddNewDormantConnectSession(). + */ + uint16 GetAsyncConnectPort() const {return _asyncConnectDest.GetPort();} + + /** Returns the destination we connected to asynchronously. */ + const IPAddressAndPort & GetAsyncConnectDestination() const {return _asyncConnectDest;} + + /** Sets the async-connect-destination (as returned by GetAsyncConnectIP() and GetAsyncConnectPort()) + * manually. Typically you don't need to call this; so only call this method if you really know + * what you are doing and why you need to. + * @param iap The IP address and port that this session connected to asynchronously. + * @param reconnectViaTCP If true, then any calls to Reconnect() will try to reconnect to this address via a + * standard TCP connection. If false, this address will be ignored by Reconnect(). + */ + void SetAsyncConnectDestination(const IPAddressAndPort & iap, bool reconnectViaTCP) {_asyncConnectDest = iap; _reconnectViaTCP = reconnectViaTCP;} + + /** Returns the node path of the node representing this session (e.g. "/192.168.1.105/17") */ + virtual const String & GetSessionRootPath() const {return _sessionRootPath;} + + /** Sets the amount of time that should pass between when this session loses its connection + * (that was previously set up using AddNewConnectSession() or AddNewDormantConnectSession()) + * and when it should automatically try to reconnect to that same destination (by calling Reconnect()). + * Default setting is MUSCLE_TIME_NEVER, meaning that automatic reconnection is disabled. + * @param delay The amount of time to delay before reconnecting, in microseconds. + */ + void SetAutoReconnectDelay(uint64 delay) {_autoReconnectDelay = delay; InvalidatePulseTime();} + + /** Returns the current automatic-reconnect delay period, as was previously set by + * SetAutoReconnectDelay(). Note that this setting is only relevant for sessions + * that were attached using AddNewConnectSession() or AddNewDormantConnectSession(). + */ + uint64 GetAutoReconnectDelay() const {return _autoReconnectDelay;} + + /** Sets the maximum time that we should allow an asynchronous-connect to continue before + * forcibly aborting it. Default behavior is determined by the MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS + * compiler define, whose default value is MUSCLE_TIME_NEVER, aka let the asynchronous connect + * go on for as long as the operating system cares to allow it to. + * Note that this setting is only relevant for sessions that were attached using AddNewConnectSession() + * or AddNewDormantConnectSession(). + * @param delay The new maximum connect time to allow, in microseconds, or MUSCLE_TIME_NEVER to not + * enforce any particular maximum connect time. + */ + void SetMaxAsyncConnectPeriod(uint64 delay) {_maxAsyncConnectPeriod = delay; InvalidatePulseTime();} + + /** Returns the current maximum-asynchronous-connect setting, in microseconds. + * Note that this setting is only relevant for sessions that were attached using AddNewConnectSession() + * or AddNewDormantConnectSession(). + */ + uint64 GetMaxAsyncConnectPeriod() const {return _maxAsyncConnectPeriod;} + + /** Returns true iff we are currently in the middle of an asynchronous TCP connection */ + bool IsConnectingAsync() const {return _connectingAsync;} + + /** Returns true iff this session is currently connected to our remote counterpart. */ + bool IsConnected() const {return _isConnected;} + + /** Returns true if this session was successfully connected to its remote counterpart before it became disconnected. + * Returns false if the session never was successfully connected. + */ + bool WasConnected() const {return _wasConnected;} + + /** This method may be called by ReflectServer when the server process runs low on memory. + * If it returns true this session may be disposed of in order to free up memory. If it + * returns false, this session will not be disposed of. + * + * Default return value is false, but the ReflectServer class will call SetExpendable(true) + * on any sessions return by ReflectSessionFactory::CreateSession() method, or + * passed to AddNewConnectSession(), unless SetExpendable() had already been explicitly + * called on the session beforehand. + */ + bool IsExpendable() const {return _isExpendable;} + + /** Calls this to set or clear the isExpendable flag on a session (see IsExpendable() for details) + * @param isExpendable If true, this session may be removed in a low-on-memory situation. If false, it won't be. + */ + void SetExpendable(bool isExpendable) {_isExpendable = isExpendable;} + + /** + * Adds a MessageRef to our gateway's outgoing message queue. + * (ref) will be sent back to our client when time permits. + * @param ref Reference to a Message to send to our client. + * @return B_NO_ERROR on success, B_ERROR if out-of-memory. + */ + virtual status_t AddOutgoingMessage(const MessageRef & ref); + + /** + * Convenience method: Calls MessageReceivedFromSession() on all session + * objects. Saves you from having to do your own iteration every time you + * want to broadcast something. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + * @param includeSelf Whether or not MessageReceivedFromSession() should be called on 'this' session. Defaults to true. + * @see GetSessions(), AddNewSession(), GetSession() + */ + void BroadcastToAllSessions(const MessageRef & msgRef, void * userData = NULL, bool includeSelf = true); + + /** + * Convenience method: Calls MessageReceivedFromSession() on all session + * objects of the specified type. Saves you from having to do your own iteration every time you + * want to broadcast something. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + * @param includeSelf Whether or not MessageReceivedFromSession() should be called on 'this' session. Defaults to true. + */ + template void BroadcastToAllSessionsOfType(const MessageRef & msgRef, void * userData = NULL, bool includeSelf=true) + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + SessionType * session = dynamic_cast(iter.GetValue()()); + if ((session)&&((includeSelf)||(session != this))) session->MessageReceivedFromSession(*this, msgRef, userData); + } + } + + /** + * Convenience method: Calls MessageReceivedFromSession() on all installed + * session-factory objects. Saves you from having to do your own iteration + * every time you want to broadcast something to the factories. + * @param msgRef a reference to the Message you wish to broadcast + * @param userData any userData value you care to include. Defaults to NULL. + * @see GetFactories(), PutAcceptFactory(), GetFactory() + */ + void BroadcastToAllFactories(const MessageRef & msgRef, void * userData = NULL); + + /** + * Closes this session's current TCP connection (if any), and creates a new + * TCP socket that will then try to asynchronously connect back to the previous + * socket's host and port. Note that this method is primarily useful for sessions + * that were added with AddNewConnectSession() or AddNewDormantConnectSession(); + * for other types of session, it will just destroy this session's DataIO and IOGateway + * and then create new ones by calling CreateDefaultSocket() and CreateDataIO(). + * @note This method will call CreateDataIO() to make a new DataIO object for the newly created socket. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + * On success, the connection result will be reported back + * later, either via a call to AsyncConnectCompleted() (if the connection + * succeeds) or a call to ClientConnectionClosed() (if the connection fails) + */ + status_t Reconnect(); + + /** Convenience method: Returns the "read" file descriptor associated with this session's + * DataIO class, or a NULL reference if there is none. + */ + const ConstSocketRef & GetSessionReadSelectSocket() const; + + /** Convenience method: Returns the "write" file descriptor associated with this session's + * DataIO class, or a NULL reference if there is none. + */ + const ConstSocketRef & GetSessionWriteSelectSocket() const; + +protected: + /** Set by StorageReflectSession::AttachedToServer() */ + void SetSessionRootPath(const String & p) {_sessionRootPath = p;} + +private: + void SetPolicyAux(AbstractSessionIOPolicyRef & setRef, uint32 & setChunk, const AbstractSessionIOPolicyRef & newRef, bool isInput); + void PlanForReconnect(); + void SetConnectingAsync(bool isConnectingAsync); + + friend class ReflectServer; + uint32 _sessionID; + String _idString; + IPAddressAndPort _ipAddressAndPort; + + bool _connectingAsync; + bool _isConnected; + uint64 _maxAsyncConnectPeriod; // max number of microseconds to allow an async-connect to take before timing out (or MUSCLE_TIME_NEVER if no limit is enforced) + uint64 _asyncConnectTimeoutTime; // timestamp of when to force an async-connect-in-progress to timeout + + String _hostName; + IPAddressAndPort _asyncConnectDest; + bool _reconnectViaTCP; // only valid when _asyncConnectDest is set + AbstractMessageIOGatewayRef _gateway; + uint64 _lastByteOutputAt; + AbstractSessionIOPolicyRef _inputPolicyRef; + AbstractSessionIOPolicyRef _outputPolicyRef; + uint32 _maxInputChunk; // as determined by our Policy object + uint32 _maxOutputChunk; // and stored here for convenience + uint64 _outputStallLimit; + bool _scratchReconnected; // scratch, watched by ReflectServer during ClientConnectionClosed() calls. + String _sessionRootPath; + + // auto-reconnect support + uint64 _autoReconnectDelay; + uint64 _reconnectTime; + bool _wasConnected; + + TamperEvidentValue _isExpendable; +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/AbstractSessionIOPolicy.h b/reflector/AbstractSessionIOPolicy.h new file mode 100644 index 00000000..042af464 --- /dev/null +++ b/reflector/AbstractSessionIOPolicy.h @@ -0,0 +1,155 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef AbstractSessionIOPolicy_h +#define AbstractSessionIOPolicy_h + +#include "util/RefCount.h" +#include "util/PulseNode.h" + +namespace muscle { + +class AbstractReflectSession; +class ReflectServer; + +/** A simple data-holding class; holds an AbstractSessionIOPolicy and a boolean to + * indicate whether it is using us as an input or not. Keeping the two + * values together here lets us use them as a "key". + */ +class PolicyHolder +{ +public: + /** Default Constructor. Sets session to NULL and asInput to false */ + PolicyHolder() : _session(NULL), _asInput(false) {/* empty */} + + /** Constructor. Sets our held values as specified by the arguments. */ + PolicyHolder(AbstractReflectSession * session, bool asInput) : _session(session), _asInput(asInput) {/* empty */} + + /** Accessor */ + AbstractReflectSession * GetSession() const {return _session;} + + /** Accessor */ + bool IsAsInput() const {return _asInput;} + + /** Returns a decent hash code for this object */ + uint32 HashCode() const {return ((uint32)((uintptr)_session))+(_asInput?1:0);} // double-cast for AMD64 + + /** Equality operator; returns true iff (rhs) has the same two settings as we do */ + bool operator == (const PolicyHolder & rhs) {return ((rhs._session == _session)&&(rhs._asInput == _asInput));} + +private: + AbstractReflectSession * _session; + bool _asInput; +}; + +/** + * This class is an interface for objects that can be used by a ReflectServer + * to control how and when I/O in a particular direction is performed for a + * session or group of sessions. Its primary use is to implement aggregate + * bandwidth limits for certain sessions or groups of sessions. Each + * AbstractReflectSession can associate itself an IO policy for reading, + * and an IO policy for writing, if it likes. + *

+ * ReflectServer calls the methods in this API in the following sequence: + *

    + *
  1. BeginIO() called
  2. + *
  3. OkayToTransfer() (called once for each session that wants to transfer data)
  4. + *
  5. GetMaxTransferChunkSize() (called once for each session that was given the okay in the previous step())
  6. + *
  7. server blocks until an event is ready, and then calls DoInput() and/or DoOutput() on sessions that need it()
  8. + *
  9. EndIO() called()
  10. + *
  11. Lather, rinse, repeat
  12. + *
+ */ +class AbstractSessionIOPolicy : public PulseNode, public RefCountable, private CountedObject +{ +public: + /** Default constructor. */ + AbstractSessionIOPolicy() : _hasBegun(false) {/* empty */} + + /** Destructor. */ + virtual ~AbstractSessionIOPolicy() {/* empty */} + + /** Called whenever an AbstractReflectSession chooses us as one of his policies of choice. + * Guaranteed not be called between calls to BeginIO() and EndIO(). + * @param holder An object containing info on the AbstractReflectSession that + * is now using this policy. + */ + virtual void PolicyHolderAdded(const PolicyHolder & holder) = 0; + + /** Called whenever an AbstractReflectSession cancels one of his policies with us. + * Guaranteed not be called between calls to BeginIO() and EndIO(). + * @param holder An object containing info on the AbstractReflectSession that + * is nolonger using this policy. Note that the contained + * session may be in the process of being destroyed, so while + * it is still a valid AbstractReflectSession, it may no longer + * be a valid object of its original subclass. + */ + virtual void PolicyHolderRemoved(const PolicyHolder & holder) = 0; + + /** Called once at the beginning of each pass through our I/O event loop. + * @param now The time at which the loop is beginning, for convenience. + */ + virtual void BeginIO(uint64 now) = 0; + + /** Should return true iff we want to allow the given session to be able to transfer + * bytes to/from its DataIO object. + * Called after BeginIO() for each iteration. Only called for the sessions + * that want to transfer data (i.e. whose gateway objects returned true from their + * IsReadyForInput() or HasBytesToOutput() methods), so you can determine the set + * of 'active' sessions based on what gets called here. + * @param holder a session who wishes to transfer data. Guaranteed to be one of + * our current PolicyHolders, and attached to the server. + * @returns true iff we want to let the session transfer, false if not. + * (Returning false keeps the session's socket from being included + * in the select() call, so the server won't be awoken even if the + * session's socket becomes ready) + */ + virtual bool OkayToTransfer(const PolicyHolder & holder) = 0; + + /** Should return the maximum number of bytes that the given session is allowed to + * transfer to/from its IOGateway's DataIO during the next I/O stage of our event loop. + * Called after all the calls to OkayToTransfer() have been done. + * Called once for each session that we previously returned true for from our + * OkayToTransfer() method. + * You may return MUSCLE_NO_LIMIT if you want the session to be able to transfer as + * much as it likes, or 0 if we want the session to not transfer anything at all (or + * any number in between, of course). + * (If you are returning 0 here, it's usually better to just have + * OkayToTransfer() return false for this PolicyHolder instead... + * that way the server won't keep waking up to service a session that won't allowed + * to transfer anything anyway) + * @param holder A session to get a limit for. Guaranteed to be one of our current + * PolicyHolders, and attached to the server. + * @returns The max number of bytes the session is allowed to transfer, for now. + * This value will be passed in to the session's gateway's DoInput() + * or DoOutput() method, as appropriate. + */ + virtual uint32 GetMaxTransferChunkSize(const PolicyHolder & holder) = 0; + + /** Called to notify you that the given session transferred the given + * number of bytes to/from its DataIO object. + * @param holder A session which transferred some bytes. Guaranteed to be one of our + * PolicyHolders, and attached to the server. + * @param numBytes How many bytes it transferred. This value will be less than or equal + * to the max value you return previously for this session from + * GetMaxReadChunkSize(). + */ + virtual void BytesTransferred(const PolicyHolder & holder, uint32 numBytes) = 0; + + /** Called once at the end of each pass through our I/O event loop. + * @param now The time at which the loop is ending, for convenience. + */ + virtual void EndIO(uint64 now) = 0; + +private: + friend class ReflectServer; + bool _hasBegun; // used by the ReflectServer +}; +DECLARE_REFTYPES(AbstractSessionIOPolicy); + +}; // end namespace muscle + +// At the bottom to avoid circular-forward-reference problems, while +// still allowing subclasses to automatically get this header +#include "reflector/AbstractReflectSession.h" + +#endif diff --git a/reflector/DataNode.cpp b/reflector/DataNode.cpp new file mode 100644 index 00000000..1349341b --- /dev/null +++ b/reflector/DataNode.cpp @@ -0,0 +1,485 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/DataNode.h" +#include "reflector/StorageReflectSession.h" + +namespace muscle { + +DataNode :: DataNode() : _children(NULL), _orderedIndex(NULL), _orderedCounter(0L), _subscribers(NULL) // _parent and _cachedDataChecksum will be set in Init()/Reset(), not here +{ + // empty +} + +DataNode :: ~DataNode() +{ + delete _children; + delete _orderedIndex; + delete _subscribers; +} + +void DataNode :: Init(const String & name, const MessageRef & initData) +{ + _nodeName = name; + _parent = NULL; + _depth = 0; + _maxChildIDHint = 0; + _data = initData; + _cachedDataChecksum = 0; +} + +void DataNode :: Reset() +{ + TCHECKPOINT; + + // Note that I'm now deleting these auxiliary objects instead of + // just clearing them. That will save memory, and also makes a + // newly-reset DataNode behavior more like a just-created one + // (See FogBugz #9845 for details) + delete _children; _children = NULL; + delete _orderedIndex; _orderedIndex = NULL; + delete _subscribers; _subscribers = NULL; + + _parent = NULL; + _depth = 0; + _maxChildIDHint = 0; + _data.Reset(); + _cachedDataChecksum = 0; +} + +void DataNode :: IncrementSubscriptionRefCount(const String & sessionID, long delta) +{ + TCHECKPOINT; + + if (delta > 0) + { + uint32 res = 0; + if (_subscribers == NULL) + { + _subscribers = newnothrow Hashtable; + if (_subscribers == NULL) WARN_OUT_OF_MEMORY; + } + if (_subscribers) + { + (void) _subscribers->Get(&sessionID, res); + (void) _subscribers->Put(&sessionID, res+delta); // I'm not sure how to cleanly handle out-of-mem here?? --jaf + } + } + else if (delta < 0) + { + uint32 res = 0; + if ((_subscribers)&&(_subscribers->Get(&sessionID, res) == B_NO_ERROR)) + { + uint32 decBy = (uint32) -delta; + if (decBy >= res) (void) _subscribers->Remove(&sessionID); + else (void) _subscribers->Put(&sessionID, res-decBy); // out-of-mem shouldn't be possible + } + } +} + +status_t DataNode :: InsertOrderedChild(const MessageRef & data, const String * optInsertBefore, const String * optNodeName, StorageReflectSession * notifyWithOnSetParent, StorageReflectSession * optNotifyChangedData, Hashtable * optRetAdded) +{ + TCHECKPOINT; + + if (_orderedIndex == NULL) + { + _orderedIndex = newnothrow Queue; + if (_orderedIndex == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } + + // Find a unique ID string for our new kid + String temp; // must be declared out here! + if (optNodeName == NULL) + { + while(true) + { + char buf[50]; + sprintf(buf, "I" UINT32_FORMAT_SPEC, _orderedCounter++); + if (HasChild(buf) == false) {temp = buf; break;} + } + optNodeName = &temp; + } + + DataNodeRef dref = notifyWithOnSetParent->GetNewDataNode(*optNodeName, data); + if (dref() == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + + uint32 insertIndex = _orderedIndex->GetNumItems(); // default to end of index + if ((optInsertBefore)&&(optInsertBefore->Cstr()[0] == 'I')) // only 'I''s could be in our index! + { + for (int i=_orderedIndex->GetNumItems()-1; i>=0; i--) + { + if ((*_orderedIndex)[i]()->GetNodeName() == *optInsertBefore) + { + insertIndex = i; + break; + } + } + } + + // Update the index + if (PutChild(dref, notifyWithOnSetParent, optNotifyChangedData) == B_NO_ERROR) + { + if (_orderedIndex->InsertItemAt(insertIndex, dref) == B_NO_ERROR) + { + String np; + if ((optRetAdded)&&(dref()->GetNodePath(np) == B_NO_ERROR)) (void) optRetAdded->Put(np, dref); + + // Notify anyone monitoring this node that the ordered-index has been updated + notifyWithOnSetParent->NotifySubscribersThatNodeIndexChanged(*this, INDEX_OP_ENTRYINSERTED, insertIndex, dref()->GetNodeName()); + return B_NO_ERROR; + } + else RemoveChild(dref()->GetNodeName(), notifyWithOnSetParent, false, NULL); // undo! + } + return B_ERROR; +} + +status_t DataNode :: RemoveIndexEntryAt(uint32 removeIndex, StorageReflectSession * optNotifyWith) +{ + TCHECKPOINT; + + if ((_orderedIndex)&&(removeIndex < _orderedIndex->GetNumItems())) + { + DataNodeRef holdKey = _orderedIndex->RemoveItemAtWithDefault(removeIndex); // gotta make a temp copy here, or it's dangling pointer time + if ((holdKey())&&(optNotifyWith)) optNotifyWith->NotifySubscribersThatNodeIndexChanged(*this, INDEX_OP_ENTRYREMOVED, removeIndex, holdKey()->GetNodeName()); + return B_NO_ERROR; + } + return B_ERROR; +} + +status_t DataNode :: InsertIndexEntryAt(uint32 insertIndex, StorageReflectSession * notifyWithOnSetParent, const String & key) +{ + TCHECKPOINT; + + if (_children) + { + DataNodeRef childNode; + if (_children->Get(&key, childNode) == B_NO_ERROR) + { + if (_orderedIndex == NULL) + { + _orderedIndex = newnothrow Queue; + if (_orderedIndex == NULL) WARN_OUT_OF_MEMORY; + } + if ((_orderedIndex)&&(_orderedIndex->InsertItemAt(insertIndex, childNode) == B_NO_ERROR)) + { + // Notify anyone monitoring this node that the ordered-index has been updated + notifyWithOnSetParent->NotifySubscribersThatNodeIndexChanged(*this, INDEX_OP_ENTRYINSERTED, insertIndex, childNode()->GetNodeName()); + return B_NO_ERROR; + } + } + } + return B_ERROR; +} + +status_t DataNode :: ReorderChild(const DataNodeRef & child, const String * moveToBeforeThis, StorageReflectSession * optNotifyWith) +{ + TCHECKPOINT; + + // Only do anything if we have an index, and the node isn't going to be moved to before itself (silly) and (child) can be removed from the index + if ((_orderedIndex)&&(child())&&((moveToBeforeThis == NULL)||(*moveToBeforeThis != child()->GetNodeName()))&&(RemoveIndexEntry(child()->GetNodeName(), optNotifyWith) == B_NO_ERROR)) + { + // Then re-add him to the index at the appropriate point + uint32 targetIndex = _orderedIndex->GetNumItems(); // default to end of index + if ((moveToBeforeThis)&&(HasChild(*moveToBeforeThis))) + { + for (int i=_orderedIndex->GetNumItems()-1; i>=0; i--) + { + if (*moveToBeforeThis == (*_orderedIndex)[i]()->GetNodeName()) + { + targetIndex = i; + break; + } + } + } + + // Now add the child back into the index at his new position + if (_orderedIndex->InsertItemAt(targetIndex, child) == B_NO_ERROR) + { + // Notify anyone monitoring this node that the ordered-index has been updated + if (optNotifyWith) optNotifyWith->NotifySubscribersThatNodeIndexChanged(*this, INDEX_OP_ENTRYINSERTED, targetIndex, child()->GetNodeName()); + return B_NO_ERROR; + } + } + return B_ERROR; +} + +status_t DataNode :: PutChild(const DataNodeRef & node, StorageReflectSession * optNotifyWithOnSetParent, StorageReflectSession * optNotifyChangedData) +{ + TCHECKPOINT; + + status_t ret = B_ERROR; + DataNode * child = node(); + if (child) + { + if (_children == NULL) + { + _children = newnothrow Hashtable; + if (_children == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + child->SetParent(this, optNotifyWithOnSetParent); + DataNodeRef oldNode; + ret = _children->Put(&child->_nodeName, node, oldNode); + if ((ret == B_NO_ERROR)&&(optNotifyChangedData)) + { + MessageRef oldData; if (oldNode()) oldData = oldNode()->GetData(); + optNotifyChangedData->NotifySubscribersThatNodeChanged(*child, oldData, false); + } + } + return ret; +} + +void DataNode :: SetParent(DataNode * parent, StorageReflectSession * optNotifyWith) +{ + TCHECKPOINT; + + if ((_parent)&&(parent)) LogTime(MUSCLE_LOG_WARNING, "Warning, overwriting previous parent of node [%s]\n", GetNodeName()()); + _parent = parent; + if (_parent) + { + const char * nn = _nodeName(); + uint32 id = atol(&nn[(*nn=='I')?1:0]); + _parent->_maxChildIDHint = muscleMax(_parent->_maxChildIDHint, id); + } + else if (_subscribers) _subscribers->Clear(); + + // Calculate our node's depth into the tree + _depth = 0; + if (_parent) + { + // Calculate the total length that our node path string will be + const DataNode * node = this; + while(node->_parent) + { + _depth++; + node = node->_parent; + } + if (optNotifyWith) optNotifyWith->NotifySubscribersOfNewNode(*this); + } +} + +const String * DataNode :: GetPathClause(uint32 depth) const +{ + if (depth <= _depth) + { + const DataNode * node = this; + for (uint32 i=depth; i<_depth; i++) if (node) node = node->_parent; + if (node) return &node->GetNodeName(); + } + return NULL; +} + +status_t DataNode :: GetNodePath(String & retPath, uint32 startDepth) const +{ + TCHECKPOINT; + + // Calculate node path and node depth + if (_parent) + { + // Calculate the total length that our node path string will be + uint32 pathLen = 0; + { + uint32 d = _depth; + const DataNode * node = this; + while((d-- >= startDepth)&&(node->_parent)) + { + pathLen += 1 + node->_nodeName.Length(); // the 1 is for the slash + node = node->_parent; + } + } + + if ((pathLen > 0)&&(startDepth > 0)) pathLen--; // for (startDepth>0), there will be no initial slash + + // Might as well make sure we have enough memory to return it, up front + if (retPath.Prealloc(pathLen) != B_NO_ERROR) return B_ERROR; + + char * dynBuf = NULL; + const uint32 stackAllocSize = 256; + char stackBuf[stackAllocSize]; // try to do this without a dynamic allocation... + if (pathLen >= stackAllocSize) // but do a dynamic allocation if we have to (should be rare) + { + dynBuf = newnothrow_array(char, pathLen+1); + if (dynBuf == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } + + char * writeAt = (dynBuf ? dynBuf : stackBuf) + pathLen; // points to last char in buffer + *writeAt = '\0'; // terminate the string first (!) + const DataNode * node = this; + uint32 d = _depth; + while((d >= startDepth)&&(node->_parent)) + { + int len = node->_nodeName.Length(); + writeAt -= len; + memcpy(writeAt, node->_nodeName(), len); + if ((startDepth == 0)||(d > startDepth)) *(--writeAt) = '/'; + node = node->_parent; + d--; + } + + retPath = (dynBuf ? dynBuf : stackBuf); + delete [] dynBuf; + } + else retPath = (startDepth == 0) ? "/" : ""; + + return B_NO_ERROR; +} + +status_t DataNode :: RemoveChild(const String & key, StorageReflectSession * optNotifyWith, bool recurse, uint32 * optCurrentNodeCount) +{ + TCHECKPOINT; + + DataNodeRef childRef; + if ((_children)&&(_children->Get(&key, childRef) == B_NO_ERROR)) + { + DataNode * child = childRef(); + if (child) + { + if (recurse) while(child->HasChildren()) child->RemoveChild(**(child->_children->GetFirstKey()), optNotifyWith, recurse, optCurrentNodeCount); + + (void) RemoveIndexEntry(key, optNotifyWith); + if (optNotifyWith) optNotifyWith->NotifySubscribersThatNodeChanged(*child, child->GetData(), true); + + child->SetParent(NULL, optNotifyWith); + } + if (optCurrentNodeCount) (*optCurrentNodeCount)--; + + (void) _children->Remove(&key, childRef); + return B_NO_ERROR; + } + return B_ERROR; +} + +status_t DataNode :: RemoveIndexEntry(const String & key, StorageReflectSession * optNotifyWith) +{ + TCHECKPOINT; + + // Update our ordered-node index & notify everyone about the change + if ((_orderedIndex)&&(key()[0] == 'I')) // if it doesn't start with I, we know it's not part of our ordered-index! + { + for (int i=_orderedIndex->GetNumItems()-1; i>=0; i--) + { + if (key == (*_orderedIndex)[i]()->GetNodeName()) + { + (void) _orderedIndex->RemoveItemAt(i); + if (optNotifyWith) optNotifyWith->NotifySubscribersThatNodeIndexChanged(*this, INDEX_OP_ENTRYREMOVED, i, key); + return B_NO_ERROR; + } + } + } + return B_ERROR; +} + +void DataNode :: SetData(const MessageRef & data, StorageReflectSession * optNotifyWith, bool isBeingCreated) +{ + MessageRef oldData; + if (isBeingCreated == false) oldData = _data; + _data = data; + _cachedDataChecksum = 0; + if (optNotifyWith) optNotifyWith->NotifySubscribersThatNodeChanged(*this, oldData, false); +} + +uint32 DataNode :: CalculateChecksum(uint32 maxRecursionDepth) const +{ + // demand-calculate the local checksum and cache the result, since it can be expensive if the Message is big + if (_cachedDataChecksum == 0) _cachedDataChecksum = _nodeName.CalculateChecksum()+(_data()?_data()->CalculateChecksum():0); + if (maxRecursionDepth == 0) return _cachedDataChecksum; + else + { + uint32 ret = _cachedDataChecksum; + if (_orderedIndex) for (int32 i=_orderedIndex->GetNumItems()-1; i>=0; i--) ret += (*_orderedIndex)[i]()->GetNodeName().CalculateChecksum(); + if (_children) for (HashtableIterator iter(*_children); iter.HasData(); iter++) ret += iter.GetValue()()->CalculateChecksum(maxRecursionDepth-1); + return ret; + } +} + +static void PrintIndent(FILE * file, int indentLevel) {for (int i=0; iGetNumItems():0, _orderedIndex?(int32)_orderedIndex->GetNumItems():(int32)-1, CalculateChecksum(maxRecursionDepth), _data()?_data()->CalculateChecksum():0); + if (_data()) _data()->PrintToStream(optFile, true, indentLevel+1); + if (maxRecursionDepth > 0) + { + if (_orderedIndex) + { + for (uint32 i=0; i<_orderedIndex->GetNumItems(); i++) + { + PrintIndent(optFile, indentLevel); + fprintf(optFile, " Index slot " UINT32_FORMAT_SPEC " = %s\n", i, (*_orderedIndex)[i]()->GetNodeName()()); + } + } + if (_children) + { + PrintIndent(optFile, indentLevel); fprintf(optFile, "Children for node [%s] follow:\n", np()); + for (HashtableIterator iter(*_children); iter.HasData(); iter++) iter.GetValue()()->PrintToStream(optFile, maxRecursionDepth-1, indentLevel+2); + } + } +} + +DataNode * DataNode :: FindFirstMatchingNode(const char * path, uint32 maxDepth) const +{ + switch(path[0]) + { + case '\0': return const_cast(this); + case '/': return GetRootNode()->FindFirstMatchingNode(path+1, maxDepth); + + default: + { + if ((_children == NULL)||(maxDepth == 0)) return NULL; + + const char * nextSlash = strchr(path, '/'); + String childKey(path, (nextSlash)?(nextSlash-path):MUSCLE_NO_LIMIT); + const char * recurseArg = nextSlash?(nextSlash+1):""; + + if (CanWildcardStringMatchMultipleValues(childKey)) + { + StringMatcher sm(childKey); + for (DataNodeRefIterator iter = GetChildIterator(); iter.HasData(); iter++) + { + if (sm.Match(*iter.GetKey())) + { + const DataNodeRef * childRef = _children->Get(iter.GetKey()); + if (childRef) + { + DataNode * ret = childRef->GetItemPointer()->FindFirstMatchingNode(recurseArg, maxDepth-1); + if (ret) return ret; + } + } + } + } + else + { + const DataNodeRef * childRef = _children->Get(&childKey); + if (childRef) return childRef->GetItemPointer()->FindFirstMatchingNode(recurseArg, maxDepth-1); + } + } + return NULL; + } +} + +DataNodeRef DataNode :: GetDescendantAux(const char * subPath) const +{ + const char * slash = strchr(subPath, '/'); + if (slash) + { + DataNodeRef child = GetChild(String(subPath, slash-subPath)); + return child() ? child()->GetDescendantAux(slash+1) : DataNodeRef(); + } + else return GetChild(subPath); +} + +}; // end namespace muscle diff --git a/reflector/DataNode.h b/reflector/DataNode.h new file mode 100644 index 00000000..8e2bb978 --- /dev/null +++ b/reflector/DataNode.h @@ -0,0 +1,307 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleDataNode_h +#define MuscleDataNode_h + +#include "reflector/DumbReflectSession.h" +#include "reflector/StorageReflectConstants.h" +#include "regex/PathMatcher.h" + +namespace muscle { + +class StorageReflectSession; +class DataNode; + +DECLARE_REFTYPES(DataNode); + +/** Iterator type for our child objects */ +typedef HashtableIterator DataNodeRefIterator; + +/** Each object of this class represents one node in the server-side data-storage tree. */ +class DataNode : public RefCountable, private CountedObject +{ +public: + /** Default Constructor. Don't create DataNode objects yourself though, call StorageReflectSession::GetNewDataNode() instead! */ + DataNode(); + + /** Destructor. Don't delete DataNode objects yourself though, let the DataNodeRef objects do it for you */ + ~DataNode(); + + /** + * Put a child without changing the ordering index + * @param child Reference to the child to accept into our list of children + * @param optNotifyWithOnSetParent If non-NULL, a StorageReflectSession to use to notify subscribers that the new node has been added + * @param optNotifyWithOnChangedData If non-NULL, a StorageReflectSession to use to notify subscribers when the node's data has been alterered + * @return B_NO_ERROR on success, B_ERROR if out of memory + */ + status_t PutChild(const DataNodeRef & child, StorageReflectSession * optNotifyWithOnSetParent, StorageReflectSession * optNotifyWithOnChangedData); + + /** + * Create and add a new child node for (data), and put it into the ordering index + * @param data Reference to a message to create a new child node for. + * @param optInsertBefore if non-NULL, the name of the child to put the new child before in our index. If NULL, (or the specified child cannot be found) the new child will be appended to the end of the index. + * @param optNodeName If non-NULL, the inserted node will have the specified name. Otherwise, a name will be generated for the node. + * @param optNotifyWithOnSetParent If non-NULL, a StorageReflectSession to use to notify subscribers that the new node has been added + * @param optNotifyWithOnChangedData If non-NULL, a StorageReflectSession to use to notify subscribers when the node's data has been alterered + * @param optAddNewChildren If non-NULL, any newly formed nodes will be added to this hashtable, keyed on their absolute node path. + * @return B_NO_ERROR on success, B_ERROR if out of memory + */ + status_t InsertOrderedChild(const MessageRef & data, const String * optInsertBefore, const String * optNodeName, StorageReflectSession * optNotifyWithOnSetParent, StorageReflectSession * optNotifyWithOnChangedData, Hashtable * optAddNewChildren); + + /** + * Moves the given node (which must be a child of ours) to be just before the node named + * (moveToBeforeThis) in our index. If (moveToBeforeThis) is not a node in our index, + * then (child) will be moved back to the end of the index. + * @param child Reference to a child node of ours, to be moved in the node ordering index. + * @param moveToBeforeThis name of another child node of ours. If this name is NULL or + * not found in our index, we'll move (child) to the end of the index. + * @param optNotifyWith If non-NULL, this will be used to sent INDEXUPDATE message to the + * interested clients, notifying them of the change. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t ReorderChild(const DataNodeRef & child, const String * moveToBeforeThis, StorageReflectSession * optNotifyWith); + + /** Returns true iff we have a child with the given name */ + bool HasChild(const String & key) const {return ((_children)&&(_children->ContainsKey(&key)));} + + /** Retrieves the child with the given name. + * @param key The name of the child we wish to retrieve + * @param returnChild On success, a reference to the retrieved child is written into this object. + * @return B_NO_ERROR if a child node was successfully retrieved, or B_ERROR if it was not found. + */ + status_t GetChild(const String & key, DataNodeRef & returnChild) const {return ((_children)&&(_children->Get(&key, returnChild) == B_NO_ERROR)) ? B_NO_ERROR : B_ERROR;} + + /** As above, except the reference to the child is returned as the return value rather than in a parameter. + * @param key The name of the child we wish to retrieve + * @return On success, a reference to the specified child node is returned. On failure, a NULL DataNodeRef is returned. + */ + DataNodeRef GetChild(const String & key) const {return _children ? _children->GetWithDefault(&key) : DataNodeRef();} + + /** Finds and returns a descendant node (i.e child, grandchild, etc) by following the provided + * slash-separated relative node path. + * @param subPath A list of child-names to descend down, with the child node-name separated from each other via slashes. + * @returns A valid DataNodeRef on success, or a NULL DataNodeRef if no child could be found at that sub-path. + */ + DataNodeRef GetDescendant(const String & subPath) const {return GetDescendantAux(subPath());} + + /** Removes the child with the given name. + * @param key The name of the child we wish to remove. + * @param optNotifyWith If non-NULL, the StorageReflectSession that should be used to notify subscribers that the given node has been removed + * @param recurse if true, the removed child's children will be removed from it, and so on, and so on... + * @param optCounter if non-NULL, this value will be incremented whenever a child is removed (recursively) + * @return B_NO_ERROR if the given child is found and removed, or B_ERROR if it could not be found. + */ + status_t RemoveChild(const String & key, StorageReflectSession * optNotifyWith, bool recurse, uint32 * optCounter); + + /** Returns an iterator that can be used for iterating over our set of children. + * @param flags If specified, this is the set of HTIT_FLAG_* flags to pass to the Hashtable iterator constructor. + */ + DataNodeRefIterator GetChildIterator(uint32 flags = 0) const {return _children ? _children->GetIterator(flags) : DataNodeRefIterator();} + + /** Returns the number of child nodes this node contains. */ + uint32 GetNumChildren() const {return _children ? _children->GetNumItems() : 0;} + + /** Returns true iff this node contains any child nodes. */ + bool HasChildren() const {return ((_children)&&(_children->HasItems()));} + + /** Returns the ASCII name of this node (e.g. "joe") */ + const String & GetNodeName() const {return _nodeName;} + + /** Generates and returns the full node path of this node (e.g. "/12.18.240.15/1234/beshare/files/joe"). + * @param retPath On success, this String will contain this node's absolute path. + * @param startDepth The depth at which the path should start. Defaults to zero, meaning the full path. + * Values greater than zero will return a partial path (e.g. a startDepth of 1 in the + * above example would return "12.18.240.15/1234/beshare/files/joe", and a startDepth + * of 2 would return "1234/beshare/files/joe") + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t GetNodePath(String & retPath, uint32 startDepth = 0) const; + + /** A more convenient verseion of the above GetNodePath() implementation. + * @param startDepth The depth at which the path should start. Defaults to zero, meaning the full path. + * Values greater than zero will return a partial path (e.g. a startDepth of 1 in the + * above example would return "12.18.240.15/1234/beshare/files/joe", and a startDepth + * of 2 would return "1234/beshare/files/joe") + * @returns this node's node path as a String. + */ + String GetNodePath(uint32 startDepth = 0) const {String ret; (void) GetNodePath(ret, startDepth); return ret;} + + /** Returns the name of the node in our path at the (depth) level. + * @param depth The node name we are interested in. For example, 0 will return the name of the + * root node, 1 would return the name of the IP address node, etc. If this number + * is greater than (depth), this method will return NULL. + */ + const String * GetPathClause(uint32 depth) const; + + /** Replaces this node's payload message with that of (data). + * @param data the new Message to associate with this node. + * @param optNotifyWith if non-NULL, this StorageReflectSession will be used to notify subscribers that this node's data has changed. + * @param isBeingCreated Should be set true only if this is the first time SetData() was called on this node after its creation. + * Which is to say, this should almost always be false. + */ + void SetData(const MessageRef & data, StorageReflectSession * optNotifyWith, bool isBeingCreated); + + /** Returns a reference to this node's Message payload. */ + const MessageRef & GetData() const {return _data;} + + /** Returns our node's parent, or NULL if this node doesn't have a parent node. */ + DataNode * GetParent() const {return _parent;} + + /** Returns this node's depth in the tree (e.g. zero if we are the root node, 1 if we are its child, etc) */ + uint32 GetDepth() const {return _depth;} + + /** Returns us to our virgin, pre-Init() state, by clearing all our children, subscribers, parent, etc. */ + void Reset(); + + /** + * Modifies the refcount for the given sessionID. + * Any sessionID's with (refCount > 0) will be in the GetSubscribers() list. + * @param sessionID the sessionID whose reference count is to be modified + * @param delta the amount to add to the reference count. + */ + void IncrementSubscriptionRefCount(const String & sessionID, long delta); + + /** Returns an iterator that can be used to iterate over our list of active subscribers */ + HashtableIterator GetSubscribers() const {return _subscribers ? _subscribers->GetIterator() : HashtableIterator();} + + /** Returns a pointer to our ordered-child index */ + const Queue * GetIndex() const {return _orderedIndex;} + + /** Insert a new entry into our ordered-child list at the (nth) entry position. + * Don't call this function unless you really know what you are doing! + * @param insertIndex Offset into the array to insert at + * @param optNotifyWith Session to use to inform everybody that the index has been changed. + * @param key Name of an existing child of this node that should be associated with the given entry. + * This child must not already be in the ordered-entry index! + * @return B_NO_ERROR on success, or B_ERROR on failure (bad index or unknown child). + */ + status_t InsertIndexEntryAt(uint32 insertIndex, StorageReflectSession * optNotifyWith, const String & key); + + /** Removes the (nth) entry from our ordered-child index, if possible. + * Don't call this function unless you really know what you are doing! + * @param removeIndex Offset into the array to remove + * @param optNotifyWith Session to use to inform everybody that the index has been changed. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t RemoveIndexEntryAt(uint32 removeIndex, StorageReflectSession * optNotifyWith); + + /** Returns the largest ID value that this node has seen in one of its children, since the + * time it was created or last cleared. Note that child nodes' names are assumed to include + * their ID value in ASCII format, possible with a preceding letter "I" (for indexed nodes). + * Child nodes whose names aren't in this format will be counted having ID zero. + * This value can be useful as a hint for generating new IDs. + */ + uint32 GetMaxKnownChildIDHint() const {return _maxChildIDHint;} + + /** You can manually set the max-known-child-ID hint here if you want to. */ + void SetMaxKnownChildIDHint(uint32 maxID) {_maxChildIDHint = maxID;} + + /** Convenience method: Returns true iff (ancestor) exists + * anywhere along the path between this node and the root node. + * (note that a node is not considered a descendant of itself) + * @param ancestor The node to check to see if we are a descendant of + */ + bool IsDescendantOf(const DataNode & ancestor) const {const DataNode * n = GetParent(); while(n) {if (n == &ancestor) return true; else n = n->GetParent();} return false;} + + /** Convenience method: Returns true iff (this) exists + * anywhere along the path between (descendant) and the root node. + * (note that a node is not considered an ancestor of itself) + * @param descendant The node to check to see if we are a descendant of it + */ + bool IsAncestorOf(const DataNode & descendant) const {return descendant.IsDescendantOf(*this);} + + /** Convenience method: Parses (path) as a series of slash-separated + * tokens (e.g. "some/node/names/here") which may contain regex chars + * if you wish. Returns the first DataNode whose path (relative to this + * node) matches (path). Returns NULL if no matching node is found. + * If the path is empty (""), this function returns (this). + * @param path The path to match against. May contain regex chars. + * If the path starts with a slash ("/"), the search will + * begin at the root node; otherwise the path is considered + * to be relative to this node. + * @param maxDepth The maximum number of tokens in the path to parse. + * If left at MUSCLE_NO_LIMIT (the default), then the + * entire path will be parsed. If set to zero, then + * this method will either return (this) or NULL. If set + * to one, only the first token will be parsed, and the + * method will return either one of this node's children, + * or NULL. Etc. + * @returns The first DataNode whose path matches (path), or NULL if no match is found. + */ + DataNode * FindFirstMatchingNode(const char * path, uint32 maxDepth = MUSCLE_NO_LIMIT) const; + + /** Convenience function: returns the root node of the node tree (by + * traversing parent links up to the top of the tree) + */ + DataNode * GetRootNode() const {DataNode * r = const_cast(this); while(r->GetParent()) r = r->GetParent(); return r;} + + /** Convenience function: Given a depth value less than or equal to our depth, returns a pointer to our ancestor node at that depth. + * @param depth The depth of the node we want returned, relative to the root of the tree. Zero would be the root node, one would be a child + * of the root node, and so on. + * @returns an ancestor DataNode, or NULL if such a node could not be found (most likely because (depth) is greater than this node's depth) + */ + DataNode * GetAncestorNode(uint32 depth) const + { + DataNode * r = const_cast(this); + while((r)&&(depth <= r->GetDepth())) + { + if (depth == r->GetDepth()) return r; + r = r->GetParent(); + } + return NULL; + } + + /** Returns a checksum representing the state of this node and the nodes + * beneath it, up to the specified recursion depth. Each node's checksum + * includes its nodename, its ordered-children-index (if any), and its payload Message. + * @param maxRecursionCount The maximum number of times to recurse. Zero would + * result in a checksum for this node only; one for this + * node and its children only, etc. Defaults to + * MUSCLE_NO_LIMIT. + * @note This method can be CPU-intensive; it is meant primarily for debugging. + * @returns a 32-bit checksum value based on the contents of this node and + * its descendants. + */ + uint32 CalculateChecksum(uint32 maxRecursionCount = MUSCLE_NO_LIMIT) const; + + /** For debugging purposes; prints the current state of this node (and + * optionally its descendants) to stdout or to another file you specify. + * @param optFile If non-NULL, the text will be printed to this file. If left as NULL, stdout will be used as a default. + * @param maxRecursionDepth The maximum number of times to recurse. Zero would + * result in a checksum for this node only; one for this + * node and its children only, etc. Defaults to + * MUSCLE_NO_LIMIT. + * @param indentLevel how many spaces to indent the generated text + */ + void PrintToStream(FILE * optFile = NULL, uint32 maxRecursionDepth = MUSCLE_NO_LIMIT, int indentLevel = 0) const; + +private: + friend class StorageReflectSession; + friend class ObjectPool; + DataNodeRef GetDescendantAux(const char * subPath) const; + + /** Assignment operator. Note that this operator is only here to assist with ObjectPool recycling operations, and doesn't actually + * make this DataNode into a copy of (rhs)... that's why we have it marked private, so that it won't be accidentally used in the traditional manner. + */ + DataNode & operator = (const DataNode & /*rhs*/) {Reset(); return *this;} + + void Init(const String & nodeName, const MessageRef & initialValue); + void SetParent(DataNode * _parent, StorageReflectSession * optNotifyWith); + status_t RemoveIndexEntry(const String & key, StorageReflectSession * optNotifyWith); + + DataNode * _parent; + MessageRef _data; + mutable uint32 _cachedDataChecksum; + Hashtable * _children; // lazy-allocated + Queue * _orderedIndex; // only used when tracking the ordering of our children (lazy-allocated) + uint32 _orderedCounter; + String _nodeName; + uint32 _depth; // number of ancestors our node has (e.g. root's _depth is zero) + uint32 _maxChildIDHint; // keep track of the largest child ID, for easier allocation of non-conflicting future child IDs + + Hashtable * _subscribers; // lazy-allocated +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/DumbReflectSession.cpp b/reflector/DumbReflectSession.cpp new file mode 100644 index 00000000..507d95ba --- /dev/null +++ b/reflector/DumbReflectSession.cpp @@ -0,0 +1,44 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/DumbReflectSession.h" + +namespace muscle { + +// This is a callback function that may be passed to the ServerProcessLoop() function. +// It creates and returns a new DumbReflectSession object. +AbstractReflectSessionRef DumbReflectSessionFactory :: CreateSession(const String &, const IPAddressAndPort &) +{ + AbstractReflectSession * ret = newnothrow DumbReflectSession; + if (ret == NULL) WARN_OUT_OF_MEMORY; + return AbstractReflectSessionRef(ret); +} + +DumbReflectSession :: +DumbReflectSession() : _defaultRoutingFlags(DEFAULT_MUSCLE_ROUTING_FLAGS_BIT_CHORD) +{ + // empty +} + +DumbReflectSession :: +~DumbReflectSession() +{ + // empty +} + +// Called when a new message is received from our IO gateway. We forward it on to all our server-side neighbors. +void +DumbReflectSession :: +MessageReceivedFromGateway(const MessageRef & msgRef, void *) +{ + if (IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS)) BroadcastToAllSessions(msgRef, NULL, IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF)); +} + +// Called when a new message is sent to us by one of our server-side neighbors. We forward it on to our client. +void +DumbReflectSession :: +MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * /*userData*/) +{ + if ((&from == this)||(IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY))) (void) AddOutgoingMessage(msg); +} + +}; // end namespace muscle diff --git a/reflector/DumbReflectSession.h b/reflector/DumbReflectSession.h new file mode 100644 index 00000000..735b1fc9 --- /dev/null +++ b/reflector/DumbReflectSession.h @@ -0,0 +1,84 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleDumbReflectSession_h +#define MuscleDumbReflectSession_h + +#include "reflector/AbstractReflectSession.h" + +namespace muscle { + +/** + * This is a factory class that returns new DumbReflectSession objects. + */ +class DumbReflectSessionFactory : public ReflectSessionFactory +{ +public: + virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & factoryInfo); +}; + +/** These flags govern the default routing behavior of unrecognized Message types */ +enum { + MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF = 0x01, /** If this bit is set, Messages broadcast by this session will be received by this session */ + MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS = 0x02, /** If this bit is set, unrecognized Messages received by this session's gateway will be broadcast to all neighbors. */ + MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY = 0x04 /** If this bit is set, unrecognized Messages received by this session's neighbors will be forwarded to this session's gateway. */ +}; + +#ifndef DEFAULT_MUSCLE_ROUTING_FLAGS_BIT_CHORD +# define DEFAULT_MUSCLE_ROUTING_FLAGS_BIT_CHORD (MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS | MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY) +#endif + +/** This class represents a single TCP connection between a muscled server and a client program. + * This class implements a simple "reflect-all-messages-to-all-clients" + * message forwarding strategy, but may be subclassed to perform more complex message routing logic. + */ +class DumbReflectSession : public AbstractReflectSession, private CountedObject +{ +public: + /** Default constructor. */ + DumbReflectSession(); + + /** Destructor. */ + virtual ~DumbReflectSession(); + + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + + /** Called when a message is sent to us by another session (possibly this one). + * @param from The session that is sending us the message. + * @param msg Reference to the message that we are receiving. + * @param userData This is a user value whose semantics are defined by the subclass. + */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * userData); + + /** Sets or unsets the specified default routing flag. + * @param flag a MUSCLE_ROUTING_FLAG_* value + * @param enable If true, the flag is set; if false, the flag is un-set. + * Note that the default states of the flags are specified by the DEFAULT_MUSCLE_ROUTING_FLAGS_BIT_CHORD macro, defined in DumbReflectSession.h + * Currently the default state of the flags is for all defined routing bits to be set. + */ + void SetRoutingFlag(uint32 flag, bool enable) + { + if (enable) _defaultRoutingFlags |= flag; + else _defaultRoutingFlags &= ~flag; + } + + /** Returns true iff the specified default routing flag is set. + * @param flag a MUSCLE_ROUTING_FLAG_* value. + */ + bool IsRoutingFlagSet(uint32 flag) const {return ((_defaultRoutingFlags & flag) != 0);} + + /** Returns a human-readable label for our session type: "Dumb Session" */ + virtual const char * GetTypeName() const {return "Dumb Session";} + + /** @deprecated synonym for SetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF). */ + void SetReflectToSelf(bool reflectToSelf) {SetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF, reflectToSelf);} + + /** @deprecated synonym for IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF). */ + bool GetReflectToSelf() const {return IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF);} + +private: + uint32 _defaultRoutingFlags; +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/FilterSessionFactory.cpp b/reflector/FilterSessionFactory.cpp new file mode 100644 index 00000000..6025b27e --- /dev/null +++ b/reflector/FilterSessionFactory.cpp @@ -0,0 +1,180 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/FilterSessionFactory.h" +#include "reflector/StorageReflectConstants.h" + +namespace muscle { + +FilterSessionFactory :: FilterSessionFactory(const ReflectSessionFactoryRef & slaveRef, uint32 msph, uint32 tms) : ProxySessionFactory(slaveRef), _tempLogFor(NULL), _maxSessionsPerHost(msph), _totalMaxSessions(tms) +{ + // empty +} + +FilterSessionFactory :: ~FilterSessionFactory() +{ + // empty +} + +AbstractReflectSessionRef FilterSessionFactory :: CreateSession(const String & clientHostIP, const IPAddressAndPort & iap) +{ + TCHECKPOINT; + + if (GetSessions().GetNumItems() >= _totalMaxSessions) + { + LogTime(MUSCLE_LOG_DEBUG, "Connection from [%s] refused (all " UINT32_FORMAT_SPEC " sessions slots are in use).\n", clientHostIP(), _totalMaxSessions); + return AbstractReflectSessionRef(); + } + + if (_maxSessionsPerHost != MUSCLE_NO_LIMIT) + { + uint32 count = 0; + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + if ((iter.GetValue()())&&(strcmp(iter.GetValue()()->GetHostName()(), clientHostIP()) == 0)&&(++count >= _maxSessionsPerHost)) + { + LogTime(MUSCLE_LOG_DEBUG, "Connection from [%s] refused (host already has " UINT32_FORMAT_SPEC " sessions open).\n", clientHostIP(), _maxSessionsPerHost); + return AbstractReflectSessionRef(); + } + } + } + + AbstractReflectSessionRef ret; + if (GetSlave()()) + { + // If we have any requires, then this IP must match at least one of them! + if (_requires.HasItems()) + { + bool matched = false; + for (HashtableIterator iter(_requires); iter.HasData(); iter++) + { + if (iter.GetValue()()->Match(clientHostIP())) + { + matched = true; + break; + } + } + if (matched == false) + { + LogTime(MUSCLE_LOG_DEBUG, "Connection from [%s] does not match any require pattern, access denied.\n", clientHostIP()); + return AbstractReflectSessionRef(); + } + } + + // This IP must *not* match any of our bans! + for (HashtableIterator iter(_bans); iter.HasData(); iter++) + { + if (iter.GetValue()()->Match(clientHostIP())) + { + LogTime(MUSCLE_LOG_DEBUG, "Connection from [%s] matches ban pattern [%s], access denied.\n", clientHostIP(), iter.GetKey()()); + return AbstractReflectSessionRef(); + } + } + + // Okay, he passes. We'll let our slave create a session for him. + ret = GetSlave()()->CreateSession(clientHostIP, iap); + if (ret()) + { + if (_inputPolicyRef()) ret()->SetInputPolicy(_inputPolicyRef); + if (_outputPolicyRef()) ret()->SetOutputPolicy(_outputPolicyRef); + } + } + return ret; +} + +void FilterSessionFactory :: MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void *) +{ + TCHECKPOINT; + + const Message * msg = msgRef(); + if (msg) + { + _tempLogFor = &from; + const String * s; + for (int b=0; (msg->FindString(PR_NAME_KEYS, b, &s) == B_NO_ERROR); b++) + { + switch(msg->what) + { + case PR_COMMAND_ADDBANS: PutBanPattern(*s); break; + case PR_COMMAND_ADDREQUIRES: PutRequirePattern(*s); break; + case PR_COMMAND_REMOVEBANS: RemoveMatchingBanPatterns(*s); break; + case PR_COMMAND_REMOVEREQUIRES: RemoveMatchingRequirePatterns(*s); break; + } + } + _tempLogFor = NULL; + } +} + +status_t FilterSessionFactory :: PutBanPattern(const String & banPattern) +{ + TCHECKPOINT; + + if (_bans.ContainsKey(banPattern)) return B_NO_ERROR; + StringMatcherRef newMatcherRef(newnothrow StringMatcher(banPattern)); + if (newMatcherRef()) + { + if (_bans.Put(banPattern, newMatcherRef) == B_NO_ERROR) + { + if (_tempLogFor) LogTime(MUSCLE_LOG_DEBUG, "Session [%s/%s] is banning [%s] on port %u\n", _tempLogFor->GetHostName()(), _tempLogFor->GetSessionIDString()(), banPattern(), _tempLogFor->GetPort()); + return B_NO_ERROR; + } + } + else WARN_OUT_OF_MEMORY; + + return B_ERROR; +} + +status_t FilterSessionFactory :: PutRequirePattern(const String & requirePattern) +{ + TCHECKPOINT; + + if (_requires.ContainsKey(requirePattern)) return B_NO_ERROR; + StringMatcherRef newMatcherRef(newnothrow StringMatcher(requirePattern)); + if (newMatcherRef()) + { + if (_requires.Put(requirePattern, newMatcherRef) == B_NO_ERROR) + { + if (_tempLogFor) LogTime(MUSCLE_LOG_DEBUG, "Session [%s/%s] is requiring [%s] on port %u\n", _tempLogFor->GetHostName()(), _tempLogFor->GetSessionIDString()(), requirePattern(), _tempLogFor->GetPort()); + return B_NO_ERROR; + } + } + else WARN_OUT_OF_MEMORY; + + return B_ERROR; +} + +status_t FilterSessionFactory :: RemoveBanPattern(const String & banPattern) +{ + if (_bans.ContainsKey(banPattern)) // don't Remove() here, since then our argument might be a dangling reference during the LogTime() call + { + if (_tempLogFor) LogTime(MUSCLE_LOG_DEBUG, "Session [%s/%s] is removing ban [%s] on port %u\n", _tempLogFor->GetHostName()(), _tempLogFor->GetSessionIDString()(), banPattern(), _tempLogFor->GetPort()); + (void) _bans.Remove(banPattern); + return B_NO_ERROR; + } + return B_ERROR; +} + +status_t FilterSessionFactory :: RemoveRequirePattern(const String & requirePattern) +{ + if (_requires.ContainsKey(requirePattern)) // don't Remove() here, since then our argument might be a dangling reference during the LogTime() call + { + if (_tempLogFor) LogTime(MUSCLE_LOG_DEBUG, "Session [%s/%s] is removing requirement [%s] on port %u\n", _tempLogFor->GetHostName()(), _tempLogFor->GetSessionIDString()(), requirePattern(), _tempLogFor->GetPort()); + (void) _requires.Remove(requirePattern); + return B_NO_ERROR; + } + return B_ERROR; +} + +void FilterSessionFactory :: RemoveMatchingBanPatterns(const String & exp) +{ + StringMatcher sm(exp); + for (HashtableIterator iter(_bans); iter.HasData(); iter++) if (sm.Match(iter.GetKey()())) RemoveBanPattern(iter.GetKey()); +} + + +void FilterSessionFactory :: RemoveMatchingRequirePatterns(const String & exp) +{ + StringMatcher sm(exp); + for (HashtableIterator iter(_requires); iter.HasData(); iter++) if (sm.Match(iter.GetKey()())) RemoveRequirePattern(iter.GetKey()); +} + +}; // end namespace muscle diff --git a/reflector/FilterSessionFactory.h b/reflector/FilterSessionFactory.h new file mode 100644 index 00000000..23d52278 --- /dev/null +++ b/reflector/FilterSessionFactory.h @@ -0,0 +1,115 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleFilterSessionFactory_h +#define MuscleFilterSessionFactory_h + +#include "reflector/AbstractReflectSession.h" +#include "reflector/AbstractSessionIOPolicy.h" +#include "regex/PathMatcher.h" + +namespace muscle { + +/** This is a convenience decorator factory; it holds a set of "ban patterns", and a + * set of "require patterns". It will refuse to grant access to any clients whose host + * IP match at least one ban pattern, or who don't match at least one require pattern + * (if there are any require patterns) For IP addresses that don't match a pattern, + * the request is passed through to the held factory. + */ +class FilterSessionFactory : public ProxySessionFactory +{ +public: + /** Constructor. + * @param slaveRef Reference to the slave factory that will do the actual creation for us. + * @param maxSessionsPerHost If set, this is the maximum number of simultaneous connections + * we will allow to be in existence from any one host at a time. + * @param totalMaxSessions If set, this is the maximum number of simultaneous connections + * we will allow to be in existence on the server at one time. + */ + FilterSessionFactory(const ReflectSessionFactoryRef & slaveRef, uint32 maxSessionsPerHost = MUSCLE_NO_LIMIT, uint32 totalMaxSessions = MUSCLE_NO_LIMIT); + + /** Destructor */ + virtual ~FilterSessionFactory(); + + /** Checks to see if the new session meets all our acceptance criteria. + * If so, the call is passed through to our held factory; if not, it's "access denied" time, and we return NULL. + * @param clientAddress A string representing the connecting client's host (typically an IP address, e.g. "192.168.1.102") + * @param factoryInfo the IP address and port of the network interface that accepted the connection + * @returns A reference to a new session object on approval, or a NULL reference on denial or error. + */ + virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & factoryInfo); + + /** Implemented to handle PR_COMMAND_(ADD/REMOVE)(BANS/REQUIRES) messages from our sessions */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData); + /** Add a new ban pattern to our set of ban patterns + * @param banPattern Pattern to match against (e.g. "192.168.0.*") + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t PutBanPattern(const String & banPattern); + + /** Add a new require pattern to our set of require patterns + * @param requirePattern Pattern to match against (e.g. "192.168.0.*") + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t PutRequirePattern(const String & requirePattern); + + /** Remove the first matching instance of (banPattern) from our set of ban patterns. + * Note that we don't do any pattern matching here, we will remove exactly one ban + * pattern that is exactly equal to the supplied argument. + * @param requirePattern Pattern to remove from the set of ban patterns + * @return B_NO_ERROR on success, or B_ERROR if the given pattern wasn't found in the set. + */ + status_t RemoveBanPattern(const String & requirePattern); + + /** Remove the first matching instance of (requirePattern) from our set of require patterns. + * Note that we don't do any pattern matching here, we will remove exactly one require + * pattern that is exactly equal to the supplied argument. + * @param requirePattern Pattern to remove from the set of require patterns + * @return B_NO_ERROR on success, or B_ERROR if the given pattern wasn't found in the set. + */ + status_t RemoveRequirePattern(const String & requirePattern); + + /** Removes all ban patterns who match the given regular expression. + * @param exp Expression to match on. + */ + void RemoveMatchingBanPatterns(const String & exp); + + /** Removes all require patterns who match the given regular expression. + * @param exp Expression to match on. + */ + void RemoveMatchingRequirePatterns(const String & exp); + + /** Sets the input-bandwidth-allocation policy to apply to sessions that we create */ + void SetInputPolicy(const AbstractSessionIOPolicyRef & ref) {_inputPolicyRef = ref;} + + /** Sets the output-bandwidth-allocation policy to apply to sessions that we create */ + void SetOutputPolicy(const AbstractSessionIOPolicyRef & ref) {_outputPolicyRef = ref;} + + /** Sets the new max-sessions-per-host limit -- i.e. how many sessions from any given IP address + * may be connected to our server concurrently. + */ + void SetMaxSessionsPerHost(uint32 maxSessionsPerHost) {_maxSessionsPerHost = maxSessionsPerHost;} + + /** Sets the new total-max-sessions limit -- i.e. how many sessions may be connected to our server concurrently. */ + void SetTotalMaxSessions(uint32 maxSessions) {_totalMaxSessions = maxSessions;} + + /** Returns the current max-sessions-per-host limit */ + uint32 GetMaxSessionsPerHost() const {return _maxSessionsPerHost;} + + /** Sets the current total-max-sessions limit -- i.e. how many sessions may be connected to our server concurrently. */ + uint32 GetTotalMaxSessions() const {return _totalMaxSessions;} + +private: + Hashtable _bans; + Hashtable _requires; + AbstractReflectSession * _tempLogFor; + + AbstractSessionIOPolicyRef _inputPolicyRef; + AbstractSessionIOPolicyRef _outputPolicyRef; + + uint32 _maxSessionsPerHost; + uint32 _totalMaxSessions; +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/RateLimitSessionIOPolicy.cpp b/reflector/RateLimitSessionIOPolicy.cpp new file mode 100644 index 00000000..688445ff --- /dev/null +++ b/reflector/RateLimitSessionIOPolicy.cpp @@ -0,0 +1,113 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/RateLimitSessionIOPolicy.h" + +namespace muscle { + +#define CUTOFF (_byteLimit/2) + +RateLimitSessionIOPolicy :: +RateLimitSessionIOPolicy(uint32 maxRate, uint32 primeBytes) : _maxRate(maxRate), _byteLimit(primeBytes), _lastTransferAt(0), _transferTally(0) +{ + // empty +} + +RateLimitSessionIOPolicy :: +~RateLimitSessionIOPolicy() +{ + // empty +} + +void +RateLimitSessionIOPolicy :: +PolicyHolderAdded(const PolicyHolder &) +{ + // empty +} + +void +RateLimitSessionIOPolicy :: +PolicyHolderRemoved(const PolicyHolder &) +{ + // empty +} + +void +RateLimitSessionIOPolicy :: +BeginIO(uint64 now) +{ + UpdateTransferTally(now); + + _lastTransferAt = now; + _numParticipants = 0; + + // If we aren't going to allow anyone to transfer, + // we'll need to make sure the server wakes up so + // we can do transfers later, after some time has passed. + if (_transferTally>=CUTOFF) InvalidatePulseTime(); +} + +uint64 +RateLimitSessionIOPolicy :: +GetPulseTime(const PulseArgs & args) +{ + // Schedule a pulse for when we estimate _transferTally will sink back down to zero. + return ((_maxRate > 0)&&(_transferTally>=CUTOFF))?(args.GetCallbackTime()+((_transferTally*MICROS_PER_SECOND)/_maxRate)):MUSCLE_TIME_NEVER; +} + +void +RateLimitSessionIOPolicy :: +Pulse(const PulseArgs & args) +{ + TCHECKPOINT; + UpdateTransferTally(args.GetCallbackTime()); +} + +void +RateLimitSessionIOPolicy :: +UpdateTransferTally(uint64 now) +{ + if (_maxRate > 0) + { + uint32 newBytesAvailable = (_lastTransferAt > 0) ? ((uint32)(((now-_lastTransferAt)*_maxRate)/MICROS_PER_SECOND)) : MUSCLE_NO_LIMIT; + if (_transferTally > newBytesAvailable) _transferTally -= newBytesAvailable; + else _transferTally = 0; + } + else _transferTally = MUSCLE_NO_LIMIT; // disable all writing by pretending we just wrote a whole lot +} + +bool +RateLimitSessionIOPolicy :: +OkayToTransfer(const PolicyHolder &) +{ + if ((_maxRate > 0)&&(_transferTally < CUTOFF)) + { + _numParticipants++; + return true; + } + else return false; +} + +uint32 +RateLimitSessionIOPolicy :: +GetMaxTransferChunkSize(const PolicyHolder &) +{ + MASSERT(_numParticipants>0, "RateLimitSessionIOPolicy::GetMax: no participants!?!?"); + return (_transferTally<_byteLimit)?((_byteLimit-_transferTally)/_numParticipants):0; +} + +void +RateLimitSessionIOPolicy :: +BytesTransferred(const PolicyHolder &, uint32 numBytes) +{ + _transferTally += numBytes; +} + +void +RateLimitSessionIOPolicy :: +EndIO(uint64) +{ + // empty +} + +}; // end namespace muscle diff --git a/reflector/RateLimitSessionIOPolicy.h b/reflector/RateLimitSessionIOPolicy.h new file mode 100644 index 00000000..10f0b9ec --- /dev/null +++ b/reflector/RateLimitSessionIOPolicy.h @@ -0,0 +1,56 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef RateLimitSessionIOPolicy_h +#define RateLimitSessionIOPolicy_h + +#include "reflector/AbstractSessionIOPolicy.h" + +namespace muscle { + +/** + * This policy allows you to enforce an aggregate maximum bandwidth usage for the set + * of AbstractReflectSessionSessions that use it. Each policy object may referenced by + * zero or more PolicyHolders at once. + */ +class RateLimitSessionIOPolicy : public AbstractSessionIOPolicy, private CountedObject +{ +public: + /** Constructor. + * @param maxRate The maximum aggregate transfer rate to be enforced for all sessions + * that use this policy, in bytes per second. + * @param primeBytes When the bytes first start to flow, the policy allows the first (primeBytes) + * bytes to be sent out immediately, before clamping down on the flow rate. + * This helps keep the policy from having to wake up the server too often, + * and saves CPU time. This parameter lets you adjust that startup-size. + * Defaults to 2048 bytes. + */ + RateLimitSessionIOPolicy(uint32 maxRate, uint32 primeBytes = 2048); + + /** Destructor. */ + virtual ~RateLimitSessionIOPolicy(); + + virtual void PolicyHolderAdded(const PolicyHolder & holder); + virtual void PolicyHolderRemoved(const PolicyHolder & holder); + + virtual void BeginIO(uint64 now); + virtual bool OkayToTransfer(const PolicyHolder & holder); + virtual uint32 GetMaxTransferChunkSize(const PolicyHolder & holder); + virtual void BytesTransferred(const PolicyHolder & holder, uint32 numBytes); + virtual void EndIO(uint64 now); + + virtual uint64 GetPulseTime(const PulseArgs & args); + virtual void Pulse(const PulseArgs & args); + +private: + void UpdateTransferTally(uint64 now); + + uint32 _maxRate; + uint32 _byteLimit; + uint64 _lastTransferAt; + uint32 _transferTally; + uint32 _numParticipants; +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/ReflectServer.cpp b/reflector/ReflectServer.cpp new file mode 100644 index 00000000..507e8e3b --- /dev/null +++ b/reflector/ReflectServer.cpp @@ -0,0 +1,1090 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/ReflectServer.h" +# include "reflector/StorageReflectConstants.h" +#ifndef MUSCLE_AVOID_SIGNAL_HANDLING +#include "reflector/SignalHandlerSession.h" +#include "system/SetupSystem.h" // for IsCurrentThreadMainThread() +#endif +#include "util/NetworkUtilityFunctions.h" +#include "util/MemoryAllocator.h" + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING +# include "system/GlobalMemoryAllocator.h" +#endif + +#ifdef MUSCLE_ENABLE_SSL +# include "dataio/SSLSocketDataIO.h" +# include "dataio/TCPSocketDataIO.h" +# include "iogateway/SSLSocketAdapterGateway.h" +#endif + +namespace muscle { + +extern bool _mainReflectServerCatchSignals; // from SetupSystem.cpp + +status_t +ReflectServer :: +AddNewSession(const AbstractReflectSessionRef & ref, const ConstSocketRef & ss) +{ + TCHECKPOINT; + + AbstractReflectSession * newSession = ref(); + if (newSession == NULL) return B_ERROR; + + newSession->SetOwner(this); // in case CreateGateway() needs to use the owner + + ConstSocketRef s = ss; + if (s() == NULL) s = ref()->CreateDefaultSocket(); + + // Create the gateway object for this session, if it isn't already set up + if (s()) + { + AbstractMessageIOGatewayRef gatewayRef = newSession->GetGateway(); + if (gatewayRef() == NULL) gatewayRef = newSession->CreateGateway(); + if (gatewayRef()) // don't combine these ifs! + { + if (gatewayRef()->GetDataIO()() == NULL) + { + // create the new DataIO for the gateway; this must always be done on the fly + // since it depends on the socket being used. + DataIORef io = newSession->CreateDataIO(s); + if (io()) + { +#ifdef MUSCLE_ENABLE_SSL + if (_inDoAccept.IsInBatch()) + { + if (_privateKey()) + { + if (dynamic_cast(io()) != NULL) // We only support SSL over TCP, for now + { + SSLSocketDataIO * sslIO = newnothrow SSLSocketDataIO(s, false, true); + DataIORef sslIORef(sslIO); + if (sslIO) + { + if ((sslIO->SetPublicKeyCertificate(_privateKey) == B_NO_ERROR)&&(sslIO->SetPrivateKey(_privateKey()->GetBuffer(), _privateKey()->GetNumBytes()) == B_NO_ERROR)) + { + io = sslIORef; + gatewayRef.SetRef(newnothrow SSLSocketAdapterGateway(gatewayRef)); + if (gatewayRef() == NULL) {WARN_OUT_OF_MEMORY; newSession->SetOwner(NULL); return B_ERROR;} + } + else {LogTime(MUSCLE_LOG_ERROR, "AcceptSession: Unable to use private key file, incoming connection refused! (Bad .pem file format?)\n"); newSession->SetOwner(NULL); return B_ERROR;} + } + else {WARN_OUT_OF_MEMORY; newSession->SetOwner(NULL); return B_ERROR;} + } + } + } + else if (_inDoConnect.IsInBatch()) + { + if (_publicKey()) + { + if (dynamic_cast(io()) != NULL) // We only support SSL over TCP, for now + { + SSLSocketDataIO * sslIO = newnothrow SSLSocketDataIO(s, false, false); + DataIORef sslIORef(sslIO); + if (sslIO) + { + if (sslIO->SetPublicKeyCertificate(_publicKey) == B_NO_ERROR) + { + io = sslIORef; + gatewayRef.SetRef(newnothrow SSLSocketAdapterGateway(gatewayRef)); + if (gatewayRef() == NULL) {WARN_OUT_OF_MEMORY; newSession->SetOwner(NULL); return B_ERROR;} + } + else {LogTime(MUSCLE_LOG_ERROR, "ConnectSession: Unable to use public key file, outgoing connection aborted! (Bad .pem file format?)\n"); newSession->SetOwner(NULL); return B_ERROR;} + } + else {WARN_OUT_OF_MEMORY; newSession->SetOwner(NULL); return B_ERROR;} + } + } + } +#endif + + gatewayRef()->SetDataIO(io); + newSession->SetGateway(gatewayRef); + + } + else {newSession->SetOwner(NULL); return B_ERROR;} + } + } + else {newSession->SetOwner(NULL); return B_ERROR;} + } + + TCHECKPOINT; + + // Set our hostname (IP address) string if it isn't already set + if (newSession->_hostName.IsEmpty()) + { + const ConstSocketRef & sock = newSession->GetSessionReadSelectSocket(); + if (sock.GetFileDescriptor() >= 0) + { + ip_address ip = GetPeerIPAddress(sock, true); + const String * remapString = _remapIPs.Get(ip); + char ipbuf[64]; Inet_NtoA(ip, ipbuf); + newSession->_hostName = remapString ? *remapString : ((ip != invalidIP) ? String(ipbuf) : newSession->GetDefaultHostName()); + } + else newSession->_hostName = newSession->GetDefaultHostName(); + } + + if (AttachNewSession(ref) == B_NO_ERROR) return B_NO_ERROR; + else if (newSession) newSession->SetOwner(NULL); + + TCHECKPOINT; + + return B_ERROR; +} + +status_t +ReflectServer :: +AddNewConnectSession(const AbstractReflectSessionRef & ref, const ip_address & destIP, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + AbstractReflectSession * session = ref(); + if (session) + { + ConstSocketRef sock = ConnectAsync(destIP, port, session->_isConnected); + + // FogBugz #5256: If ConnectAsync() fails, we want to act as if it succeeded, so that the calling + // code still uses its normal asynchronous-connect-failure code path. That way the + // caller doesn't have to worry about synchronous failure as a separate case. + bool usingFakeBrokenConnection = false; + if (sock() == NULL) + { + ConstSocketRef tempSockRef; // tempSockRef represents the closed remote end of the failed connection and is intentionally closed ASAP + if (CreateConnectedSocketPair(sock, tempSockRef) == B_NO_ERROR) + { + session->_isConnected = false; + usingFakeBrokenConnection = true; + } + } + + if (sock()) + { + NestCountGuard ncg(_inDoConnect); + session->_asyncConnectDest = IPAddressAndPort(destIP, port); + session->_reconnectViaTCP = true; + session->SetMaxAsyncConnectPeriod(maxAsyncConnectPeriod); // must be done BEFORE SetConnectingAsync()! + session->SetConnectingAsync((usingFakeBrokenConnection == false)&&(session->_isConnected == false)); + + char ipbuf[64]; Inet_NtoA(destIP, ipbuf); + session->_hostName = (destIP != invalidIP) ? ipbuf : "_unknown_"; + + if (AddNewSession(ref, sock) == B_NO_ERROR) + { + if (autoReconnectDelay != MUSCLE_TIME_NEVER) session->SetAutoReconnectDelay(autoReconnectDelay); + if (session->_isConnected) + { + session->_wasConnected = true; + session->AsyncConnectCompleted(); + } + if (session->_isExpendable.HasValueBeenSet() == false) session->SetExpendable(true); + return B_NO_ERROR; + } + else + { + session->_asyncConnectDest.Reset(); + session->_hostName.Clear(); + session->_isConnected = false; + session->SetConnectingAsync(false); + } + } + } + return B_ERROR; +} + +status_t +ReflectServer :: +AddNewDormantConnectSession(const AbstractReflectSessionRef & ref, const ip_address & destIP, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + AbstractReflectSession * session = ref(); + if (session) + { + NestCountGuard ncg(_inDoConnect); + session->_asyncConnectDest = IPAddressAndPort(destIP, port); + session->_reconnectViaTCP = true; + char ipbuf[64]; Inet_NtoA(destIP, ipbuf); + session->_hostName = (destIP != invalidIP) ? ipbuf : "_unknown_"; + status_t ret = AddNewSession(ref, ConstSocketRef()); + if (ret == B_NO_ERROR) + { + if (autoReconnectDelay != MUSCLE_TIME_NEVER) session->SetAutoReconnectDelay(autoReconnectDelay); + session->SetMaxAsyncConnectPeriod(maxAsyncConnectPeriod); + return B_NO_ERROR; + } + else + { + session->_asyncConnectDest.Reset(); + session->_hostName.Clear(); + } + } + return B_ERROR; +} + +status_t +ReflectServer :: +AttachNewSession(const AbstractReflectSessionRef & ref) +{ + AbstractReflectSession * newSession = ref(); + if ((newSession)&&(_sessions.Put(&newSession->GetSessionIDString(), ref) == B_NO_ERROR)) + { + newSession->SetOwner(this); + if (newSession->AttachedToServer() == B_NO_ERROR) + { + newSession->SetFullyAttachedToServer(true); + if (_doLogging) LogTime(MUSCLE_LOG_DEBUG, "New %s (" UINT32_FORMAT_SPEC " total)\n", newSession->GetSessionDescriptionString()(), _sessions.GetNumItems()); + return B_NO_ERROR; + } + else + { + newSession->AboutToDetachFromServer(); // well, it *was* attached, if only for a moment + newSession->DoOutput(MUSCLE_NO_LIMIT); // one last chance for him to send any leftover data! + if (_doLogging) LogTime(MUSCLE_LOG_DEBUG, "%s aborted startup (" UINT32_FORMAT_SPEC " left)\n", newSession->GetSessionDescriptionString()(), _sessions.GetNumItems()-1); + } + newSession->SetOwner(NULL); + (void) _sessions.Remove(&newSession->GetSessionIDString()); + } + return B_ERROR; +} + + +ReflectServer :: ReflectServer() : _keepServerGoing(true), _serverStartedAt(0), _doLogging(true), _serverSessionID(GetCurrentTime64()+GetRunTime64()+rand()) +{ + if (_serverSessionID == 0) _serverSessionID++; // paranoia: make sure 0 can be used as a guard value + + // make sure _lameDuckSessions has plenty of memory available in advance (we need might need it in a tight spot later!) + _lameDuckSessions.EnsureSize(256); +} + +ReflectServer :: ~ReflectServer() +{ + // empty +} + +void +ReflectServer :: Cleanup() +{ + // Detach all sessions + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + AbstractReflectSessionRef nextValue = iter.GetValue(); + if (nextValue()) + { + AbstractReflectSession & ars = *nextValue(); + ars.SetFullyAttachedToServer(false); + ars.AboutToDetachFromServer(); + ars.DoOutput(MUSCLE_NO_LIMIT); // one last chance for him to send any leftover data! + ars.SetOwner(NULL); + _lameDuckSessions.AddTail(nextValue); // we'll delete it below + _sessions.Remove(iter.GetKey()); // but prevent other sessions from accessing it now that it's detached + } + } + } + + // Detach all factories + RemoveAcceptFactory(0); + + // This will dereference everything so they can be safely deleted here + _lameDuckSessions.Clear(); + _lameDuckFactories.Clear(); +} + +status_t +ReflectServer :: +ReadyToRun() +{ + return B_NO_ERROR; +} + +const char * +ReflectServer :: +GetServerName() const +{ + return "MUSCLE"; +} + +/** Makes sure the given policy has its BeginIO() called, if necessary, and returns it */ +uint32 +ReflectServer :: +CheckPolicy(Hashtable & policies, const AbstractSessionIOPolicyRef & policyRef, const PolicyHolder & ph, uint64 now) const +{ + AbstractSessionIOPolicy * p = policyRef(); + if (p) + { + // Any policy that is found attached to a session goes into our temporary policy set + policies.PutWithDefault(policyRef); + + // If the session is ready, and BeginIO() hasn't been called on this policy already, do so now + if ((ph.GetSession())&&(p->_hasBegun == false)) + { + CallSetCycleStartTime(*p, now); + p->BeginIO(now); + p->_hasBegun = true; + } + } + return ((ph.GetSession())&&((p == NULL)||(p->OkayToTransfer(ph)))) ? MUSCLE_NO_LIMIT : 0; +} + +void ReflectServer :: CheckForOutOfMemory(const AbstractReflectSessionRef & optSessionRef) +{ +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + if ((ma)&&(ma->HasAllocationFailed())) + { + ma->SetAllocationHasFailed(false); // clear the memory-failed flag + if ((optSessionRef())&&(optSessionRef()->IsExpendable())) + { + if (_doLogging) LogTime(MUSCLE_LOG_CRITICALERROR, "Low Memory! Aborting %s to get some back!\n", optSessionRef()->GetSessionDescriptionString()()); + AddLameDuckSession(optSessionRef); + } + + uint32 dumpCount = DumpBoggedSessions(); // see what other cleanup we can do + if (_doLogging) LogTime(MUSCLE_LOG_CRITICALERROR, "Low Memory condition detected in session [%s]. Dumped " UINT32_FORMAT_SPEC " bogged sessions!\n", optSessionRef()?optSessionRef()->GetSessionDescriptionString()():NULL, dumpCount); + } +#else + (void) optSessionRef; +#endif +} + +status_t +ReflectServer :: +ServerProcessLoop() +{ + TCHECKPOINT; + + _serverStartedAt = GetRunTime64(); + + if (_doLogging) + { + LogTime(MUSCLE_LOG_DEBUG, "This %s server was compiled on " __DATE__ " " __TIME__ "\n", GetServerName()); +#ifdef MUSCLE_AVOID_IPV6 + const char * ipState = "disabled"; +#else + const char * ipState = "enabled"; +#endif + LogTime(MUSCLE_LOG_DEBUG, "The server was compiled with MUSCLE version %s. IPv6 support is %s.\n", MUSCLE_VERSION_STRING, ipState); + LogTime(MUSCLE_LOG_DEBUG, "This server's session ID is " UINT64_FORMAT_SPEC ".\n", GetServerSessionID()); + } + +#ifndef MUSCLE_AVOID_SIGNAL_HANDLING + if ((_mainReflectServerCatchSignals)&&(IsCurrentThreadMainThread())) + { + SignalHandlerSession * shs = newnothrow SignalHandlerSession; + if (shs == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + if (AddNewSession(AbstractReflectSessionRef(shs)) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "ReflectServer::ReadyToRun: Could not install SignalHandlerSession!\n"); + return B_ERROR; + } + } +#endif + + if (ReadyToRun() != B_NO_ERROR) + { + if (_doLogging) LogTime(MUSCLE_LOG_CRITICALERROR, "Server: ReadyToRun() failed, aborting.\n"); + return B_ERROR; + } + + TCHECKPOINT; + + // Print an informative startup message + if ((_doLogging)&&(GetMaxLogLevel() >= MUSCLE_LOG_DEBUG)) + { + if (_factories.HasItems()) + { + bool listeningOnAll = false; + for (HashtableIterator iter(_factories); iter.HasData(); iter++) + { + const IPAddressAndPort & iap = iter.GetKey(); + LogTime(MUSCLE_LOG_DEBUG, "%s is listening on port %u ", GetServerName(), iap.GetPort()); + if (iap.GetIPAddress() == invalidIP) + { + Log(MUSCLE_LOG_DEBUG, "on all network interfaces.\n"); + listeningOnAll = true; + } + else Log(MUSCLE_LOG_DEBUG, "on network interface %s\n", Inet_NtoA(iap.GetIPAddress())()); + } + + if (listeningOnAll) + { + Queue ifs; + if ((GetNetworkInterfaceInfos(ifs) == B_NO_ERROR)&&(ifs.HasItems())) + { + LogTime(MUSCLE_LOG_DEBUG, "This host's network interface addresses are as follows:\n"); + for (uint32 i=0; i policies; + + while(ClearLameDucks() == B_NO_ERROR) + { + TCHECKPOINT; + EventLoopCycleBegins(); + + uint64 nextPulseAt = MUSCLE_TIME_NEVER; // running minimum of everything that wants to be Pulse()'d + + // Set up socket multiplexer registrations and Pulse() timing info for all our different components + { + const uint64 now = GetRunTime64(); // nothing in this scope is supposed to take a significant amount of time to execute, so just calculate this once + + TCHECKPOINT; + + // Set up the session factories so we can be notified when a new connection is received + if (_factories.HasItems()) + { + for (HashtableIterator iter(_factories); iter.HasData(); iter++) + { + ConstSocketRef * nextAcceptSocket = iter.GetValue()()->IsReadyToAcceptSessions() ? _factorySockets.Get(iter.GetKey()) : NULL; + int nfd = nextAcceptSocket ? nextAcceptSocket->GetFileDescriptor() : -1; + if (nfd >= 0) (void) _multiplexer.RegisterSocketForReadReady(nfd); + CallGetPulseTimeAux(*iter.GetValue()(), now, nextPulseAt); + } + } + + TCHECKPOINT; + + // Set up the sessions, their associated IO-gateways, and their IOPolicies + if (_sessions.HasItems()) + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + AbstractReflectSession * session = iter.GetValue()(); + if (session) + { + session->_maxInputChunk = session->_maxOutputChunk = 0; + AbstractMessageIOGateway * g = session->GetGateway()(); + if (g) + { + int sessionReadFD = session->GetSessionReadSelectSocket().GetFileDescriptor(); + if ((sessionReadFD >= 0)&&(session->IsConnectingAsync() == false)) + { + session->_maxInputChunk = CheckPolicy(policies, session->GetInputPolicy(), PolicyHolder(session->IsReadyForInput() ? session : NULL, true), now); + if (session->_maxInputChunk > 0) (void) _multiplexer.RegisterSocketForReadReady(sessionReadFD); + } + + int sessionWriteFD = session->GetSessionWriteSelectSocket().GetFileDescriptor(); + if (sessionWriteFD >= 0) + { + bool out; + if (session->IsConnectingAsync()) + { + out = true; // so we can watch for the async-connect event +#if defined(WIN32) + // Under Windows, failed asynchronous TCP connect()'s are communicated via the a raised exception-flag + (void) _multiplexer.RegisterSocketForExceptionRaised(sessionWriteFD); +#endif + } + else + { + session->_maxOutputChunk = CheckPolicy(policies, session->GetOutputPolicy(), PolicyHolder(session->HasBytesToOutput() ? session : NULL, false), now); + out = ((session->_maxOutputChunk > 0)||((g->GetDataIO()())&&(g->GetDataIO()()->HasBufferedOutput()))); + } + + if (out) + { + (void) _multiplexer.RegisterSocketForWriteReady(sessionWriteFD); + if (session->_lastByteOutputAt == 0) session->_lastByteOutputAt = now; // the bogged-session-clock starts ticking when we first want to write... + if (session->_outputStallLimit != MUSCLE_TIME_NEVER) nextPulseAt = muscleMin(nextPulseAt, session->_lastByteOutputAt+session->_outputStallLimit); + } + else session->_lastByteOutputAt = 0; // If we no longer want to write, then the bogged-session-clock-timeout is cancelled + } + + TCHECKPOINT; + CallGetPulseTimeAux(*g, now, nextPulseAt); + TCHECKPOINT; + } + TCHECKPOINT; + CallGetPulseTimeAux(*session, now, nextPulseAt); + TCHECKPOINT; + } + } + } + + TCHECKPOINT; + CallGetPulseTimeAux(*this, now, nextPulseAt); + TCHECKPOINT; + + // Set up the Session IO Policies + if (policies.HasItems()) + { + // Now that the policies know *who* amongst their policyholders will be reading/writing, + // let's ask each activated policy *how much* each policyholder should be allowed to read/write. + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + AbstractReflectSession * session = iter.GetValue()(); + if (session) + { + AbstractSessionIOPolicy * inPolicy = session->GetInputPolicy()(); + AbstractSessionIOPolicy * outPolicy = session->GetOutputPolicy()(); + if ((inPolicy)&&( session->_maxInputChunk > 0)) session->_maxInputChunk = inPolicy->GetMaxTransferChunkSize(PolicyHolder(session, true)); + if ((outPolicy)&&(session->_maxOutputChunk > 0)) session->_maxOutputChunk = outPolicy->GetMaxTransferChunkSize(PolicyHolder(session, false)); + } + } + + // Now that all is prepared, calculate all the policies' wakeup times + TCHECKPOINT; + for (HashtableIterator iter(policies); iter.HasData(); iter++) CallGetPulseTimeAux(*iter.GetKey()(), now, nextPulseAt); + TCHECKPOINT; + } + } + + TCHECKPOINT; + + // This block is the center of the MUSCLE server's universe -- where we sit and wait for the next event + if (_multiplexer.WaitForEvents(nextPulseAt) < 0) + { + if (_doLogging) LogTime(MUSCLE_LOG_CRITICALERROR, "WaitForEvents() failed, aborting!\n"); + ClearLameDucks(); + return B_ERROR; + } + + // Each event-loop cycle officially "starts" as soon as WaitForEvents() returns + CallSetCycleStartTime(*this, GetRunTime64()); + + TCHECKPOINT; + + // Before we do any session I/O, make sure there hasn't been a generalized memory failure + CheckForOutOfMemory(AbstractReflectSessionRef()); + + TCHECKPOINT; + + // Do I/O for each of our attached sessions + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + TCHECKPOINT; + + AbstractReflectSessionRef & sessionRef = iter.GetValue(); + AbstractReflectSession * session = sessionRef(); + if (session) + { +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + if (ma) (void) ma->SetAllocationHasFailed(false); // (session)'s responsibility for starts here! If we run out of mem on his watch, he's history +#endif + + TCHECKPOINT; + + CallSetCycleStartTime(*session, GetRunTime64()); + TCHECKPOINT; + CallPulseAux(*session, session->GetCycleStartTime()); + { + AbstractMessageIOGateway * gateway = session->GetGateway()(); + if (gateway) + { + TCHECKPOINT; + + CallSetCycleStartTime(*gateway, GetRunTime64()); + CallPulseAux(*gateway, gateway->GetCycleStartTime()); + } + } + + TCHECKPOINT; + + int readSock = session->GetSessionReadSelectSocket().GetFileDescriptor(); + if (readSock >= 0) + { + int32 readBytes = 0; + if (_multiplexer.IsSocketReadyForRead(readSock)) + { + readBytes = session->DoInput(*session, session->_maxInputChunk); // session->MessageReceivedFromGateway() gets called here + + AbstractSessionIOPolicy * p = session->GetInputPolicy()(); + if ((p)&&(readBytes >= 0)) p->BytesTransferred(PolicyHolder(session, true), (uint32)readBytes); + } + + TCHECKPOINT; + + if (readBytes < 0) + { + bool wasConnecting = session->IsConnectingAsync(); + if ((DisconnectSession(session) == false)&&(_doLogging)) LogTime(MUSCLE_LOG_DEBUG, "Connection for %s %s (read error).\n", session->GetSessionDescriptionString()(), wasConnecting?"failed":"was severed"); + } + } + + int writeSock = session->GetSessionWriteSelectSocket().GetFileDescriptor(); + if (writeSock >= 0) + { + int32 wroteBytes = 0; + + TCHECKPOINT; + + if (_multiplexer.IsSocketReadyForWrite(writeSock)) + { + if (session->IsConnectingAsync()) wroteBytes = (FinalizeAsyncConnect(sessionRef) == B_NO_ERROR) ? 0 : -1; + else + { + // if the session's DataIO object is still has bytes buffered for output, try to send them now + AbstractMessageIOGateway * g = session->GetGateway()(); + if (g) + { + DataIO * io = g->GetDataIO()(); + if (io) io->WriteBufferedOutput(); + } + + wroteBytes = session->DoOutput(session->_maxOutputChunk); + + AbstractSessionIOPolicy * p = session->GetOutputPolicy()(); + if ((p)&&(wroteBytes >= 0)) p->BytesTransferred(PolicyHolder(session, false), (uint32)wroteBytes); + } + } +#if defined(WIN32) + if (_multiplexer.IsSocketExceptionRaised(writeSock)) wroteBytes = -1; // async connect() failed! +#endif + + TCHECKPOINT; + + if (wroteBytes < 0) + { + bool wasConnecting = session->IsConnectingAsync(); + if ((DisconnectSession(session) == false)&&(_doLogging)) LogTime(MUSCLE_LOG_DEBUG, "Connection for %s %s (write error).\n", session->GetSessionDescriptionString()(), wasConnecting?"failed":"was severed"); + } + else if (session->_lastByteOutputAt > 0) + { + // Check for output stalls + const uint64 now = GetRunTime64(); + if ((wroteBytes > 0)||(session->_maxOutputChunk == 0)) session->_lastByteOutputAt = now; // reset the moribundness-timer + else if (now-session->_lastByteOutputAt > session->_outputStallLimit) + { + if (_doLogging) LogTime(MUSCLE_LOG_WARNING, "Connection for %s timed out (output stall, no data movement for " UINT64_FORMAT_SPEC " seconds).\n", session->GetSessionDescriptionString()(), MicrosToSeconds(session->_outputStallLimit)); + (void) DisconnectSession(session); + } + } + } + } + TCHECKPOINT; + CheckForOutOfMemory(sessionRef); // if the session caused a memory error, give him the boot + } + } + + TCHECKPOINT; + + // Pulse() our other PulseNode objects, as necessary + { + // Tell the session policies we're done doing I/O (for now) + if (policies.HasItems()) + { + for (HashtableIterator iter(policies); iter.HasData(); iter++) + { + AbstractSessionIOPolicy * p = iter.GetKey()(); + if (p->_hasBegun) + { + p->EndIO(GetRunTime64()); + p->_hasBegun = false; + } + } + } + + // Pulse the Policies + if (policies.HasItems()) for (HashtableIterator iter(policies); iter.HasData(); iter++) CallPulseAux(*iter.GetKey()(), GetRunTime64()); + + // Pulse the Server + CallPulseAux(*this, GetRunTime64()); + } + policies.Clear(); + + TCHECKPOINT; + + // Lastly, check our accepting ports to see if anyone is trying to connect... + if (_factories.HasItems()) + { + for (HashtableIterator iter(_factories); iter.HasData(); iter++) + { + ReflectSessionFactory * factory = iter.GetValue()(); + CallSetCycleStartTime(*factory, GetRunTime64()); + CallPulseAux(*factory, factory->GetCycleStartTime()); + + if (factory->IsReadyToAcceptSessions()) + { + ConstSocketRef * as = _factorySockets.Get(iter.GetKey()); + int fd = as ? as->GetFileDescriptor() : -1; + if ((fd >= 0)&&(_multiplexer.IsSocketReadyForRead(fd))) (void) DoAccept(iter.GetKey(), *as, factory); + } + } + } + + TCHECKPOINT; + EventLoopCycleEnds(); + } + + TCHECKPOINT; + (void) ClearLameDucks(); // get rid of any leftover ducks + TCHECKPOINT; + + return B_NO_ERROR; +} + +void ReflectServer :: ShutdownIOFor(AbstractReflectSession * session) +{ + AbstractMessageIOGateway * gw = session->GetGateway()(); + if (gw) + { + DataIO * io = gw->GetDataIO()(); + if (io) io->Shutdown(); // so we won't try to do I/O on this one anymore + } +} + +status_t ReflectServer :: ClearLameDucks() +{ + // Delete any factories that were previously marked for deletion + _lameDuckFactories.Clear(); + + // Remove any sessions that were previously marked for removal + AbstractReflectSessionRef duckRef; + while(_lameDuckSessions.RemoveHead(duckRef) == B_NO_ERROR) + { + AbstractReflectSession * duck = duckRef(); + if (duck) + { + const String & id = duck->GetSessionIDString(); + if (_sessions.ContainsKey(&id)) + { + duck->SetFullyAttachedToServer(false); + duck->AboutToDetachFromServer(); + duck->DoOutput(MUSCLE_NO_LIMIT); // one last chance for him to send any leftover data! + if (_doLogging) LogTime(MUSCLE_LOG_DEBUG, "Closed %s (" UINT32_FORMAT_SPEC " left)\n", duck->GetSessionDescriptionString()(), _sessions.GetNumItems()-1); + duck->SetOwner(NULL); + (void) _sessions.Remove(&id); + } + } + } + + return _keepServerGoing ? B_NO_ERROR : B_ERROR; +} + +void ReflectServer :: LogAcceptFailed(int lvl, const char * desc, const char * ipbuf, const IPAddressAndPort & iap) +{ + if (_doLogging) + { + if (iap.GetIPAddress() == invalidIP) LogTime(lvl, "%s for [%s] on port %u.\n", desc, ipbuf?ipbuf:"???", iap.GetPort()); + else LogTime(lvl, "%s for [%s] on port %u on interface [%s].\n", desc, ipbuf?ipbuf:"???", iap.GetPort(), Inet_NtoA(iap.GetIPAddress())()); + } +} + +status_t ReflectServer :: DoAccept(const IPAddressAndPort & iap, const ConstSocketRef & acceptSocket, ReflectSessionFactory * optFactory) +{ + // Accept a new connection and try to start up a session for it + ip_address acceptedFromIP; + ConstSocketRef newSocket = Accept(acceptSocket, &acceptedFromIP); + if (newSocket()) + { + NestCountGuard ncg(_inDoAccept); + IPAddressAndPort nip(acceptedFromIP, iap.GetPort()); + ip_address remoteIP = GetPeerIPAddress(newSocket, true); + if (remoteIP == invalidIP) LogAcceptFailed(MUSCLE_LOG_DEBUG, "GetPeerIPAddress() failed", NULL, nip); + else + { + char ipbuf[64]; Inet_NtoA(remoteIP, ipbuf); + + AbstractReflectSessionRef newSessionRef; + if (optFactory) newSessionRef = optFactory->CreateSession(ipbuf, nip); + + if (newSessionRef()) + { + if (newSessionRef()->_isExpendable.HasValueBeenSet() == false) newSessionRef()->SetExpendable(true); + newSessionRef()->_ipAddressAndPort = iap; + newSessionRef()->_isConnected = true; + if (AddNewSession(newSessionRef, newSocket) == B_NO_ERROR) + { + newSessionRef()->_wasConnected = true; + return B_NO_ERROR; // success! + } + else + { + newSessionRef()->_isConnected = false; + newSessionRef()->_ipAddressAndPort.Reset(); + } + } + else if (optFactory) LogAcceptFailed(MUSCLE_LOG_DEBUG, "Session creation denied", ipbuf, nip); + } + } + else LogAcceptFailed(MUSCLE_LOG_DEBUG, "Accept() failed", NULL, iap); + + return B_ERROR; +} + +uint32 ReflectServer :: DumpBoggedSessions() +{ + TCHECKPOINT; + + uint32 ret = 0; + + // New for v1.82: also find anyone whose outgoing message queue is getting too large. + // (where "too large", for now, is more than 5 megabytes) + // This could happen if someone has a really slow Internet connection, or has decided to + // stop reading from their end indefinitely for some reason. + const int MAX_MEGABYTES = 5; + for (HashtableIterator xiter(GetSessions()); xiter.HasData(); xiter++) + { + AbstractReflectSession * asr = xiter.GetValue()(); + if ((asr)&&(asr->IsExpendable())) + { + AbstractMessageIOGateway * gw = asr->GetGateway()(); + if (gw) + { + uint32 qSize = 0; + const Queue & q = gw->GetOutgoingMessageQueue(); + for (int k=q.GetNumItems()-1; k>=0; k--) + { + const Message * qmsg = q[k](); + if (qmsg) qSize += qmsg->FlattenedSize(); + } + if (qSize > MAX_MEGABYTES*1024*1024) + { + if (_doLogging) LogTime(MUSCLE_LOG_CRITICALERROR, "Low Memory! Aborting %s to get some back!\n", asr->GetSessionDescriptionString()()); + AddLameDuckSession(xiter.GetValue()); + ret++; + } + } + } + } + return ret; +} + +AbstractReflectSessionRef +ReflectServer :: +GetSession(const String & name) const +{ + AbstractReflectSessionRef ref; + (void) _sessions.Get(&name, ref); + return ref; +} + +AbstractReflectSessionRef +ReflectServer :: +GetSession(uint32 id) const +{ + char buf[64]; sprintf(buf, UINT32_FORMAT_SPEC, id); + return GetSession(buf); +} + +ReflectSessionFactoryRef +ReflectServer :: +GetFactory(uint16 port, const ip_address & optInterfaceIP) const +{ + ReflectSessionFactoryRef ref; + (void) _factories.Get(IPAddressAndPort(optInterfaceIP, port), ref); + return ref; +} + +status_t +ReflectServer :: +ReplaceSession(const AbstractReflectSessionRef & newSessionRef, AbstractReflectSession * oldSession) +{ + TCHECKPOINT; + + // move the gateway from the old session to the new one... + AbstractReflectSession * newSession = newSessionRef(); + if (newSession == NULL) return B_ERROR; + + newSession->SetGateway(oldSession->GetGateway()); + newSession->_hostName = oldSession->_hostName; + newSession->_ipAddressAndPort = oldSession->_ipAddressAndPort; + + if (AttachNewSession(newSessionRef) == B_NO_ERROR) + { + oldSession->SetGateway(AbstractMessageIOGatewayRef()); /* gateway now belongs to newSession */ + EndSession(oldSession); + return B_NO_ERROR; + } + else + { + // Oops, rollback changes and error out + newSession->SetGateway(AbstractMessageIOGatewayRef()); + newSession->_hostName.Clear(); + newSession->_ipAddressAndPort.Reset(); + return B_ERROR; + } +} + +bool ReflectServer :: DisconnectSession(AbstractReflectSession * session) +{ + session->SetConnectingAsync(false); + session->_isConnected = false; + session->_scratchReconnected = false; // if the session calls Reconnect() this will be set to true below + + AbstractMessageIOGateway * oldGW = session->GetGateway()(); + DataIO * oldIO = oldGW ? oldGW->GetDataIO()() : NULL; + + bool ret = session->ClientConnectionClosed(); + + AbstractMessageIOGateway * newGW = session->GetGateway()(); + DataIO * newIO = newGW ? newGW->GetDataIO()() : NULL; + + if (ret) + { + ShutdownIOFor(session); + AddLameDuckSession(session); + } + else if ((session->_scratchReconnected == false)&&(newGW == oldGW)&&(newIO == oldIO)) ShutdownIOFor(session); + + return ret; +} + +void +ReflectServer :: +EndSession(AbstractReflectSession * who) +{ + AddLameDuckSession(who); +} + +void +ReflectServer :: +EndServer() +{ + _keepServerGoing = false; +} + +status_t +ReflectServer :: +PutAcceptFactory(uint16 port, const ReflectSessionFactoryRef & factoryRef, const ip_address & optInterfaceIP, uint16 * optRetPort) +{ + if (port > 0) (void) RemoveAcceptFactory(port, optInterfaceIP); // Get rid of any previous acceptor on this port... + + ReflectSessionFactory * f = factoryRef(); + if (f) + { + ConstSocketRef acceptSocket = CreateAcceptingSocket(port, 20, &port, optInterfaceIP); + if (acceptSocket()) + { + IPAddressAndPort iap(optInterfaceIP, port); + if ((SetSocketBlockingEnabled(acceptSocket, false) == B_NO_ERROR)&&(_factories.Put(iap, factoryRef) == B_NO_ERROR)) + { + if (_factorySockets.Put(iap, acceptSocket) == B_NO_ERROR) + { + f->SetOwner(this); + if (optRetPort) *optRetPort = port; + if (f->AttachedToServer() == B_NO_ERROR) + { + f->SetFullyAttachedToServer(true); + return B_NO_ERROR; + } + else + { + (void) RemoveAcceptFactory(port, optInterfaceIP); + return B_ERROR; + } + } + else _factories.Remove(iap); // roll back! + } + } + } + return B_ERROR; +} + +status_t +ReflectServer :: +RemoveAcceptFactoryAux(const IPAddressAndPort & iap) +{ + ReflectSessionFactoryRef ref; + if (_factories.Get(iap, ref) == B_NO_ERROR) // don't remove it yet, in case AboutToDetachFromServer() calls a method on us + { + ref()->SetFullyAttachedToServer(false); + ref()->AboutToDetachFromServer(); + _lameDuckFactories.AddTail(ref); // we'll actually have (factory) deleted later on, since at the moment + // we could be in the middle of one of (ref())'s own method calls! + (void) _factories.Remove(iap); // must call this AFTER the AboutToDetachFromServer() call! + + // See if there are any other instances of this factory still present. + // if there aren't, we'll clear the factory's owner-pointer so he can't access us anymore + if (_factories.IndexOfValue(ref) < 0) ref()->SetOwner(NULL); + + (void) _factorySockets.Remove(iap); + + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t +ReflectServer :: +RemoveAcceptFactory(uint16 port, const ip_address & optInterfaceIP) +{ + if (port > 0) return RemoveAcceptFactoryAux(IPAddressAndPort(optInterfaceIP, port)); + else + { + for (HashtableIterator iter(_factories); iter.HasData(); iter++) (void) RemoveAcceptFactoryAux(iter.GetKey()); + return B_NO_ERROR; + } +} + +status_t +ReflectServer :: +FinalizeAsyncConnect(const AbstractReflectSessionRef & ref) +{ + AbstractReflectSession * session = ref(); + if ((session)&&(muscle::FinalizeAsyncConnect(session->GetSessionReadSelectSocket()) == B_NO_ERROR)) + { + session->SetConnectingAsync(false); + session->_isConnected = session->_wasConnected = true; + session->AsyncConnectCompleted(); + return B_NO_ERROR; + } + return B_ERROR; +} + +uint64 +ReflectServer :: +GetNumAvailableBytes() const +{ +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + const MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + return (ma) ? ma->GetNumAvailableBytes((size_t)GetNumUsedBytes()) : ((uint64)-1); +#else + return ((uint64)-1); +#endif +} + +uint64 +ReflectServer :: +GetMaxNumBytes() const +{ +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + const MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + return ma ? ma->GetMaxNumBytes() : ((uint64)-1); +#else + return ((uint64)-1); +#endif +} + +uint64 +ReflectServer :: +GetNumUsedBytes() const +{ +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + return GetNumAllocatedBytes(); +#else + return 0; // if we're not tracking, there is no way to know! +#endif +} + +void +ReflectServer :: AddLameDuckSession(const AbstractReflectSessionRef & ref) +{ + if ((_lameDuckSessions.IndexOf(ref) < 0)&&(_lameDuckSessions.AddTail(ref) != B_NO_ERROR)&&(_doLogging)) LogTime(MUSCLE_LOG_CRITICALERROR, "Server: AddLameDuckSession() failed, I'm REALLY in trouble! Aggh!\n"); +} + +void +ReflectServer :: +AddLameDuckSession(AbstractReflectSession * who) +{ + TCHECKPOINT; + + for (HashtableIterator xiter(GetSessions()); xiter.HasData(); xiter++) + { + if (xiter.GetValue()() == who) + { + AddLameDuckSession(xiter.GetValue()); + break; + } + } +} + +}; // end namespace muscle diff --git a/reflector/ReflectServer.h b/reflector/ReflectServer.h new file mode 100644 index 00000000..bd46f4c4 --- /dev/null +++ b/reflector/ReflectServer.h @@ -0,0 +1,328 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleReflectServer_h +#define MuscleReflectServer_h + +#include "reflector/AbstractReflectSession.h" +#include "util/NestCount.h" +#include "util/SocketMultiplexer.h" + +namespace muscle { + +/** This class represents a MUSCLE server: It runs on a centrally located machine, + * and many clients may connect to it simultaneously. This server can then redirect messages + * uploaded by any client to other clients in a somewhat efficient manner. + * This class can be used as-is, or subclassed if necessary. + * There is typically only one ReflectServer object present in a given MUSCLE server program. + */ +class ReflectServer : public RefCountable, public PulseNode, private PulseNodeManager, private CountedObject +{ +public: + /** Constructor. */ + ReflectServer(); + + /** Destructor. */ + virtual ~ReflectServer(); + + /** The main loop for the message reflection server. + * This method will not return until the server stops running (usually due to an error). + * @return B_NO_ERROR if the server has decided to exit peacefully, or B_ERROR if there was a + * fatal error during setup or execution. + */ + virtual status_t ServerProcessLoop(); + + /** This function may be called zero or more times before ServerProcessLoop() + * Each call adds one port that the server should listen on, and the factory object + * to use to create sessions when a connection is received on that port. + * @param port The TCP port the server will listen on. (muscled's traditional port is 2960) + * If this port is zero, then the server will choose an available port number to use. + * If this port is the same as one specified in a previous call to PutAcceptFactory(), + * the old factory associated with that port will be replaced with this one. + * @param sessionFactoryRef Reference to a factory object that can generate new sessions when needed. + * @param optInterfaceIP Optional local interface address to listen on. If not specified, or if specified + * as (invalidIP), then connections will be accepted from all local network interfaces. + * @param optRetPort If specified non-NULL, then on success the port that the factory was bound to will + * be placed into this parameter. + * @return B_NO_ERROR on success, B_ERROR on failure (couldn't bind to socket?) + */ + virtual status_t PutAcceptFactory(uint16 port, const ReflectSessionFactoryRef & sessionFactoryRef, const ip_address & optInterfaceIP = invalidIP, uint16 * optRetPort = NULL); + + /** Remove a listening port callback that was previously added by PutAcceptFactory(). + * @param port whose callback should be removed. If (port) is set to zero, all callbacks will be removed. + * @param optInterfaceIP Interface(s) that the specified callbacks were assigned to in their PutAcceptFactory() call. + * This parameter is ignored when (port) is zero. + * @returns B_NO_ERROR on success, or B_ERROR if a factory for the specified port was not found. + */ + virtual status_t RemoveAcceptFactory(uint16 port, const ip_address & optInterfaceIP = invalidIP); + + /** + * Called after the server is set up, but just before accepting any connections. + * Should return B_NO_ERROR if it's okay to continue, or B_ERROR to abort and shut down the server. + * @return Default implementation returns B_NO_ERROR. + */ + virtual status_t ReadyToRun(); + + /** + * Adds a new session that uses the given socket for I/O. + * @param ref New session to add to the server. + * @param socket The TCP socket that the new session should use, or a NULL reference, if the new session is to have no client connection (or use the default socket, if CreateDefaultSocket() has been overridden by the session's subclass). Note that if the session already has a gateway and DataIO installed, then the DataIO's existing socket will be used instead, and this socket will be ignored. + * @return B_NO_ERROR if the new session was added successfully, or B_ERROR if there was an error setting it up. + */ + virtual status_t AddNewSession(const AbstractReflectSessionRef & ref, const ConstSocketRef & socket); + + /** Convenience method: Calls AddNewSession() with a NULL Socket reference, so the session's default socket + * (obtained by calling ref()->CreateDefaultSocket()) will be used. + */ + status_t AddNewSession(const AbstractReflectSessionRef & ref) {return AddNewSession(ref, ConstSocketRef());} + + /** + * Like AddNewSession(), only creates a session that connects asynchronously to + * the given IP address. AttachedToServer() will be called immediately on the + * session, and then when the connection is complete, AsyncConnectCompleted() will + * be called. Other than that, however, (session) will behave like any other session, + * except any I/O messages for the client won't be transferred until the connection + * completes. + * @param ref New session to add to the server. + * @param targetIPAddress IP address to connect to + * @param port Port to connect to at that IP address. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (ref). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR if the session was successfully added, or B_ERROR on error (out-of-memory?) + */ + status_t AddNewConnectSession(const AbstractReflectSessionRef & ref, const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** + * Like AddNewConnectSession(), except that the added session will not initiate + * a TCP connection to the specified address immediately. Instead, it will just + * hang out and do nothing until you call Reconnect() on it. Only then will it + * create the TCP connection to the address specified here. + * @param ref New session to add to the server. + * @param targetIPAddress IP address to connect to + * @param port Port to connect to at that IP address. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (ref). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR if the session was successfully added, or B_ERROR on error (out-of-memory?) + */ + status_t AddNewDormantConnectSession(const AbstractReflectSessionRef & ref, const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** + * Should be called just before the ReflectServer is to be destroyed. + * this in a good place to put any cleanup code. Be sure + * to call Cleanup() of your parent class as well! + * (We can't just do this in the destructor, as some cleanup + * relies on the subclass still being functional, which it isn't + * when our destructor gets called!) + */ + virtual void Cleanup(); + + /** Accessor to our central-state repository message */ + Message & GetCentralState() {return _centralState;} + + /** Set whether or not we should log informational messages when sessions are added and removed, etc. + * Default state is true. + */ + void SetDoLogging(bool log) {_doLogging = log;} + + /** Returns whether or not we should be logging informational messages. */ + bool GetDoLogging() const {return _doLogging;} + + /** + * Returns a human-readable string that describes the type of server that is running. + * @return Default implementation returns "MUSCLE". + */ + virtual const char * GetServerName() const; + + /** Returns a read-only reference to our table of sessions currently attached to this server. */ + const Hashtable & GetSessions() const {return _sessions;} + + /** Convenience method: Given a session ID string, returns a reference to the session, or a NULL reference if no such session exists. */ + AbstractReflectSessionRef GetSession(const String & sessionName) const; + + /** Convenience method: Given a session ID number, returns a reference to the session, or a NULL reference if no such session exists. */ + AbstractReflectSessionRef GetSession(uint32 sessionID) const; + + /** Returns an iterator that allows one to iterate over all the session factories currently attached to this server. */ + const Hashtable & GetFactories() const {return _factories;} + + /** Convenience method: Given a port number, returns a reference to the factory of that port, or a + * NULL reference if no such factory exists. + * @param port number to check + * @param optInterfaceIP If the factory was created to listen on a specific local interface address + * (when it was passed in to PutAcceptFactory()), then specify that address again here. + * Defaults to (invalidIP), indicating a factory that listens on all local network interfaces. + */ + ReflectSessionFactoryRef GetFactory(uint16 port, const ip_address & optInterfaceIP = invalidIP) const; + + /** Call this and the server will quit ASAP */ + void EndServer(); + + /** Returns the value GetRunTime64() was at, when the ServerProcessLoop() event loop started. */ + uint64 GetServerStartTime() const {return _serverStartedAt;} + + /** Returns the number of bytes that are currently available to be allocated, or ((uint64)-1) + * if no memory watcher was specified in the constructor. + */ + uint64 GetNumAvailableBytes() const; + + /** Returns the maximum number of bytes that may be allocated at any given time, or ((uint64)-1) + * if no memory watcher was specified in the constructor. + */ + uint64 GetMaxNumBytes() const; + + /** Returns the number of bytes that are currently allocated, or ((uint64)-1) + * if no memory watcher was specified in the constructor. + */ + uint64 GetNumUsedBytes() const; + + /** Returns a reference to a table mapping IP addresses to custom strings... + * This table may be examined or altered. When a new connection is accepted, + * the ReflectServer will consult this table for the address-level MUSCLE node's + * name. If an entry is found, it will be used verbatim; otherwise, a name will + * be created based on the peer's IP address. Useful for e.g. NAT remapping... + */ + Hashtable & GetAddressRemappingTable() {return _remapIPs;} + + /** Read-only implementation of the above */ + const Hashtable & GetAddressRemappingTable() const {return _remapIPs;} + + /** Returns a number that is (hopefully) unique to each ReflectSession instance. + * This number will be different each time the server is run, but will remain the same for the duration of the server's life. + */ + uint64 GetServerSessionID() const {return _serverSessionID;} + + /** This method is called at the beginning of each iteration of the event loop. + * Default implementation is a no-op. + */ + virtual void EventLoopCycleBegins() {/* empty */} + + /** This method is called at the end of each iteration of the event loop. + * Default implementation is a no-op. + */ + virtual void EventLoopCycleEnds() {/* empty */} + +#ifdef MUSCLE_ENABLE_SSL + /** Sets the SSL private key data that should be used to authenticate and encrypt + * accepted incoming TCP connections. Default state is a NULL reference (i.e. no SSL + * encryption will be used for incoming connecitons). + * @param privateKey Reference to the contents of a .pem file containing both + * a PRIVATE KEY section and a CERTIFICATE section, or a NULL reference + * if you want to make SSL disabled again. + * @note this method is only available if MUSCLE_ENABLE_OPENSSL is defined. + */ + void SetSSLPrivateKey(const ConstByteBufferRef & privateKey) {_privateKey = privateKey;} + + /** Returns the current SSL private key data, or a NULL ref if there isn't any. */ + const ConstByteBufferRef & GetSSLPrivateKey() const {return _privateKey;} + + /** Sets the SSL public key data that should be used to authenticate and encrypt + * outgoing TCP connections. Default state is a NULL reference (i.e. no SSL + * encryption will be used for outgoing connections). + * @param publicKey Reference to the contents of a .pem file containing a CERTIFICATE + * section, or a NULL reference if you want to make SSL disabled again. + * @note this method is only available if MUSCLE_ENABLE_OPENSSL is defined. + */ + void SetSSLPublicKeyCertificate(const ConstByteBufferRef & publicKey) {_publicKey = publicKey;} + + /** Returns the current SSL public key data, or a NULL ref if there isn't any. */ + const ConstByteBufferRef & GetSSLPublicKeyCertificate() const {return _publicKey;} +#endif + +protected: + /** + * This version of AddNewSession (which is called by the previous + * version) assumes that the gateway, hostname, port, etc of the + * new session have already been set up. + * @param ref The new session to add. + * @return B_NO_ERROR if the new session was added successfully, or B_ERROR if there was an error setting it up. + */ + virtual status_t AttachNewSession(const AbstractReflectSessionRef & ref); + + /** Called by a session to send a message to its factory. + * @see AbstractReflectSession::SendMessageToFactory() for details. + */ + status_t SendMessageToFactory(AbstractReflectSession * session, const MessageRef & msgRef, void * userData); + + /** + * Called by a session to get itself replaced (the + * new session will continue using the same message io streams + * as the old one) + * @return B_NO_ERROR on success, B_ERROR if the new session + * returns an error in its AttachedToServer() method. If + * B_ERROR is returned, then this call is guaranteed not to + * have had any effect on the old session. + */ + status_t ReplaceSession(const AbstractReflectSessionRef & newSession, AbstractReflectSession * replaceThisOne); + + /** Called by a session to get itself removed & destroyed */ + void EndSession(AbstractReflectSession * which); + + /** Called by a session to force its TCP connection to be closed + * @param which The session to force-disconnect. + * @returns true iff the session has decided to terminate itself, or false if it decided to continue + * hanging around the server even though its client connection has been severed. + * @see AbstractReflectSession::ClientConnectionClosed(). + */ + bool DisconnectSession(AbstractReflectSession * which); + +private: + friend class AbstractReflectSession; + void AddLameDuckSession(const AbstractReflectSessionRef & whoRef); + void AddLameDuckSession(AbstractReflectSession * who); // convenience method ... less efficient + void ShutdownIOFor(AbstractReflectSession * session); + status_t ClearLameDucks(); // returns B_NO_ERROR if the server should keep going, or B_ERROR otherwise + uint32 DumpBoggedSessions(); + status_t RemoveAcceptFactoryAux(const IPAddressAndPort & iap); + status_t FinalizeAsyncConnect(const AbstractReflectSessionRef & ref); + status_t DoAccept(const IPAddressAndPort & iap, const ConstSocketRef & acceptSocket, ReflectSessionFactory * optFactory); + void LogAcceptFailed(int lvl, const char * desc, const char * ipbuf, const IPAddressAndPort & iap); + uint32 CheckPolicy(Hashtable & policies, const AbstractSessionIOPolicyRef & policyRef, const PolicyHolder & ph, uint64 now) const; + void CheckForOutOfMemory(const AbstractReflectSessionRef & optSessionRef); + + Hashtable _factories; + Hashtable _factorySockets; + + Queue _lameDuckFactories; // for delayed-deletion of factories when they go away + + Message _centralState; + Hashtable _sessions; + Queue _lameDuckSessions; // sessions that are due to be removed + bool _keepServerGoing; + uint64 _serverStartedAt; + bool _doLogging; + uint64 _serverSessionID; + + Hashtable _remapIPs; // for v2.20; custom strings for "special" IP addresses + SocketMultiplexer _multiplexer; + +#ifdef MUSCLE_ENABLE_SSL + ConstByteBufferRef _publicKey; // used for making outgoing TCP connections + ConstByteBufferRef _privateKey; // used for receiving incoming TCP connections +#endif + NestCount _inDoAccept; + NestCount _inDoConnect; +}; +DECLARE_REFTYPES(ReflectServer); + +}; // end namespace muscle + +#endif diff --git a/reflector/ServerComponent.cpp b/reflector/ServerComponent.cpp new file mode 100644 index 00000000..11a1c384 --- /dev/null +++ b/reflector/ServerComponent.cpp @@ -0,0 +1,184 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/ServerComponent.h" +#include "reflector/ReflectServer.h" + +namespace muscle { + +ServerComponent :: +ServerComponent() : _owner(NULL), _fullyAttached(false) +{ + // empty +} + +ServerComponent :: +~ServerComponent() +{ + MASSERT(_owner == NULL, "ServerComponent deleted while still attached to its ReflectServer! Maybe you did not call Cleanup() on the ReflectServer object, or did not forward an AboutToDetachFromServer() call to your superclass's implementation?"); +} + +status_t +ServerComponent :: +AttachedToServer() +{ + return B_NO_ERROR; +} + +void +ServerComponent :: +AboutToDetachFromServer() +{ + // empty +} + +Message & +ServerComponent :: +GetCentralState() const +{ + MASSERT(_owner, "Can not call GetCentralState() while not attached to the server"); + return _owner->GetCentralState(); +} + +const Hashtable & +ServerComponent :: +GetSessions() const +{ + MASSERT(_owner, "Can not call GetSessions() while not attached to the server"); + return _owner->GetSessions(); +} + +AbstractReflectSessionRef +ServerComponent :: +GetSession(uint32 id) const +{ + MASSERT(_owner, "Can not call GetSession() while not attached to the server"); + return _owner->GetSession(id); +} + +AbstractReflectSessionRef +ServerComponent :: +GetSession(const String & id) const +{ + MASSERT(_owner, "Can not call GetSession() while not attached to the server"); + return _owner->GetSession(id); +} + +const Hashtable & +ServerComponent :: +GetFactories() const +{ + MASSERT(_owner, "Can not call GetFactories() while not attached to the server"); + return _owner->GetFactories(); +} + +ReflectSessionFactoryRef +ServerComponent :: +GetFactory(uint16 port) const +{ + MASSERT(_owner, "Can not call GetFactory() while not attached to the server"); + return _owner->GetFactory(port); +} + +status_t +ServerComponent :: +AddNewSession(const AbstractReflectSessionRef & ref, const ConstSocketRef & socket) +{ + MASSERT(_owner, "Can not call AddNewSession() while not attached to the server"); + return _owner->AddNewSession(ref, socket); +} + +status_t +ServerComponent :: +AddNewConnectSession(const AbstractReflectSessionRef & ref, const ip_address & ip, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + MASSERT(_owner, "Can not call AddNewConnectSession() while not attached to the server"); + return _owner->AddNewConnectSession(ref, ip, port, autoReconnectDelay, maxAsyncConnectPeriod); +} + +status_t +ServerComponent :: +AddNewDormantConnectSession(const AbstractReflectSessionRef & ref, const ip_address & ip, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + MASSERT(_owner, "Can not call AddNewDormantConnectSession() while not attached to the server"); + return _owner->AddNewDormantConnectSession(ref, ip, port, autoReconnectDelay, maxAsyncConnectPeriod); +} + +void +ServerComponent :: +EndServer() +{ + MASSERT(_owner, "Can not call EndServer() while not attached to the server"); + _owner->EndServer(); +} + +uint64 +ServerComponent :: +GetServerStartTime() const +{ + MASSERT(_owner, "Can not call GetServerStartTime() while not attached to the server"); + return _owner->GetServerStartTime(); +} + +uint64 +ServerComponent :: +GetServerSessionID() const +{ + MASSERT(_owner, "Can not call GetServerSessionID() while not attached to the server"); + return _owner->GetServerSessionID(); +} + +uint64 +ServerComponent :: +GetNumAvailableBytes() const +{ + MASSERT(_owner, "Can not call GetNumAvailableBytes() while not attached to the server"); + return _owner->GetNumAvailableBytes(); +} + +uint64 +ServerComponent :: +GetMaxNumBytes() const +{ + MASSERT(_owner, "Can not call GetMaxNumBytes() while not attached to the server"); + return _owner->GetMaxNumBytes(); +} + +uint64 +ServerComponent :: +GetNumUsedBytes() const +{ + MASSERT(_owner, "Can not call GetNumUsedBytes() while not attached to the server"); + return _owner->GetNumUsedBytes(); +} + +status_t +ServerComponent :: +PutAcceptFactory(uint16 port, const ReflectSessionFactoryRef & factoryRef, const ip_address & optInterfaceIP, uint16 * optRetPort) +{ + MASSERT(_owner, "Can not call PutAcceptFactory() while not attached to the server"); + return _owner->PutAcceptFactory(port, factoryRef, optInterfaceIP, optRetPort); +} + +status_t +ServerComponent :: +RemoveAcceptFactory(uint16 port, const ip_address & optInterfaceIP) +{ + MASSERT(_owner, "Can not call RemoveAcceptFactory() while not attached to the server"); + return _owner->RemoveAcceptFactory(port, optInterfaceIP); +} + +void +ServerComponent :: +MessageReceivedFromSession(AbstractReflectSession &, const MessageRef &, void *) +{ + // empty +} + +void +ServerComponent :: +MessageReceivedFromFactory(ReflectSessionFactory &, const MessageRef &, void * ) +{ + // empty +} + +}; // end namespace muscle diff --git a/reflector/ServerComponent.h b/reflector/ServerComponent.h new file mode 100644 index 00000000..4e59f42d --- /dev/null +++ b/reflector/ServerComponent.h @@ -0,0 +1,268 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleServerComponent_h +#define MuscleServerComponent_h + +#include "message/Message.h" +#include "util/RefCount.h" +#include "util/PulseNode.h" +#include "util/NetworkUtilityFunctions.h" // for ip_address + +namespace muscle { + +class AbstractReflectSession; +class ReflectServer; +class ReflectSessionFactory; + +DECLARE_REFTYPES(AbstractReflectSession); +DECLARE_REFTYPES(ReflectSessionFactory); + +#ifndef MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS +# define MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS MUSCLE_TIME_NEVER +#endif + +/** + * This class represents any object that can be added to a ReflectServer object + * in one way or another, to help define the ReflectServer's behaviour. This + * class provides callback wrappers that let you operate on the server's state. + */ +class ServerComponent : public RefCountable, public PulseNode, private CountedObject +{ +public: + /** Default Constructor. */ + ServerComponent(); + + /** Destructor. */ + virtual ~ServerComponent(); + + /** + * This method is called when this object has been added to + * a ReflectServer object. When this method is called, it + * is okay to call the other methods in the ServerComponent API. + * Should return B_NO_ERROR if everything is okay; something + * else if there is a problem and the attachment should be aborted. + * Default implementation does nothing and returns B_NO_ERROR. + * If you override this, be sure to call your superclass's implementation + * of this method as the first thing in your implementation, and + * if it doesn't return B_NO_ERROR, immediately return an error yourself. + */ + virtual status_t AttachedToServer(); + + /** + * This method is called just before we are removed from the + * ReflectServer object. Methods in the ServerComponent API may + * still be called at this time (but not after this method returns). + * Default implementation does nothing. + * If you override this, be sure to call you superclass's implementation + * of this method as the last thing you do in your implementation. + */ + virtual void AboutToDetachFromServer(); + + /** + * Called when a message is sent to us by an AbstractReflectSession object. + * Default implementation is a no-op. + * @param from The session who is sending the Message to us. + * @param msg A reference to the message that was sent. + * @param userData Additional data whose semantics are determined by the sending subclass. + * (For StorageReflectSessions, this value, if non-NULL, is a pointer to the + * DataNode in this Session's node subtree that was matched by the paths in (msg)) + */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * userData); + + /** + * Called when a message is sent to us by a ReflectSessionFactory object. + * Default implementation is a no-op. + * @param from The session who is sending the Message to us. + * @param msg A reference to the message that was sent. + * @param userData Additional data whose semantics are determined by the sending subclass. + */ + virtual void MessageReceivedFromFactory(ReflectSessionFactory & from, const MessageRef & msg, void * userData); + + /** Returns true if we are attached to the ReflectServer object, false if we are not. */ + bool IsAttachedToServer() const {return (_owner != NULL);} + + /** Returns true if we are attached FULLY to the ReflectServer object, false if we are not. + * The difference between this method and IsAttachedToServer() is that this method only returns + * true after AttachedToServer() has completed successully, and before AboutToDetachFromServer() + * has been called. Compare that to IsAttachedToServer()'s which returns true during the + * AttachedToServer and AboutToDetachFromServer() calls themselves, also. + */ + bool IsFullyAttachedToServer() const {return _fullyAttached;} + + /** Sets the fully-attached-to-server flag for this session. Typically only the ReflectServer class should call this. */ + void SetFullyAttachedToServer(bool fullyAttached) {_fullyAttached = fullyAttached;} + + /** Returns the ReflectServer we are currently attached to, or NULL if we aren't currently attached to a ReflectServer. */ + ReflectServer * GetOwner() const {return _owner;} + + /** Sets the ReflectServer we are currently attached to. Don't call this if you don't know what you are doing. */ + void SetOwner(ReflectServer * s) {_owner = s;} + +protected: + /** Returns the value GetRunTime64() was at when this server's ServerProcessLoop() began. */ + uint64 GetServerStartTime() const; + + /** Returns a number that is unique (hopefully) to our ReflectServer object in this process */ + uint64 GetServerSessionID() const; + + /** Returns the number of bytes that are currently available to be allocated */ + uint64 GetNumAvailableBytes() const; + + /** Returns the maximum number of bytes that may be allocated at any given time */ + uint64 GetMaxNumBytes() const; + + /** Returns the number of bytes that are currently allocated */ + uint64 GetNumUsedBytes() const; + + /** Passes through to ReflectServer::PutAcceptFactory() */ + status_t PutAcceptFactory(uint16 port, const ReflectSessionFactoryRef & factoryRef, const ip_address & interfaceIP = invalidIP, uint16 * optRetPort = NULL); + + /** Passes through to ReflectServer::RemoveAcceptFactory() */ + status_t RemoveAcceptFactory(uint16 port, const ip_address & interfaceIP = invalidIP); + + /** Tells the whole server process to quit ASAP. */ + void EndServer(); + + /** + * Returns a reference to a Message that is shared by all objects in + * a single ReflectServer. This message can be used for whatever + * purpose the ServerComponents care to; it is not used by the + * server itself. (Note that StorageReflectSessions add data to + * this Message and expect it to remain there, so be careful not + * to remove or overwrite it if you are using StorageReflectSessions) + */ + Message & GetCentralState() const; + + /** + * Adds the given AbstractReflectSession to the server's session list. + * If (socket) is less than zero, no TCP connection will be used, + * and the session will be a pure server-side entity. + * @param session A reference to the new session to add to the server. + * @param socket the socket descriptor associated with the new session, or a NULL reference. + * If the socket descriptor argument is a NULL reference, the session's + * CreateDefaultSocket() method will be called to supply the ConstSocketRef. + * If that also returns a NULL reference, then the client will run without + * a connection to anything. + * @return B_NO_ERROR if the session was successfully added, or B_ERROR on error. + */ + status_t AddNewSession(const AbstractReflectSessionRef & session, const ConstSocketRef & socket = ConstSocketRef()); + + /** + * Like AddNewSession(), only creates a session that connects asynchronously to + * the given IP address. AttachedToServer() will be called immediately on the + * session, and then when the connection is complete, AsyncConnectCompleted() will + * be called. Other than that, however, (session) will behave like any other session, + * except any I/O messages for the client won't be transferred until the connection + * completes. + * @param session A reference to the new session to add to the server. + * @param targetIPAddress IP address to connect to + * @param port port to connect to at that address + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (session). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR if the session was successfully added, or B_ERROR on error + * (out-of-memory or the connect attempt failed immediately). + */ + status_t AddNewConnectSession(const AbstractReflectSessionRef & session, const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** + * Like AddNewConnectSession(), except that the added session will not initiate + * a TCP connection to the specified address immediately. Instead, it will just + * hang out and do nothing until you call Reconnect() on it. Only then will it + * create the TCP connection to the address specified here. + * @param ref New session to add to the server. + * @param targetIPAddress IP address to connect to + * @param port Port to connect to at that IP address. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (ref). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR if the session was successfully added, or B_ERROR on error + * (out-of-memory, or the connect attempt failed immediately) + */ + status_t AddNewDormantConnectSession(const AbstractReflectSessionRef & ref, const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** Returns our server's table of attached sessions. */ + const Hashtable & GetSessions() const; + + /** + * Looks up a session connected to our ReflectServer via its session ID string. + * @param id The ID of the session you are looking for. + * @return A reference to the session with the given session ID, or a NULL reference on failure. + */ + AbstractReflectSessionRef GetSession(uint32 id) const; + + /** + * Looks up a session connected to our ReflectServer via its session ID string. + * @param id The ID string of the session you are looking for. + * @return A reference to the session with the given session ID, or a NULL reference on failure. + */ + AbstractReflectSessionRef GetSession(const String & id) const; + + /** Convenience method: Returns a pointer to the first session of the specified type. Returns NULL if no session of the specified type is found. + * @note this method iterates over the session list, so it's not as efficient as one might hope. + */ + template SessionType * FindFirstSessionOfType() const + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + SessionType * ret = dynamic_cast(iter.GetValue()()); + if (ret) return ret; + } + return NULL; + }; + + /** Convenience method: Populates the specified table with sessions of the specified session type. + * @param results The list of matching sessions is returned here. + * @param maxSessionsToReturn No more than this many sessions will be placed into the table. Defaults to MUSCLE_NO_LIMIT. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory) + */ + template status_t FindSessionsOfType(Queue & results, uint32 maxSessionsToReturn = MUSCLE_NO_LIMIT) const + { + if (maxSessionsToReturn > 0) + { + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + SessionType * ret = dynamic_cast(iter.GetValue()()); + if (ret) + { + if (results.AddTail(iter.GetValue()) != B_NO_ERROR) return B_ERROR; + if (--maxSessionsToReturn == 0) break; + } + } + } + return B_NO_ERROR; + } + + /** Returns the table of session factories currently attached to the server. */ + const Hashtable & GetFactories() const; + + /** Given a port number, returns a reference to the factory of that port, or a NULL reference if no +such factory exists. */ + ReflectSessionFactoryRef GetFactory(uint16) const; + +private: + ReflectServer * _owner; + bool _fullyAttached; +}; + +}; // end namespace muscle + +#endif diff --git a/reflector/SignalHandlerSession.cpp b/reflector/SignalHandlerSession.cpp new file mode 100644 index 00000000..3b5a61af --- /dev/null +++ b/reflector/SignalHandlerSession.cpp @@ -0,0 +1,77 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "reflector/SignalHandlerSession.h" +#include "system/SignalMultiplexer.h" +#include "util/NetworkUtilityFunctions.h" + +namespace muscle { + +ConstSocketRef SignalHandlerSession :: CreateDefaultSocket() +{ + ConstSocketRef sock; + (void) CreateConnectedSocketPair(sock, _handlerSocket); + return sock; +} + +int32 SignalHandlerSession :: DoInput(AbstractGatewayMessageReceiver &, uint32) +{ + uint32 byteCount = 0; + while(1) + { + char buf[64]; + int32 bytesReceived = ReceiveData(GetSessionReadSelectSocket(), buf, sizeof(buf), false); + if (bytesReceived > 0) + { + byteCount += bytesReceived; + for (int32 i=0; i +{ +public: + /** Default constructor. */ + SignalHandlerSession() {/* empty */} + virtual ~SignalHandlerSession() {/* empty */} + + virtual ConstSocketRef CreateDefaultSocket(); + virtual int32 DoInput(AbstractGatewayMessageReceiver &, uint32); + virtual void MessageReceivedFromGateway(const MessageRef &, void *) {/* empty */} + virtual status_t AttachedToServer(); + virtual void AboutToDetachFromServer(); + virtual const char * GetTypeName() const {return "SignalHandler";} + virtual void SignalHandlerFunc(int whichSignal); + +protected: + /** This method is called in the main thread whenever a signal is received. + * @param whichSignal the number of the signal received, as provided by the OS. + * On POSIX OS's this might be SIGINT or SIGHUP; under Windows + * it would be something like CTRL_CLOSE_EVENT or CTRL_LOGOFF_EVENT. + * Default behavior is to always just call EndServer() so that the server process + * will exit cleanly as soon as possible. + */ + virtual void SignalReceived(int whichSignal); + +private: + ConstSocketRef _handlerSocket; +}; + +/** Returns true iff any SignalHandlerSession ever caught a signal since this process was started. */ +bool WasSignalCaught(); + +/** Sets whether or not the ReflectServer in the main thread should try to handle signals. + * Default state is false, unless MUSCLE_CATCH_SIGNALS_BY_DEFAULT was defined at compile time. + * Note that this flag is read at the beginning of ReflectServer::ServerProcessLoop(), so + * you must set it before then for it to have any effect. + */ +void SetMainReflectServerCatchSignals(bool enable); + +/** Returns true iff the main-ReflectServer-handle-signals flags is set to true. */ +bool GetMainReflectServerCatchSignals(); + +}; // end namespace muscle diff --git a/reflector/StorageReflectConstants.h b/reflector/StorageReflectConstants.h new file mode 100644 index 00000000..0dabb6a6 --- /dev/null +++ b/reflector/StorageReflectConstants.h @@ -0,0 +1,370 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleStorageReflectConstants_h +#define MuscleStorageReflectConstants_h + +#include "support/MuscleSupport.h" + +#ifdef __cplusplus +namespace muscle { +#endif + +/** 'What' codes understood to have special meaning by the StorageReflectSession class */ +enum +{ + BEGIN_PR_COMMANDS = 558916400, // '!Pc0' + PR_COMMAND_SETPARAMETERS, // Adds/replaces the given fields in the parameters table + PR_COMMAND_GETPARAMETERS, // Returns the current parameter set to the client + PR_COMMAND_REMOVEPARAMETERS, // deletes the parameters specified in PR_NAME_KEYS + PR_COMMAND_SETDATA, // Adds/replaces the given message in the data table + PR_COMMAND_GETDATA, // Retrieves the given message(s) in the data table + PR_COMMAND_REMOVEDATA, // Removes the gives message(s) from the data table + PR_COMMAND_JETTISONRESULTS, // Removes data from outgoing result messages + PR_COMMAND_INSERTORDEREDDATA, // Insert nodes underneath a node, as an ordered list + PR_COMMAND_PING, // Echo this message back to the sending client + PR_COMMAND_KICK, // Kick matching clients off the server (Requires privilege) + PR_COMMAND_ADDBANS, // Add ban patterns to the server's ban list (Requires privilege) + PR_COMMAND_REMOVEBANS, // Remove ban patterns from the server's ban list (Requires privilege) + PR_COMMAND_BATCH, // Messages in PR_NAME_KEYS are executed in order, as if they came separately + PR_COMMAND_NOOP, // Does nothing at all + PR_COMMAND_REORDERDATA, // Moves one or more entries in a node index to a different spot in the index + PR_COMMAND_ADDREQUIRES, // Add require patterns to the server's require list (Requires ban privilege) + PR_COMMAND_REMOVEREQUIRES, // Remove require patterns from the server's require list (Requires ban privilege) + PR_COMMAND_SETDATATREES, // Sets an entire subtree of data from a single Message (Not implemented!) + PR_COMMAND_GETDATATREES, // Returns an entire subtree of data as a single Message + PR_COMMAND_JETTISONDATATREES, // Removes matching RESULT_DATATREES Messages from the outgoing queue + PR_COMMAND_RESERVED14, // reserved for future expansion + PR_COMMAND_RESERVED15, // reserved for future expansion + PR_COMMAND_RESERVED16, // reserved for future expansion + PR_COMMAND_RESERVED17, // reserved for future expansion + PR_COMMAND_RESERVED18, // reserved for future expansion + PR_COMMAND_RESERVED19, // reserved for future expansion + PR_COMMAND_RESERVED20, // reserved for future expansion + PR_COMMAND_RESERVED21, // reserved for future expansion + PR_COMMAND_RESERVED22, // reserved for future expansion + PR_COMMAND_RESERVED23, // reserved for future expansion + PR_COMMAND_RESERVED24, // reserved for future expansion + PR_COMMAND_RESERVED25, // reserved for future expansion + END_PR_COMMANDS +}; + +/** 'What' codes that may be generated by the StorageReflectSession and sent back to the client */ +enum +{ + BEGIN_PR_RESULTS = 558920240, // '!Pr0' + PR_RESULT_PARAMETERS, // Sent to client in response to PR_COMMAND_GETPARAMETERS + PR_RESULT_DATAITEMS, // Sent to client in response to PR_COMMAND_GETDATA, or subscriptions + PR_RESULT_ERRORUNIMPLEMENTED, // Sent to client to tell him that we don't know how to process his request message + PR_RESULT_INDEXUPDATED, // Notification that an entry has been inserted into an ordered index + PR_RESULT_PONG, // Response from a PR_COMMAND_PING message + PR_RESULT_ERRORACCESSDENIED, // Your client isn't allowed to do something it tried to do + PR_RESULT_DATATREES, // Reply to a PR_COMMAND_GETDATATREES message + PR_RESULT_RESERVED5, // reserved for future expansion + PR_RESULT_RESERVED6, // reserved for future expansion + PR_RESULT_RESERVED7, // reserved for future expansion + PR_RESULT_RESERVED8, // reserved for future expansion + PR_RESULT_RESERVED9, // reserved for future expansion + PR_RESULT_RESERVED10, // reserved for future expansion + PR_RESULT_RESERVED11, // reserved for future expansion + PR_RESULT_RESERVED12, // reserved for future expansion + PR_RESULT_RESERVED13, // reserved for future expansion + PR_RESULT_RESERVED14, // reserved for future expansion + PR_RESULT_RESERVED15, // reserved for future expansion + PR_RESULT_RESERVED16, // reserved for future expansion + PR_RESULT_RESERVED17, // reserved for future expansion + PR_RESULT_RESERVED18, // reserved for future expansion + PR_RESULT_RESERVED19, // reserved for future expansion + PR_RESULT_RESERVED20, // reserved for future expansion + PR_RESULT_RESERVED21, // reserved for future expansion + PR_RESULT_RESERVED22, // reserved for future expansion + PR_RESULT_RESERVED23, // reserved for future expansion + PR_RESULT_RESERVED24, // reserved for future expansion + PR_RESULT_RESERVED25, // reserved for future expansion + END_PR_RESULTS +}; + +/** Privilege codes (if a client has these bits, he can do the associated actions) */ +enum +{ + PR_PRIVILEGE_KICK = 0, + PR_PRIVILEGE_ADDBANS, + PR_PRIVILEGE_REMOVEBANS, + PR_NUM_PRIVILEGES +}; + +/** Op-codes found as part of the strings sent in a PR_RESULT_INDEXUPDATED message */ +enum +{ + INDEX_OP_ENTRYINSERTED = 'i', // Entry was inserted at the given slot index, with the given ID + INDEX_OP_ENTRYREMOVED = 'r', // Entry was removed from the given slot index, had the given ID + INDEX_OP_CLEARED = 'c' // Index was cleared +}; + +// Recognized message field names +#define PR_NAME_KEYS "!SnKy" // STRING: one or more key-strings +#define PR_NAME_FILTERS "!SnFl" // Message: One or more archived QueryFilter objects +#define PR_NAME_REMOVED_DATAITEMS "!SnRd" // STRING: one or more key-strings of removed data items +#define PR_NAME_SUBSCRIBE_QUIETLY "!SnQs" // Any type: if present in a PR_COMMAND_SETPARAMETERS message, disables inital-value-send from new subscriptions +#define PR_NAME_SET_QUIETLY "!SnQ2" // Any type: if present in a PR_COMMAND_SETDATA message, then the message won't cause subscribers to be notified. +#define PR_NAME_REMOVE_QUIETLY "!SnQ3" // Any type: if present in a PR_COMMAND_REMOVEDATA message, then the message won't cause subscribers to be notified. +#define PR_NAME_REFLECT_TO_SELF "!Self" // If set as parameter, include ourself in wildcard matches +#define PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS "!G2N" // If set as parameter, session broadcasts unrecognized Messages to neighbors (set by default) +#define PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY "!N2G" // If set as parameter, session accepts unrecognized Messages from neighbors and sends them to gateway (set by default) +#define PR_NAME_DISABLE_SUBSCRIPTIONS "!Dsub" // If set as a parameter, disable all subscription updates. +#define PR_NAME_MAX_UPDATE_MESSAGE_ITEMS "!MxUp" // Int32 parameter; sets max # of items per PR_RESULT_DATAITEMS message +#define PR_NAME_SESSION_ROOT "!Root" // String returned in parameter set; contains this sessions /host/sessionID +#define PR_NAME_REJECTED_MESSAGE "!Rjct" // Message: In PR_RESULT_ERROR_* messages, returns the client's message that failed to execute. +#define PR_NAME_PRIVILEGE_BITS "!Priv" // int32 bit-chord of PR_PRIVILEGE_* bits. +#define PR_NAME_SERVER_MEM_AVAILABLE "!Mav" // int64 indicating how many more bytes are available for MUSCLE server to use +#define PR_NAME_SERVER_MEM_USED "!Mus" // int64 indicating how many bytes the MUSCLE server currently has allocated +#define PR_NAME_SERVER_MEM_MAX "!Mmx" // uint64 indicating how the maximum number of bytes the MUSCLE server may have allocated at once. +#define PR_NAME_SERVER_VERSION "!Msv" // String indicating version of MUSCLE that the server was compiled from +#define PR_NAME_SERVER_UPTIME "!Mup" // uint64 indicating how many microseconds the server has been running for +#define PR_NAME_SERVER_CURRENTTIMEUTC "!Mct" // uint64 indicating the server's current wall-clock (microseconds since 1970), in UTC format +#define PR_NAME_SERVER_CURRENTTIMELOCAL "!Mcl" // uint64 indicating the server's current wall-clock (microseconds since 1970), in the server's local timezone format +#define PR_NAME_SERVER_RUNTIME "!Mrt" // uint64 indicating the server's current run-time clock (microseconds) +#define PR_NAME_SERVER_SESSION_ID "!Ssi" // uint64 that is unique to this particular instance of the server in this particular process +#define PR_NAME_MAX_NODES_PER_SESSION "!Mns" // uint32 indicating the maximum number of nodes uploadable by a session +#define PR_NAME_SESSION "session" // this field will be replaced with the sender's session number for any client-to-client message (named "session" for BeShare backwards compatibility) +#define PR_NAME_SUBSCRIBE_PREFIX "SUBSCRIBE:" // Prefix for parameters that indicate a subscription request +#define PR_NAME_TREE_REQUEST_ID "!TRid" // Identifier field for associating PR_RESULT_DATATREES replies with PR_COMMAND_GETDATATREE commands +#define PR_NAME_REPLY_ENCODING "!Enc" // Parameter name holding int32 of MUSCLE_MESSAGE_ENCODING_* used to send to client +#define PR_NAME_MAXDEPTH "!MDep" // If present as an int32 in PR_COMMAND_GETDATATREES, returned trees will be clipped to this maximum depth. (0==roots only) + +// Names in the output message generated by StorageReflectSession::SaveNodeTreeToMessage() +#define PR_NAME_NODEDATA "data" // this submessage is the payload of the current node +#define PR_NAME_NODECHILDREN "kids" // this submessage represents the children of the current node (recursive) +#define PR_NAME_NODEINDEX "index" // this submessage represents index/ordering of the current node + +// This is a specialization of AbstractReflectSession that adds several +// useful capabilities to the Reflect Server. Abilities include: +// - Messages can specify (via wildcard path matching) which other +// clients they should be reflected to. If the message doesn't specify +// a path, then the session's default path can be used. +// - Clients can upload and store data on the server (in the server's RAM only). +// This data will remain accessible to all sessions until the client disconnects. +// - Clients can "subscribe" to server state information and be automatically +// notified when the information has changed. +// +// CLIENT-TO-SERVER MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_COMMAND_SETPARAMETERS: +// All fields of the message are placed into the session's parameter set. +// Any fields in the previous parameters set with matching names are replaced. +// Currently parsed parameter names are as follows: +// +// "SUBSCRIBE:" : Any parameter name that begins with the prefix SUBSCRIBE: +// is taken to represent a request to monitor the contents of +// all nodes whose pathnames match the path that follows. So, +// for example, the parameter name +// +// SUBSCRIBE:/*/*/Joe +// +// Indicates a request to watch all nodes with paths that match +// the pattern matching expression /*/*/Joe. +// As of muscle 2.40, the value of this parameter may be a Message +// containing an archived QueryFilter object, in which case the +// subscription will use the archived QueryFilter to test matching +// nodes in addition to the path matching. If the parameter value +// is not a Message object, no QueryFilter will be used. +// Thus, these parameters may be of any type. Once a SUBSCRIBE +// parameter has been added, any data nodes that match the specified +// path will be returned immediately to the client in a PR_RESULT_DATAITEMS +// message. Furthermore, any time these nodes are modified or deleted, +// or any time a new node is added that matches the path, another +// PR_RESULT_DATAITEMS message will be sent to notify the client of +// the change. +// +// PR_NAME_KEYS : If set, any non-"special" messages without a +// PR_NAME_KEYS field will be reflected to clients +// who match at least one of the set of key-paths +// listed here. (Should be one or more string values) +// +// PR_NAME_FILTERS : If the PR_NAME_KEYS parameter is set, this parameter +// may be set also, and the reflecting of non-special +// messages will be filtered using the QueryFilter specified in this parameter. +// +// PR_NAME_REFLECT_TO_SELF : If set, wildcard matches can match the current session. +// Otherwise, wildcard matches with the current session will +// be suppressed (on the grounds that your client already knows +// the value of anything that it uploaded, and doesn't need to +// be reminded of it). This field may be of any type, only +// its existence/non-existence is relevant. This parameter is NOT set by default. +// +// PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS : If set, then any unrecognized Message received by the current +// session will be broadcast to all other neighboring sessions. +// This parameter is set by default. +// +// PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY : If set, then any unrecognized Message received from a neighboring +// session will be sent out to the current session's gateay (and thus +// to the client). This parameter is set by default. +// +// PR_NAME_REPLY_ENCODING : If set, this int32 specifies the MUSCLE_MESSAGE_ENCODING_* +// value to be used by the session when sending data back to the client. +// If unset, the default value (MUSCLE_MESSAGE_ENCODING_DEFAULT) is used. +// Setting this parameter is useful if you want the server to compress +// the data it sends back to your client. +// +// +// if 'what' is PR_COMMAND_GETPARAMETERS: +// Causes a PR_RESULT_PARAMETERS message to be returned to the client. The returned message +// contains the entire current parameter set. +// +// if 'what' is PR_COMMAND_REMOVEPARAMETERS: +// The session looks for PR_NAME_KEYS string entrys. For each string found +// under this entry, any matching field in the parameters message are deleted. +// Wildcards are permitted in these strings. (So e.g. "*" would remove ALL parameters) +// +// if 'what' is PR_COMMAND_SETDATA: +// Scans the message for all fields of type message. Each message field +// should contain only one message. The field's name is parsed as a local +// key-path of the data item (e.g. "myData", or "imageInfo/colors/red"). +// Each contained message will be stored in the local session's data tree under +// that key path. (Note: fields that start with a '/' are not allowed, and +// will be ignored!) +// +// if 'what' is PR_COMMAND_REMOVEDATA: +// Removes all data nodes that match the path(s) in the PR_NAME_KEYS string field. +// Paths should be specified relative to this session's root node (i.e. they should +// not start with a slash) QueryFilter objects may be provided in the PR_NAME_FILTERS +// Message field. Any PR_NAME_KEYS string without a matching PR_NAME_FILTERS Message +// will remove data based solely on path matching, without any QueryFilter object. +// +// if 'what' is PR_COMMAND_GETDATA: +// The session looks for one or more strings in the PR_NAME_KEYS field. Each +// string represents a key-path indicating which information the client is +// interested in retrieving. If there is no leading slash, "/*/*/" will be +// implicitely prepended. Here are some valid example key-paths: +// /*/*/color (gets "color" from all hostnames, all session IDs) +// /joe.blow.com/*/shape (gets "shape" from all sessions connected from joe.blow.com) +// /joe.blow.com/19435935093/sound (gets "sound" from a single session) +// /*/*/vehicleTypes/* (gets all vehicle types from all clients) +// j* (equivalent to "/*/*/j*") +// shape/* (equivalent to "/*/*/shape/*") +// The union of all the sets of matching data nodes specified by these paths will be +// added to one or more PR_RESULT_DATAITEMS message which is then passed back to the client. +// Each matching message is added with its full path as a field name. +// A PR_NAME_FILTERS field may be added to further limit the nodes matched +// by the PR_NAME_KEYS field. +// +// if 'what' is PR_COMMAND_INSERTORDEREDDATA: +// The session looks for one or more messages in the PR_NAME_KEYS field. Each +// string represents a wildpath, rooted at this session's node (read: no leading +// slash should be present) that specifies zero or more data nodes to insert ordered/ +// indexed children under. Each node in the union of these node sets will have new +// ordered/indexed child nodes created underneath it. The names of these new child +// nodes will be chosen algorithmically by the server. There will be one child node +// created for each sub-message in this message. Sub-messages may be added under any +// field name; if the field name happens to be the name of a currently indexed child, +// the new message node will be be inserted *before* the specified child in the index. +// Otherwise, it will be appended to the end of the index. Clients who have subscribed +// to the specified nodes will see the updates to the index; clients who have subscribed +// to the children will get updates of the actual data as well. +// A PR_NAME_FILTERS field may be added to further limit the nodes matched +// by the PR_NAME_KEYS field. +// +// if 'what' is PR_COMMAND_REORDERDATA: +// The session looks for one or more strings in the message. Each string field's name +// will be used as a wild path, matching zero or more nodes in the node tree; each +// string field's value may be the name of a currently existing indexed child under the +// matching node; if so, the node(s) specified by the wild path will be reordered +// in the index to appear just before the node specified in the string value. +// If the string field's value is not the name of a child of the matching node, then the +// nodes specified in the string field's name will be moved to the end of the index. +// A PR_NAME_FILTERS field may be added to further limit the nodes matched +// by the PR_NAME_KEYS field. +// +// if 'what' is PR_COMMAND_PING: +// The session will change the message's 'what' code to PR_RESULT_PONG, and send +// it right back to the client. In this way, the client can find out (a) that +// the server is still alive, (b) how long it takes the server to respond, and +// (c) that any previously sent operations have finished. +// +// if 'what' is PR_COMMAND_KICK: +// The server will look for one or more strings in the PR_NAME_KEYS field. It will +// do a search of the database for any nodes matching one or more of the node paths +// specified by these strings, and will kick any session with matching nodes off +// of the server. Of course, this will only be done if the client who sent the +// PR_COMMAND_KICK field has PR_PRIVILEGE_KICK access. +// +// if 'what' is PR_COMMAND_ADDBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be added to the server's "banned IP list", and +// subsequent connection attempts from IP addresses matching any of these ban strings +// will be denied. Of course, this will only be done if the client who sent the +// PR_COMMAND_ADDBANS field has PR_PRIVILEGE_ADDBANS access. +// +// if 'what' is PR_COMMAND_REMOVEBANS: +// The server will look for one or more strings in the PR_NAME_KEYS field. Any +// strings that are found will be used a pattern-matching strings against the +// current set of "ban" patterns. Any "ban" patterns that are matched by any +// of the PR_NAME_KEYS strings will be removed from the "banned IP patterns" set, +// so that IP addresses who matched those patterns will be able to connect to +// the server again. Of course, this will only be done if the client who sent the +// PR_COMMAND_REMOVEBANS field has PR_PRIVILEGE_REMOVEBANS access. +// +// if 'what' is PR_COMMAND_RESERVED_*: +// The server will change the 'what' code of your message to PR_RESULT_UNIMPLEMENTED, +// and send it back to your client. +// +// if 'what' is PR_RESULT_*: +// The message will be silently dropped. You are not allowed to send PR_RESULT_(*) +// messages to the server, and should be very ashamed of yourself for even thinking +// about it. +// +// All other 'what' codes +// Messages with other 'what' codes are simply reflected to other clients verbatim. +// If a PR_NAME_KEYS string field is found in the message, then it will be parsed as a +// set of key-paths, and only other clients who can match at least one of these key-paths +// will receive the message. If no PR_NAME_KEYS field is found, then the parameter +// named PR_NAME_KEYS will be used as a default value. If that parameter isn't +// found, then the message will be reflected to all clients (except this one). +// +// SERVER-TO-CLIENT MESSAGE FORMAT SPECIFICATION: +// +// if 'what' is PR_RESULT_PARAMETERS: +// The message contains the complete parameter set that is currently associated with +// this client on the server. Parameters may have any field name, and be of any type. +// Certain parameter names are recognized by the StorageReflectSession as having special +// meaning; see the documentation on PR_COMMAND_SETPARAMETERS for information about these. +// +// if 'what' is PR_RESULT_DATAITEMS: +// The message contains information about data that is stored on the server. All stored +// data is stored in the form of Messages. Thus, all data in this message will be +// in the form of a message field, with the field's name being the fully qualified path +// of the node it represents (e.g. "/my.computer.com/5/MyNodeName") and the value being +// the stored data itself. Occasionally it is necessary to inform the client that a data +// node has been deleted; this is done by adding the deceased node's path name as a string +// to the PR_NAME_REMOVED_DATAITEM field. If multiple nodes were removed, there may be +// more than one string present in the PR_NAME_REMOVED_DATAITEM field. +// +// if 'what' is PR_RESULT_INDEXUPDATED: +// The message contains information about index entries that were added (via PR_COMMAND_INSERTORDERREDDATA) +// to a node that the client is subscribed to. Each entry's field name is the fully qualified +// path of a subscribed node, and the value(s) are strings of this format: "%c%lu:%s", %c is +// a single character that is one of the INDEX_OP_* values, the %lu is an index the item was added to +// or removed from, and %s is the key string for the child in question. +// Note that there may be more than one value per field! +// +// if 'what' is PR_RESULT_ERRORUNIMPLEMENTED: +// Your message is being returned to you because it tried to use functionality that +// hasn't been implemented on the server side. This usually happens if you are trying +// to use a new MUSCLE feature with an old MUSCLE server. +// +// if 'what' is PR_RESULT_PONG: +// Your PR_COMMAND_PING message has been returned to you as a PR_RESULT_PONG message. +// +// if 'what' is PR_RESULT_ERRORACCESSDENIED: +// You tried to do something that you don't have permission to do (such as kick, ban, +// or unban another user). +// +// if 'what' is anything else: +// This message was reflected to your client by a neighboring client session. The content +// of the message is not specified by the StorageReflectSession; it just passes any message +// on verbatim. + +#ifdef __cplusplus +}; // end namespace muscle +#endif + +#endif diff --git a/reflector/StorageReflectSession.cpp b/reflector/StorageReflectSession.cpp new file mode 100644 index 00000000..f72d4180 --- /dev/null +++ b/reflector/StorageReflectSession.cpp @@ -0,0 +1,1873 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "reflector/ReflectServer.h" +#include "reflector/StorageReflectSession.h" +#include "iogateway/MessageIOGateway.h" + +namespace muscle { + +#define DEFAULT_PATH_PREFIX "*/*" // when we get a path name without a leading '/', prepend this +#define DEFAULT_MAX_SUBSCRIPTION_MESSAGE_SIZE 50 // no more than 50 items/update message, please + +// field under which we file our shared data in the central-state message +static const String SRS_SHARED_DATA = "srs_shared"; + +StorageReflectSessionFactory :: StorageReflectSessionFactory() : _maxIncomingMessageSize(MUSCLE_NO_LIMIT) +{ + // empty +} + +AbstractReflectSessionRef StorageReflectSessionFactory :: CreateSession(const String &, const IPAddressAndPort &) +{ + TCHECKPOINT; + + AbstractReflectSession * srs = newnothrow StorageReflectSession; + AbstractReflectSessionRef ret(srs); + + if ((srs)&&(SetMaxIncomingMessageSizeFor(srs) == B_NO_ERROR)) return ret; + else + { + WARN_OUT_OF_MEMORY; + return AbstractReflectSessionRef(); + } +} + +status_t StorageReflectSessionFactory :: SetMaxIncomingMessageSizeFor(AbstractReflectSession * session) const +{ + if (_maxIncomingMessageSize != MUSCLE_NO_LIMIT) + { + if (session->GetGateway()() == NULL) session->SetGateway(session->CreateGateway()); + MessageIOGateway * gw = dynamic_cast(session->GetGateway()()); + if (gw) gw->SetMaxIncomingMessageSize(_maxIncomingMessageSize); + else return B_ERROR; + } + return B_NO_ERROR; +} + +StorageReflectSession :: +StorageReflectSession() : + _parameters(PR_RESULT_PARAMETERS), + _sharedData(NULL), + _subscriptionsEnabled(true), + _maxSubscriptionMessageItems(DEFAULT_MAX_SUBSCRIPTION_MESSAGE_SIZE), + _indexingPresent(false), + _currentNodeCount(0), + _maxNodeCount(MUSCLE_NO_LIMIT) +{ + // empty +} + +StorageReflectSession :: +~StorageReflectSession() +{ + // empty +} + +StorageReflectSession::StorageReflectSessionSharedData * +StorageReflectSession :: +InitSharedData() +{ + TCHECKPOINT; + + Message & state = GetCentralState(); + + void * sp = NULL; (void) state.FindPointer(SRS_SHARED_DATA, sp); + StorageReflectSession::StorageReflectSessionSharedData * sd = (StorageReflectSession::StorageReflectSessionSharedData *) sp; + if (sd) return sd; + + // oops, there's no shared data object! We must be the first session. + // So we'll create the root node and the shared data object, and + // add it to the central-state Message ourself. + DataNodeRef globalRoot = GetNewDataNode("", CastAwayConstFromRef(GetEmptyMessageRef())); + if (globalRoot()) + { + sd = newnothrow StorageReflectSessionSharedData(globalRoot); + if (sd) + { + if (state.ReplacePointer(true, SRS_SHARED_DATA, sd) == B_NO_ERROR) return sd; + delete sd; + } + else WARN_OUT_OF_MEMORY; + } + return NULL; +} + +status_t +StorageReflectSession :: +AttachedToServer() +{ + TCHECKPOINT; + + if (DumbReflectSession::AttachedToServer() != B_NO_ERROR) return B_ERROR; + + _sharedData = InitSharedData(); + if (_sharedData == NULL) return B_ERROR; + + Message & state = GetCentralState(); + const String & hostname = GetHostName(); + const String & sessionid = GetSessionIDString(); + + // Is there already a node for our hostname? + DataNodeRef hostDir; + if (GetGlobalRoot().GetChild(hostname, hostDir) != B_NO_ERROR) + { + // nope.... we'll add one then + hostDir = GetNewDataNode(hostname, CastAwayConstFromRef(GetEmptyMessageRef())); + if ((hostDir() == NULL)||(GetGlobalRoot().PutChild(hostDir, this, this) != B_NO_ERROR)) {WARN_OUT_OF_MEMORY; Cleanup(); return B_ERROR;} + } + + // Create a new node for our session (we assume no such + // node already exists, as session id's are supposed to be unique) + if (hostDir() == NULL) {Cleanup(); return B_ERROR;} + if (hostDir()->HasChild(sessionid())) LogTime(MUSCLE_LOG_WARNING, "WARNING: Non-unique session id [%s] being overwritten!\n", sessionid()); + + SetSessionRootPath(hostname.Prepend("/") + "/" + sessionid); + + DataNodeRef sessionNode = GetNewDataNode(sessionid, CastAwayConstFromRef(GetEmptyMessageRef())); + if (sessionNode()) + { +#ifdef MUSCLE_AVOID_IPV6 + const String & matchHostname = hostname; +#else + String matchHostname = hostname; + { + // match against IPv4-style address-strings for IPv4 addresses, per Lior + ip_address ip = Inet_AtoN(hostname()); + if ((IsValidAddress(ip))&&(IsIPv4Address(ip))) matchHostname = Inet_NtoA(ip, true); + } +#endif + + // See if we get any special privileges + int32 privBits = 0; + for (int p=0; p<=PR_NUM_PRIVILEGES; p++) + { + char temp[32]; + sprintf(temp, "priv%i", p); + const String * privPattern; + for (int q=0; (state.FindString(temp, q, &privPattern) == B_NO_ERROR); q++) + { + if (StringMatcher(*privPattern).Match(matchHostname())) + { + if (p == PR_NUM_PRIVILEGES) privBits = ~0; // all privileges granted! + else privBits |= (1L<PutChild(_sessionDir, this, this) != B_NO_ERROR) {WARN_OUT_OF_MEMORY; Cleanup(); return B_ERROR;} + + // do subscription notifications here + PushSubscriptionMessages(); + + // Get our node-creation limit. For now, this is the same for all sessions. + // (someday maybe I'll work out a way to give different limits to different sessions) + uint32 nodeLimit; + if (state.FindInt32(PR_NAME_MAX_NODES_PER_SESSION, nodeLimit) == B_NO_ERROR) _maxNodeCount = nodeLimit; + + return B_NO_ERROR; + } + + WARN_OUT_OF_MEMORY; + Cleanup(); + + TCHECKPOINT; + return B_ERROR; +} + +void +StorageReflectSession :: +AboutToDetachFromServer() +{ + Cleanup(); + DumbReflectSession::AboutToDetachFromServer(); +} + +void +StorageReflectSession :: +Cleanup() +{ + TCHECKPOINT; + + if (_sharedData) + { + DataNodeRef hostNodeRef; + if (GetGlobalRoot().GetChild(GetHostName(), hostNodeRef) == B_NO_ERROR) + { + DataNode * hostNode = hostNodeRef(); + if (hostNode) + { + // make sure our session node is gone + hostNode->RemoveChild(GetSessionIDString(), this, true, NULL); + + // If our host node is now empty, it goes too + if (hostNode->HasChildren() == false) GetGlobalRoot().RemoveChild(hostNode->GetNodeName(), this, true, NULL); + } + + PushSubscriptionMessages(); + } + + // If the global root is now empty, it goes too + if (GetGlobalRoot().HasChildren() == false) + { + GetCentralState().RemoveName(SRS_SHARED_DATA); + _sharedData->_root.Reset(); // do this first! + delete _sharedData; + } + else + { + // Remove all of our subscription-marks from neighbor's nodes + (void) _subscriptions.DoTraversal((PathMatchCallback)DoSubscribeRefCallbackFunc, this, GetGlobalRoot(), false, (void *)(-LONG_MAX)); + } + _sharedData = NULL; + } + _nextSubscriptionMessage.Reset(); + _nextIndexSubscriptionMessage.Reset(); + + TCHECKPOINT; +} + +void +StorageReflectSession :: +NotifySubscribersThatNodeChanged(DataNode & modifiedNode, const MessageRef & oldData, bool isBeingRemoved) +{ + TCHECKPOINT; + + for (HashtableIterator subIter = modifiedNode.GetSubscribers(); subIter.HasData(); subIter++) + { + StorageReflectSession * next = dynamic_cast(GetSession(*subIter.GetKey())()); + if ((next)&&((next != this)||(GetReflectToSelf()))) next->NodeChanged(modifiedNode, oldData, isBeingRemoved); + } + + TCHECKPOINT; +} + +void +StorageReflectSession :: +NotifySubscribersThatNodeIndexChanged(DataNode & modifiedNode, char op, uint32 index, const String & key) +{ + TCHECKPOINT; + + for (HashtableIterator subIter = modifiedNode.GetSubscribers(); subIter.HasData(); subIter++) + { + AbstractReflectSessionRef nRef = GetSession(*subIter.GetKey()); + StorageReflectSession * s = dynamic_cast(nRef()); + if (s) s->NodeIndexChanged(modifiedNode, op, index, key); + } + + TCHECKPOINT; +} + +void +StorageReflectSession :: +NotifySubscribersOfNewNode(DataNode & newNode) +{ + TCHECKPOINT; + + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + StorageReflectSession * next = dynamic_cast(iter.GetValue()()); + if (next) next->NodeCreated(newNode); // always notify; !Self filtering will be done elsewhere + } + + TCHECKPOINT; +} + +void +StorageReflectSession :: +NodeCreated(DataNode & newNode) +{ + newNode.IncrementSubscriptionRefCount(GetSessionIDString(), _subscriptions.GetMatchCount(newNode, newNode.GetData()(), 0)); // FogBugz #5803 +} + +void +StorageReflectSession :: +NodeChanged(DataNode & modifiedNode, const MessageRef & oldData, bool isBeingRemoved) +{ + TCHECKPOINT; + + if (GetSubscriptionsEnabled()) + { + if (_subscriptions.GetNumFilters() > 0) + { + ConstMessageRef constOldData = oldData; // We need a non-const ConstMessageRef to pass to MatchesNode(), in case MatchesNode() changes the ref -- even though we won't use the changed data + bool matchedBefore = _subscriptions.MatchesNode(modifiedNode, constOldData, 0); + + // uh oh... we gotta determine whether the modified node's status wrt QueryFilters has changed! + // Based on that, we will simulate for the client the node's "addition" or "removal" at the appropriate times. + if (isBeingRemoved) + { + if (matchedBefore == false) return; // since the node didn't match before either, no node-removed-update is necessary now + } + else if (constOldData()) + { + ConstMessageRef constNewData = modifiedNode.GetData(); + bool matchesNow = _subscriptions.MatchesNode(modifiedNode, constNewData, 0); + + if ((matchedBefore == false)&&(matchesNow == false)) return; // no change in status, so no update is necessary + else if ((matchedBefore)&&(matchesNow == false)) isBeingRemoved = true; // no longer matches, so we need to send a node-removed update + } + else if (matchedBefore == false) return; // Adding a new node: only notify the client if it matches at least one of his QueryFilters + } + NodeChangedAux(modifiedNode, isBeingRemoved); + } +} + +void +StorageReflectSession :: +NodeChangedAux(DataNode & modifiedNode, bool isBeingRemoved) +{ + TCHECKPOINT; + + if (_nextSubscriptionMessage() == NULL) _nextSubscriptionMessage = GetMessageFromPool(PR_RESULT_DATAITEMS); + if (_nextSubscriptionMessage()) + { + _sharedData->_subsDirty = true; + String np; + if (modifiedNode.GetNodePath(np) == B_NO_ERROR) + { + if (isBeingRemoved) + { + if (_nextSubscriptionMessage()->HasName(np, B_MESSAGE_TYPE)) + { + // Oops! We can't specify a remove-then-add operation for a given node in a single Message, + // because the removes and the adds are expressed via different mechanisms. + // So in this case we have to force a flush of the current message now, and + // then add the new notification to the next one! + PushSubscriptionMessages(); + NodeChangedAux(modifiedNode, isBeingRemoved); // and then start again + } + else _nextSubscriptionMessage()->AddString(PR_NAME_REMOVED_DATAITEMS, np); + } + else _nextSubscriptionMessage()->AddMessage(np, modifiedNode.GetData()); + } + if (_nextSubscriptionMessage()->GetNumNames() >= _maxSubscriptionMessageItems) PushSubscriptionMessages(); + } + else WARN_OUT_OF_MEMORY; +} + +void +StorageReflectSession :: +NodeIndexChanged(DataNode & modifiedNode, char op, uint32 index, const String & key) +{ + TCHECKPOINT; + + if (GetSubscriptionsEnabled()) + { + if (_nextIndexSubscriptionMessage() == NULL) _nextIndexSubscriptionMessage = GetMessageFromPool(PR_RESULT_INDEXUPDATED); + + String np; + if ((_nextIndexSubscriptionMessage())&&(modifiedNode.GetNodePath(np) == B_NO_ERROR)) + { + _sharedData->_subsDirty = true; + char temp[100]; + sprintf(temp, "%c" UINT32_FORMAT_SPEC ":%s", op, index, key()); + _nextIndexSubscriptionMessage()->AddString(np, temp); + } + else WARN_OUT_OF_MEMORY; + // don't push subscription messages here.... it will be done elsewhere + } +} + +status_t +StorageReflectSession :: +SetDataNode(const String & nodePath, const MessageRef & dataMsgRef, bool overwrite, bool create, bool quiet, bool addToIndex, const String * optInsertBefore) +{ + TCHECKPOINT; + + DataNode * node = _sessionDir(); + if (node == NULL) return B_ERROR; + + if ((nodePath.HasChars())&&(nodePath[0] != '/')) + { + int32 prevSlashPos = -1; + int32 slashPos = 0; + DataNodeRef childNodeRef; + String nextClause; + while(slashPos >= 0) + { + slashPos = nodePath.IndexOf('/', prevSlashPos+1); + nextClause = nodePath.Substring(prevSlashPos+1, (slashPos >= 0) ? slashPos : (int32)nodePath.Length()); + DataNodeRef allocedNode; + if (node->GetChild(nextClause, childNodeRef) != B_NO_ERROR) + { + if ((create)&&(_currentNodeCount < _maxNodeCount)) + { + allocedNode = GetNewDataNode(nextClause, ((slashPos < 0)&&(addToIndex == false)) ? dataMsgRef : CastAwayConstFromRef(GetEmptyMessageRef())); + if (allocedNode()) + { + childNodeRef = allocedNode; + if ((slashPos < 0)&&(addToIndex)) + { + if (node->InsertOrderedChild(dataMsgRef, optInsertBefore, (nextClause.HasChars())?&nextClause:NULL, this, quiet?NULL:this, NULL) == B_NO_ERROR) + { + _currentNodeCount++; + _indexingPresent = true; + } + } + else if (node->PutChild(childNodeRef, this, ((quiet)||(slashPos < 0)) ? NULL : this) == B_NO_ERROR) _currentNodeCount++; + } + else {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + else return B_ERROR; + } + + node = childNodeRef(); + if ((slashPos < 0)&&(addToIndex == false)) + { + if ((node == NULL)||((overwrite == false)&&(node != allocedNode()))) return B_ERROR; + node->SetData(dataMsgRef, quiet ? NULL : this, (node == allocedNode())); // do this to trigger the changed-notification + } + prevSlashPos = slashPos; + } + } + + return B_NO_ERROR; +} + +bool +StorageReflectSession :: +HasPrivilege(int priv) const +{ + return ((_parameters.GetInt32(PR_NAME_PRIVILEGE_BITS) & (1<AddString(PR_NAME_TREE_REQUEST_ID, *id) == B_NO_ERROR))) + { + if (msg.HasName(PR_NAME_KEYS, B_STRING_TYPE)) + { + int32 maxDepth = -1; (void) msg.FindInt32(PR_NAME_MAXDEPTH, maxDepth); + + NodePathMatcher matcher; + matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, DEFAULT_PATH_PREFIX); + + void * args[] = {reply(), (void *)((long)maxDepth)}; + (void) matcher.DoTraversal((PathMatchCallback)GetSubtreesCallbackFunc, this, GetGlobalRoot(), true, args); + } + MessageReceivedFromSession(*this, reply, NULL); // send the result back to our client + } + } + break; + + case PR_COMMAND_NOOP: + // do nothing! + break; + + case PR_COMMAND_BATCH: + { + MessageRef subRef; + for (int i=0; msg.FindMessage(PR_NAME_KEYS, i, subRef) == B_NO_ERROR; i++) CallMessageReceivedFromGateway(subRef, userData); + } + break; + + case PR_COMMAND_KICK: + { + if (HasPrivilege(PR_PRIVILEGE_KICK)) + { + if (msg.HasName(PR_NAME_KEYS, B_STRING_TYPE)) + { + NodePathMatcher matcher; + (void) matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, DEFAULT_PATH_PREFIX); + (void) matcher.DoTraversal((PathMatchCallback)KickClientCallbackFunc, this, GetGlobalRoot(), true, NULL); + } + } + else BounceMessage(PR_RESULT_ERRORACCESSDENIED, msgRef); + } + break; + + case PR_COMMAND_ADDBANS: case PR_COMMAND_ADDREQUIRES: + if (HasPrivilege(PR_PRIVILEGE_ADDBANS)) + { + ReflectSessionFactoryRef factoryRef = GetFactory(GetPort()); + if (factoryRef()) factoryRef()->MessageReceivedFromSession(*this, msgRef, NULL); + } + else BounceMessage(PR_RESULT_ERRORACCESSDENIED, msgRef); + break; + + case PR_COMMAND_REMOVEBANS: case PR_COMMAND_REMOVEREQUIRES: + if (HasPrivilege(PR_PRIVILEGE_REMOVEBANS)) + { + ReflectSessionFactoryRef factoryRef = GetFactory(GetPort()); + if (factoryRef()) factoryRef()->MessageReceivedFromSession(*this, msgRef, NULL); + } + else BounceMessage(PR_RESULT_ERRORACCESSDENIED, msgRef); + break; + + case PR_COMMAND_SETPARAMETERS: + { + bool updateDefaultMessageRoute = false; + bool subscribeQuietly = msg.HasName(PR_NAME_SUBSCRIBE_QUIETLY); + Message getMsg(PR_COMMAND_GETDATA); + for (MessageFieldNameIterator it = msg.GetFieldNameIterator(); it.HasData(); it++) + { + const String & fn = it.GetFieldName(); + bool copyField = true; + if (strncmp(fn(), "SUBSCRIBE:", 10) == 0) + { + ConstQueryFilterRef filter; + MessageRef filterMsgRef; + if (msg.FindMessage(fn, filterMsgRef) == B_NO_ERROR) filter = GetGlobalQueryFilterFactory()()->CreateQueryFilter(*filterMsgRef()); + + String path = fn.Substring(10); + String fixPath(path); + _subscriptions.AdjustStringPrefix(fixPath, DEFAULT_PATH_PREFIX); + const PathMatcherEntry * e = _subscriptions.GetEntries().Get(fixPath); + if (e) + { + const QueryFilter * subscriptionFilter = e->GetFilter()(); + if ((GetSubscriptionsEnabled())&&((filter() != NULL)||(subscriptionFilter != NULL))) + { + // If the filter is different, then we need to change our subscribed-set to + // report the addition of nodes that match the new filter but not the old, and + // report the removal of the nodes that match the old filter but not the new. Whew! + void * args[] = {(void *)subscriptionFilter, (void *)filter()}; + NodePathMatcher temp; + if (temp.PutPathString(fixPath, ConstQueryFilterRef()) == B_NO_ERROR) (void) temp.DoTraversal((PathMatchCallback)ChangeQueryFilterCallbackFunc, this, GetGlobalRoot(), false, args); + } + + // And now, set e's filter to the new filter. + (void) _subscriptions.SetFilterForEntry(fixPath, filter); // FogBugz #5803 + } + else + { + // This marks any currently existing matching nodes so they know to notify us + // It must be done once per subscription path, as it uses per-sub ref-counting + NodePathMatcher temp; + if ((temp.PutPathString(fixPath, ConstQueryFilterRef()) == B_NO_ERROR)&&(_subscriptions.PutPathString(fixPath, filter) == B_NO_ERROR)) (void) temp.DoTraversal((PathMatchCallback)DoSubscribeRefCallbackFunc, this, GetGlobalRoot(), false, (void *)1L); + } + if ((subscribeQuietly == false)&&(getMsg.AddString(PR_NAME_KEYS, path) == B_NO_ERROR)) + { + // We have to have a filter message to match each string, to prevent "bleed-down" of earlier + // filters matching later strings. So add a dummy filter Message if we don't have an actual one. + if (filterMsgRef() == NULL) filterMsgRef.SetRef(const_cast(&GetEmptyMessage()), false); + (void) getMsg.AddMessage(PR_NAME_FILTERS, filterMsgRef); + } + } + else if (fn == PR_NAME_REFLECT_TO_SELF) SetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF, true); + else if (fn == PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS) SetRoutingFlag(MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS, true); + else if (fn == PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY) SetRoutingFlag(MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY, true); + else if (fn == PR_NAME_DISABLE_SUBSCRIPTIONS) + { + SetSubscriptionsEnabled(false); + } + else if ((fn == PR_NAME_KEYS)||(fn == PR_NAME_FILTERS)) + { + msg.MoveName(fn, _defaultMessageRouteMessage); + updateDefaultMessageRoute = true; + } + else if (fn == PR_NAME_SUBSCRIBE_QUIETLY) + { + // don't add this to the parameter set; it's just an "argument" for the SUBSCRIBE: fields. + copyField = false; + } + else if (fn == PR_NAME_MAX_UPDATE_MESSAGE_ITEMS) + { + (void) msg.FindInt32(PR_NAME_MAX_UPDATE_MESSAGE_ITEMS, _maxSubscriptionMessageItems); + } + else if (fn == PR_NAME_PRIVILEGE_BITS) + { + // don't add this to the parameter set; clients aren't allowed to change + // their privilege bits (maybe someday, for some clients, but not now) + copyField = false; + } + else if (fn == PR_NAME_REPLY_ENCODING) + { + int32 enc; + if (msg.FindInt32(PR_NAME_REPLY_ENCODING, enc) != B_NO_ERROR) enc = MUSCLE_MESSAGE_ENCODING_DEFAULT; + MessageIOGateway * gw = dynamic_cast(GetGateway()()); + if (gw) gw->SetOutgoingEncoding(enc); + } + + if (copyField) msg.CopyName(fn, _parameters); + } + if (updateDefaultMessageRoute) UpdateDefaultMessageRoute(); + if (getMsg.HasName(PR_NAME_KEYS)) DoGetData(getMsg); // return any data that matches the subscription + } + break; + + case PR_COMMAND_GETPARAMETERS: + { + if (_sessionDir()) + { + String np; + MessageRef resultMessage = GetMessageFromPool(_parameters); + if ((resultMessage())&&(_sessionDir()->GetNodePath(np) == B_NO_ERROR)) + { + // Add hard-coded params + + resultMessage()->RemoveName(PR_NAME_REFLECT_TO_SELF); + if (IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF)) resultMessage()->AddBool(PR_NAME_REFLECT_TO_SELF, true); + + resultMessage()->RemoveName(PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS); + if (IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS)) resultMessage()->AddBool(PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS, true); + + resultMessage()->RemoveName(PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY); + if (IsRoutingFlagSet(MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY)) resultMessage()->AddBool(PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY, true); + + resultMessage()->RemoveName(PR_NAME_SESSION_ROOT); + resultMessage()->AddString(PR_NAME_SESSION_ROOT, np); + + resultMessage()->RemoveName(PR_NAME_SERVER_VERSION); + resultMessage()->AddString(PR_NAME_SERVER_VERSION, MUSCLE_VERSION_STRING); + + resultMessage()->RemoveName(PR_NAME_SERVER_MEM_AVAILABLE); + resultMessage()->AddInt64(PR_NAME_SERVER_MEM_AVAILABLE, GetNumAvailableBytes()); + + resultMessage()->RemoveName(PR_NAME_SERVER_MEM_USED); + resultMessage()->AddInt64(PR_NAME_SERVER_MEM_USED, GetNumUsedBytes()); + + resultMessage()->RemoveName(PR_NAME_SERVER_MEM_MAX); + resultMessage()->AddInt64(PR_NAME_SERVER_MEM_MAX, GetMaxNumBytes()); + + uint64 now = GetRunTime64(); + + resultMessage()->RemoveName(PR_NAME_SERVER_UPTIME); + resultMessage()->AddInt64(PR_NAME_SERVER_UPTIME, now-GetServerStartTime()); + + resultMessage()->RemoveName(PR_NAME_SERVER_CURRENTTIMEUTC); + resultMessage()->AddInt64(PR_NAME_SERVER_CURRENTTIMEUTC, GetCurrentTime64(MUSCLE_TIMEZONE_UTC)); + + resultMessage()->RemoveName(PR_NAME_SERVER_CURRENTTIMELOCAL); + resultMessage()->AddInt64(PR_NAME_SERVER_CURRENTTIMELOCAL, GetCurrentTime64(MUSCLE_TIMEZONE_LOCAL)); + + resultMessage()->RemoveName(PR_NAME_SERVER_RUNTIME); + resultMessage()->AddInt64(PR_NAME_SERVER_RUNTIME, now); + + resultMessage()->RemoveName(PR_NAME_MAX_NODES_PER_SESSION); + resultMessage()->AddInt64(PR_NAME_MAX_NODES_PER_SESSION, _maxNodeCount); + + resultMessage()->RemoveName(PR_NAME_SERVER_SESSION_ID); + resultMessage()->AddInt64(PR_NAME_SERVER_SESSION_ID, GetServerSessionID()); + + AddApplicationSpecificParametersToParametersResultMessage(*resultMessage()); + + MessageReceivedFromSession(*this, resultMessage, NULL); + } + else WARN_OUT_OF_MEMORY; + } + } + break; + + case PR_COMMAND_REMOVEPARAMETERS: + { + bool updateDefaultMessageRoute = false; + const String * nextName; + for (int i=0; msg.FindString(PR_NAME_KEYS, i, &nextName) == B_NO_ERROR; i++) + { + // Search the parameters message for all parameters that match (nextName)... + StringMatcher matcher; + if (matcher.SetPattern(*nextName) == B_NO_ERROR) + { + if (matcher.IsPatternUnique()) (void) RemoveParameter(RemoveEscapeChars(*nextName), updateDefaultMessageRoute); // FogBugz #10055 + else + { + for (MessageFieldNameIterator it = _parameters.GetFieldNameIterator(); it.HasData(); it++) if (matcher.Match(it.GetFieldName()())) (void) RemoveParameter(it.GetFieldName(), updateDefaultMessageRoute); + } + } + } + if (updateDefaultMessageRoute) UpdateDefaultMessageRoute(); + } + break; + + case PR_COMMAND_SETDATA: + { + bool quiet = msg.HasName(PR_NAME_SET_QUIETLY); + for (MessageFieldNameIterator it = msg.GetFieldNameIterator(B_MESSAGE_TYPE); it.HasData(); it++) + { + MessageRef dataMsgRef; + for (int32 i=0; msg.FindMessage(it.GetFieldName(), i, dataMsgRef) == B_NO_ERROR; i++) SetDataNode(it.GetFieldName(), dataMsgRef, true, true, quiet); + } + } + break; + + case PR_COMMAND_INSERTORDEREDDATA: + (void) InsertOrderedData(msgRef, NULL); + break; + + case PR_COMMAND_REORDERDATA: + { + // Because REORDERDATA operates solely on pre-existing nodes, we can allow wildcards in our node paths. + if (_sessionDir()) + { + // Do each field as a separate operation (so they won't mess each other up) + for (MessageFieldNameIterator iter = msg.GetFieldNameIterator(B_STRING_TYPE); iter.HasData(); iter++) + { + const String * value; + if (msg.FindString(iter.GetFieldName(), &value) == B_NO_ERROR) + { + Message temp; + temp.AddString(PR_NAME_KEYS, iter.GetFieldName()); + + NodePathMatcher matcher; + (void) matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, temp, NULL); + (void) matcher.DoTraversal((PathMatchCallback)ReorderDataCallbackFunc, this, *_sessionDir(), true, (void *)value); + } + } + } + } + break; + + case PR_COMMAND_GETDATA: + DoGetData(msg); + break; + + case PR_COMMAND_REMOVEDATA: + { + NodePathMatcher matcher; + matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, NULL); + DoRemoveData(matcher, msg.HasName(PR_NAME_REMOVE_QUIETLY)); + } + break; + + case PR_RESULT_PARAMETERS: + // fall-thru + case PR_RESULT_DATAITEMS: + LogTime(MUSCLE_LOG_WARNING, "Warning, client at [%s] sent me a PR_RESULT_* code. Bad client!\n", GetHostName()()); + break; + + case PR_COMMAND_JETTISONRESULTS: + { + if (msg.HasName(PR_NAME_KEYS, B_STRING_TYPE)) + { + NodePathMatcher matcher; + matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, DEFAULT_PATH_PREFIX); + JettisonOutgoingResults(&matcher); + } + else JettisonOutgoingResults(NULL); + } + break; + + case PR_COMMAND_PING: + { + msg.what = PR_RESULT_PONG; // mark it as processed... + MessageReceivedFromSession(*this, msgRef, NULL); // and send it right back to our client + } + break; + + default: + BounceMessage(PR_RESULT_ERRORUNIMPLEMENTED, msgRef); + break; + } + } + else + { + // New for v1.85; if the message has a PR_NAME_SESSION field in it, make sure it's the correct one! + // This is to foil certain people (olorin ;^)) who would otherwise be spoofing messages from other people. + (void) msg.ReplaceString(false, PR_NAME_SESSION, GetSessionIDString()); + + // what code not in our reserved range: must be a client-to-client message + if (msg.HasName(PR_NAME_KEYS, B_STRING_TYPE)) + { + NodePathMatcher matcher; + (void) matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, DEFAULT_PATH_PREFIX); + (void) matcher.DoTraversal((PathMatchCallback)PassMessageCallbackFunc, this, GetGlobalRoot(), true, const_cast(&msgRef)); + } + else if (_parameters.HasName(PR_NAME_KEYS, B_STRING_TYPE)) + { + (void) _defaultMessageRoute.DoTraversal((PathMatchCallback)PassMessageCallbackFunc, this, GetGlobalRoot(), true, const_cast(&msgRef)); + } + else DumbReflectSession::MessageReceivedFromGateway(msgRef, userData); + } + + TCHECKPOINT; +} + +void StorageReflectSession :: UpdateDefaultMessageRoute() +{ + _defaultMessageRoute.Clear(); + _defaultMessageRoute.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, _defaultMessageRouteMessage, DEFAULT_PATH_PREFIX); +} + +/** A little bitty class just to hold the find-sessions-traversal's results properly */ +class FindMatchingSessionsData +{ +public: + FindMatchingSessionsData(Hashtable & results, uint32 maxResults) : _results(results), _ret(B_NO_ERROR), _maxResults(maxResults) + { + // empty + } + + Hashtable & _results; + status_t _ret; + uint32 _maxResults; +}; + +AbstractReflectSessionRef StorageReflectSession :: FindMatchingSession(const String & nodePath, const ConstQueryFilterRef & filter, bool matchSelf) const +{ + AbstractReflectSessionRef ret; + Hashtable results; + if ((FindMatchingSessions(nodePath, filter, results, matchSelf, 1) == B_NO_ERROR)&&(results.HasItems())) ret = results.GetFirstValueWithDefault(); + return ret; +} + +status_t StorageReflectSession :: FindMatchingSessions(const String & nodePath, const ConstQueryFilterRef & filter, Hashtable & retSessions, bool includeSelf, uint32 maxResults) const +{ + TCHECKPOINT; + + status_t ret = B_NO_ERROR; + + if (nodePath.HasChars()) + { + const char * s; + String temp; + if (nodePath.StartsWith('/')) s = nodePath()+1; + else + { + temp = nodePath.Prepend(DEFAULT_PATH_PREFIX "/"); + s = temp(); + } + + NodePathMatcher matcher; + if (matcher.PutPathString(s, filter) == B_NO_ERROR) + { + FindMatchingSessionsData data(retSessions, maxResults); + (void) matcher.DoTraversal((PathMatchCallback)FindSessionsCallbackFunc, const_cast(this), GetGlobalRoot(), true, &data); + ret = data._ret; + } + else ret = B_ERROR; + } + else return retSessions.Put(GetSessions()); + + if (includeSelf == false) (void) retSessions.Remove(&GetSessionIDString()); + return ret; +} + +status_t StorageReflectSession :: SendMessageToMatchingSessions(const MessageRef & msgRef, const String & nodePath, const ConstQueryFilterRef & filter, bool includeSelf) +{ + TCHECKPOINT; + + if (nodePath.HasChars()) + { + const char * s; + String temp; + if (nodePath.StartsWith("/")) s = nodePath()+1; + else + { + temp = nodePath.Prepend(DEFAULT_PATH_PREFIX "/"); + s = temp(); + } + + NodePathMatcher matcher; + if (matcher.PutPathString(s, filter) == B_NO_ERROR) + { + void * sendMessageData[] = {const_cast(&msgRef), &includeSelf}; // gotta include the includeSelf param too, alas + (void) matcher.DoTraversal((PathMatchCallback)SendMessageCallbackFunc, this, GetGlobalRoot(), true, sendMessageData); + return B_NO_ERROR; + } + return B_ERROR; + } + else + { + BroadcastToAllSessions(msgRef, NULL, includeSelf); + return B_NO_ERROR; + } +} + +/** A little bitty class just to hold the find-nodes-traversal's results properly */ +class FindMatchingNodesData +{ +public: + FindMatchingNodesData(Queue & results, uint32 maxResults) : _results(results), _ret(B_NO_ERROR), _maxResults(maxResults) + { + // empty + } + + Queue & _results; + status_t _ret; + uint32 _maxResults; +}; + +int StorageReflectSession :: FindNodesCallback(DataNode & node, void * userData) +{ + FindMatchingNodesData * data = (FindMatchingNodesData *) userData; + if (data->_results.AddTail(DataNodeRef(&node)) != B_NO_ERROR) + { + data->_ret = B_ERROR; // Oops, out of memory! + return -1; // abort now + } + else return (data->_results.GetNumItems() == data->_maxResults) ? -1 : (int)node.GetDepth(); // continue traversal as usual unless, we have reached our limit +} + +DataNodeRef StorageReflectSession :: FindMatchingNode(const String & nodePath, const ConstQueryFilterRef & filter) const +{ + Queue results; + return ((FindMatchingNodes(nodePath, filter, results, 1) == B_NO_ERROR)&&(results.HasItems())) ? results.Head() : DataNodeRef(); +} + +status_t StorageReflectSession :: FindMatchingNodes(const String & nodePath, const ConstQueryFilterRef & filter, Queue & retNodes, uint32 maxResults) const +{ + status_t ret = B_NO_ERROR; + + bool isGlobal = nodePath.StartsWith('/'); + NodePathMatcher matcher; + if (matcher.PutPathString(isGlobal?nodePath.Substring(1):nodePath, filter) == B_NO_ERROR) + { + FindMatchingNodesData data(retNodes, maxResults); + (void) matcher.DoTraversal((PathMatchCallback)FindNodesCallbackFunc, const_cast(this), isGlobal?GetGlobalRoot():*_sessionDir(), true, &data); + ret = data._ret; + } + else ret = B_ERROR; + + return ret; +} + +int +StorageReflectSession :: +SendMessageCallback(DataNode & node, void * userData) +{ + void ** a = (void **) userData; + return PassMessageCallbackAux(node, *((MessageRef *)a[0]), *((bool *)a[1])); +} + +status_t StorageReflectSession :: InsertOrderedData(const MessageRef & msgRef, Hashtable * optNewNodes) +{ + TCHECKPOINT; + + // Because INSERTORDEREDDATA operates solely on pre-existing nodes, we can allow wildcards in our node paths. + if ((_sessionDir())&&(msgRef())) + { + void * args[2] = {msgRef(), optNewNodes}; + NodePathMatcher matcher; + (void) matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, *msgRef(), NULL); + (void) matcher.DoTraversal((PathMatchCallback)InsertOrderedDataCallbackFunc, this, *_sessionDir(), true, args); + return B_NO_ERROR; + } + return B_ERROR; +} + +void +StorageReflectSession :: +BounceMessage(uint32 errorCode, const MessageRef & msgRef) +{ + // Unknown code; bounce it back to our client + MessageRef bounce = GetMessageFromPool(errorCode); + if (bounce()) + { + bounce()->AddMessage(PR_NAME_REJECTED_MESSAGE, msgRef); + MessageReceivedFromSession(*this, bounce, NULL); // send rejection notice to client + } +} + +void +StorageReflectSession :: +DoGetData(const Message & msg) +{ + TCHECKPOINT; + + NodePathMatcher matcher; + matcher.PutPathsFromMessage(PR_NAME_KEYS, PR_NAME_FILTERS, msg, DEFAULT_PATH_PREFIX); + + MessageRef messageArray[2]; // first is the DATAITEMS message, second is the INDEXUPDATED message (both demand-allocated) + (void) matcher.DoTraversal((PathMatchCallback)GetDataCallbackFunc, this, GetGlobalRoot(), true, messageArray); + + // Send any still-pending "get" results... + SendGetDataResults(messageArray[0]); + SendGetDataResults(messageArray[1]); +} + +void +StorageReflectSession :: +SendGetDataResults(MessageRef & replyMessage) +{ + TCHECKPOINT; + + if (replyMessage()) + { + MessageReceivedFromSession(*this, replyMessage, NULL); + replyMessage.Reset(); // force re-alloc later if need be + } +} + +void StorageReflectSession :: DoRemoveData(NodePathMatcher & matcher, bool quiet) +{ + TCHECKPOINT; + + if (_sessionDir()) + { + Queue removeSet; + (void) matcher.DoTraversal((PathMatchCallback)RemoveDataCallbackFunc, this, *_sessionDir(), true, &removeSet); + for (int i=removeSet.GetNumItems()-1; i>=0; i--) + { + DataNode * next = removeSet[i](); + DataNode * parent = next->GetParent(); + if ((next)&&(parent)) parent->RemoveChild(next->GetNodeName(), quiet ? NULL : this, true, &_currentNodeCount); + } + } +} + +status_t StorageReflectSession :: RemoveDataNodes(const String & nodePath, const ConstQueryFilterRef & filterRef, bool quiet) +{ + TCHECKPOINT; + + NodePathMatcher matcher; + if (matcher.PutPathString(nodePath, filterRef) != B_NO_ERROR) return B_ERROR; + DoRemoveData(matcher, quiet); + return B_NO_ERROR; +} + +void +StorageReflectSession :: +PushSubscriptionMessages() +{ + TCHECKPOINT; + + if (_sharedData->_subsDirty) + { + _sharedData->_subsDirty = false; + + // Send out any subscription results that were generated... + for (HashtableIterator iter(GetSessions()); iter.HasData(); iter++) + { + StorageReflectSession * nextSession = dynamic_cast(iter.GetValue()()); + if (nextSession) + { + nextSession->PushSubscriptionMessage(nextSession->_nextSubscriptionMessage); + nextSession->PushSubscriptionMessage(nextSession->_nextIndexSubscriptionMessage); + } + } + PushSubscriptionMessages(); // in case these generated even more messages... + } +} + +void +StorageReflectSession :: +PushSubscriptionMessage(MessageRef & ref) +{ + TCHECKPOINT; + + MessageRef oldRef = ref; // (ref) is one of our _next*SubscriptionMessage members + while(oldRef()) + { + ref.Reset(); /* In case FromSession() wants to add more suscriptions... */ + MessageReceivedFromSession(*this, oldRef, NULL); + oldRef = ref; + } +} + +int +StorageReflectSession :: +PassMessageCallback(DataNode & node, void * userData) +{ + return PassMessageCallbackAux(node, *((MessageRef *)userData), GetReflectToSelf()); +} + +int +StorageReflectSession :: +PassMessageCallbackAux(DataNode & node, const MessageRef & msgRef, bool includeSelfOkay) +{ + TCHECKPOINT; + + DataNode * n = &node; + while(n->GetDepth() > 2) n = n->GetParent(); // go up to session level... + StorageReflectSession * next = dynamic_cast(GetSession(n->GetNodeName())()); + if ((next)&&((next != this)||(includeSelfOkay))) next->MessageReceivedFromSession(*this, msgRef, &node); + return 2; // This causes the traversal to immediately skip to the next session +} + +int +StorageReflectSession :: +FindSessionsCallback(DataNode & node, void * userData) +{ + TCHECKPOINT; + + DataNode * n = &node; + while(n->GetDepth() > 2) n = n->GetParent(); // go up to session level... + AbstractReflectSessionRef sref = GetSession(n->GetNodeName()); + + StorageReflectSession * next = dynamic_cast(sref()); + FindMatchingSessionsData * data = (FindMatchingSessionsData *) userData; + if ((next)&&(data->_results.Put(&next->GetSessionIDString(), sref) != B_NO_ERROR)) + { + data->_ret = B_ERROR; // Oops, out of memory! + return -1; // abort now + } + else return (data->_results.GetNumItems() == data->_maxResults) ? -1 : 2; // This causes the traversal to immediately skip to the next session +} + +int +StorageReflectSession :: +KickClientCallback(DataNode & node, void * /*userData*/) +{ + TCHECKPOINT; + + DataNode * n = &node; + while(n->GetDepth() > 2) n = n->GetParent(); // go up to session level... + AbstractReflectSessionRef sref = GetSession(n->GetNodeName()); + StorageReflectSession * next = dynamic_cast(sref()); + if ((next)&&(next != this)) + { + LogTime(MUSCLE_LOG_DEBUG, "Session [%s/%s] is kicking session [%s/%s] off the server.\n", GetHostName()(), GetSessionIDString()(), next->GetHostName()(), next->GetSessionIDString()()); + next->EndSession(); // die!! + } + return 2; // This causes the traversal to immediately skip to the next session +} + +int +StorageReflectSession :: +GetSubtreesCallback(DataNode & node, void * ud) +{ + TCHECKPOINT; + + void ** args = (void **)ud; + Message * reply = (Message *) args[0]; + int32 maxDepth = (int32) ((long)args[1]); + + bool inMyOwnSubtree = false; // default: actual value will only be calculated if it makes a difference + bool reflectToSelf = GetReflectToSelf(); + if (reflectToSelf == false) + { + // Make sure (node) isn't part of our own tree! If it is, move immediately to the next session + const DataNode * n = &node; + while(n->GetDepth() > 2) n = n->GetParent(); + if ((_indexingPresent == false)&&(GetSession(n->GetNodeName())() == this)) return 2; // skip to next session node + } + // Don't send our own data to our own client; he already knows what we have, because he uploaded it! + if ((inMyOwnSubtree == false)||(reflectToSelf)) + { + MessageRef subMsg = GetMessageFromPool(); + String nodePath; + if ((subMsg() == NULL)||(node.GetNodePath(nodePath) != B_NO_ERROR)||(reply->AddMessage(nodePath, subMsg) != B_NO_ERROR)||(SaveNodeTreeToMessage(*subMsg(), &node, "", true, (maxDepth>=0)?(uint32)maxDepth:MUSCLE_NO_LIMIT, NULL) != B_NO_ERROR)) return 0; + } + return node.GetDepth(); // continue traversal as usual +} + +int +StorageReflectSession :: +ChangeQueryFilterCallback(DataNode & node, void * ud) +{ + void ** args = (void **) ud; + const QueryFilter * oldFilter = (const QueryFilter *) args[0]; + const QueryFilter * newFilter = (const QueryFilter *) args[1]; + ConstMessageRef constMsg1 = node.GetData(); + ConstMessageRef constMsg2 = node.GetData(); + bool oldMatches = ((constMsg1() == NULL)||(oldFilter == NULL)||(oldFilter->Matches(constMsg1, &node))); + bool newMatches = ((constMsg2() == NULL)||(newFilter == NULL)||(newFilter->Matches(constMsg2, &node))); + if (oldMatches != newMatches) NodeChangedAux(node, oldMatches); + return node.GetDepth(); // continue traversal as usual +} + +int +StorageReflectSession :: +DoSubscribeRefCallback(DataNode & node, void * userData) +{ + node.IncrementSubscriptionRefCount(GetSessionIDString(), (long) userData); + return node.GetDepth(); // continue traversal as usual +} + +int +StorageReflectSession :: +GetDataCallback(DataNode & node, void * userData) +{ + TCHECKPOINT; + + MessageRef * messageArray = (MessageRef *) userData; + + bool inMyOwnSubtree = false; // default: actual value will only be calculated if it makes a difference + bool reflectToSelf = GetReflectToSelf(); + if (reflectToSelf == false) + { + // Make sure (node) isn't part of our own tree! If it is, move immediately to the next session + const DataNode * n = &node; + while(n->GetDepth() > 2) n = n->GetParent(); + if ((_indexingPresent == false)&&(GetSession(n->GetNodeName())() == this)) return 2; // skip to next session node + } + + // Don't send our own data to our own client; he already knows what we have, because he uploaded it! + if ((inMyOwnSubtree == false)||(reflectToSelf)) + { + MessageRef & resultMsg = messageArray[0]; + if (resultMsg() == NULL) resultMsg = GetMessageFromPool(PR_RESULT_DATAITEMS); + String np; + if ((resultMsg())&&(node.GetNodePath(np) == B_NO_ERROR)) + { + (void) resultMsg()->AddMessage(np, node.GetData()); + if (resultMsg()->GetNumNames() >= _maxSubscriptionMessageItems) SendGetDataResults(resultMsg); + } + else + { + WARN_OUT_OF_MEMORY; + return 0; // abort! + } + } + + // But indices we need to send to ourself no matter what, as they are generated on the server side. + const Queue * index = node.GetIndex(); + if (index) + { + uint32 indexLen = index->GetNumItems(); + if (indexLen > 0) + { + MessageRef & indexUpdateMsg = messageArray[1]; + if (indexUpdateMsg() == NULL) indexUpdateMsg = GetMessageFromPool(PR_RESULT_INDEXUPDATED); + String np; + if ((indexUpdateMsg())&&(node.GetNodePath(np) == B_NO_ERROR)) + { + char clearStr[] = {INDEX_OP_CLEARED, '\0'}; + (void) indexUpdateMsg()->AddString(np, clearStr); + for (uint32 i=0; iAddString(np, (*index)[i]()->GetNodeName().Prepend(temp)); + } + if (indexUpdateMsg()->GetNumNames() >= _maxSubscriptionMessageItems) SendGetDataResults(messageArray[1]); + } + else + { + WARN_OUT_OF_MEMORY; + return 0; // abort! + } + } + } + + return node.GetDepth(); // continue traveral as usual +} + +int +StorageReflectSession :: +RemoveDataCallback(DataNode & node, void * userData) +{ + TCHECKPOINT; + + if (node.GetDepth() > 2) // ensure that we never remove host nodes or session nodes this way + { + DataNodeRef nodeRef; + if (node.GetParent()->GetChild(node.GetNodeName(), nodeRef) == B_NO_ERROR) + { + (void) ((Queue *)userData)->AddTail(nodeRef); + return node.GetDepth()-1; // no sense in recursing down a node that we're going to delete anyway + } + } + return node.GetDepth(); +} + +int +StorageReflectSession :: +InsertOrderedDataCallback(DataNode & node, void * userData) +{ + TCHECKPOINT; + + void ** args = (void **) userData; + const Message * insertMsg = (const Message *) args[0]; + Hashtable * optRetResults = (Hashtable *) args[1]; + for (MessageFieldNameIterator iter = insertMsg->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + MessageRef nextRef; + for (int i=0; (insertMsg->FindMessage(iter.GetFieldName(), i, nextRef) == B_NO_ERROR); i++) (void) InsertOrderedChildNode(node, &iter.GetFieldName(), nextRef, optRetResults); + } + return node.GetDepth(); +} + +status_t +StorageReflectSession :: +InsertOrderedChildNode(DataNode & node, const String * optInsertBefore, const MessageRef & childNodeMsg, Hashtable * optAddNewChildren) +{ + if ((_currentNodeCount < _maxNodeCount)&&(node.InsertOrderedChild(childNodeMsg, optInsertBefore, NULL, this, this, optAddNewChildren) == B_NO_ERROR)) + { + _indexingPresent = true; // disable optimization in GetDataCallback() + _currentNodeCount++; + return B_NO_ERROR; + } + else return B_ERROR; +} + +int +StorageReflectSession :: +ReorderDataCallback(DataNode & node, void * userData) +{ + DataNode * indexNode = node.GetParent(); + if (indexNode) + { + DataNodeRef childNodeRef; + if (indexNode->GetChild(node.GetNodeName(), childNodeRef) == B_NO_ERROR) indexNode->ReorderChild(childNodeRef, (const String *) userData, this); + } + return node.GetDepth(); +} + +bool +StorageReflectSession :: NodePathMatcher :: +MatchesNode(DataNode & node, ConstMessageRef & optData, int rootDepth) const +{ + for (HashtableIterator iter(GetEntries()); iter.HasData(); iter++) if (PathMatches(node, optData, iter.GetValue(), rootDepth)) return true; + return false; +} + +uint32 +StorageReflectSession :: NodePathMatcher :: +GetMatchCount(DataNode & node, const Message * optData, int rootDepth) const +{ + TCHECKPOINT; + + int matchCount = 0; + ConstMessageRef fakeRef(optData, false); + for (HashtableIterator iter(GetEntries()); iter.HasData(); iter++) if (PathMatches(node, fakeRef, iter.GetValue(), rootDepth)) matchCount++; + return matchCount; +} + +bool +StorageReflectSession :: NodePathMatcher :: +PathMatches(DataNode & node, ConstMessageRef & optData, const PathMatcherEntry & entry, int rootDepth) const +{ + TCHECKPOINT; + + const StringMatcherQueue * nextSubscription = entry.GetParser()(); + int pd = nextSubscription->GetNumItems(); + if (pd == ((int32)node.GetDepth())-rootDepth) // only paths with the same number of clauses as the node's path (less rootDepth) can ever match + { + DataNode * travNode = &node; + bool match = true; // optimistic default + for (int j=nextSubscription->GetNumItems()-1; j>=rootDepth; j--,travNode=travNode->GetParent()) + { + StringMatcher * nextMatcher = nextSubscription->GetItemAt(j)->GetItemPointer(); + if ((nextMatcher)&&(nextMatcher->Match(travNode->GetNodeName()()) == false)) + { + match = false; + break; + } + } + if (match) return entry.FilterMatches(optData, &node); + } + return false; +} + +uint32 +StorageReflectSession :: NodePathMatcher :: +DoTraversal(PathMatchCallback cb, StorageReflectSession * This, DataNode & node, bool useFilters, void * userData) +{ + TraversalContext ctxt(cb, This, useFilters, userData, node.GetDepth()); + (void) DoTraversalAux(ctxt, node); + return ctxt.GetVisitCount(); +} + +int +StorageReflectSession :: NodePathMatcher :: +DoTraversalAux(TraversalContext & data, DataNode & node) +{ + TCHECKPOINT; + + int depth = node.GetDepth(); + bool parsersHaveWildcards = false; // optimistic default + { + // If none of our parsers are using wildcarding at our current level, we can use direct hash lookups (faster) + for (HashtableIterator iter(GetEntries()); iter.HasData(); iter++) + { + const StringMatcherQueue * nextQueue = iter.GetValue().GetParser()(); + if ((nextQueue)&&((int)nextQueue->GetNumItems() > depth-data.GetRootDepth())) + { + StringMatcher * nextMatcher = nextQueue->GetItemAt(depth-data.GetRootDepth())->GetItemPointer(); + if ((nextMatcher == NULL)||(nextMatcher->IsPatternUnique() == false)) + { + parsersHaveWildcards = true; // Oops, there will be some pattern matching involved, gotta iterate + break; + } + } + } + } + + if (parsersHaveWildcards) + { + // general case -- iterate over all children of our node and see if any match + for (DataNodeRefIterator it = node.GetChildIterator(); it.HasData(); it++) if (CheckChildForTraversal(data, it.GetValue()(), depth)) return depth; + } + else + { + // optimized case -- since our parsers are all node-specific, we can do a single lookup for each, + // and avoid having to iterate over all the children of this node. + Queue alreadyDid; // To make sure we don't do the same child twice (could happen if two matchers are the same) + for (HashtableIterator iter(GetEntries()); iter.HasData(); iter++) + { + const StringMatcherQueue * nextQueue = iter.GetValue().GetParser()(); + if ((nextQueue)&&((int)nextQueue->GetNumItems() > depth-data.GetRootDepth())) + { + const String & key = nextQueue->GetItemAt(depth-data.GetRootDepth())->GetItemPointer()->GetPattern(); + DataNodeRef nextChildRef; + if ((node.GetChild(RemoveEscapeChars(key), nextChildRef) == B_NO_ERROR)&&(alreadyDid.IndexOf(nextChildRef()) == -1)) + { + if (CheckChildForTraversal(data, nextChildRef(), depth)) return depth; + (void) alreadyDid.AddTail(nextChildRef()); + } + } + } + } + + return node.GetDepth(); +} + +bool +StorageReflectSession :: NodePathMatcher :: +CheckChildForTraversal(TraversalContext & data, DataNode * nextChild, int & depth) +{ + TCHECKPOINT; + + if (nextChild) + { + const String & nextChildName = nextChild->GetNodeName(); + bool matched = false; // set if we have called the callback on this child already + bool recursed = false; // set if we have recursed to this child already + + // Try all parsers and see if any of them match at this level + for (HashtableIterator iter(GetEntries()); iter.HasData(); iter++) + { + const StringMatcherQueue * nextQueue = iter.GetValue().GetParser()(); + if (nextQueue) + { + int numClausesInParser = nextQueue->GetNumItems(); + if (numClausesInParser > depth-data.GetRootDepth()) + { + const StringMatcher * nextMatcher = nextQueue->GetItemAt(depth-data.GetRootDepth())->GetItemPointer(); + if ((nextMatcher == NULL)||(nextMatcher->Match(nextChildName()))) + { + // A match! Now, depending on whether this match is the + // last clause in the path or not, we either do the callback or descend. + // But we make sure not to do either of these things more than once per node. + if (depth == data.GetRootDepth()+numClausesInParser-1) + { + if (matched == false) + { + // when there is more than one string being used to match, + // it's possible that two or more strings can "conspire" + // to match a node even though any given string doesn't match it. + // For example, if we have the match-strings: + // /j*/k* + // /k*/j* + // The node /jeremy/jenny would match, even though it isn't + // specified by any of the subscription strings. This is bad. + // So for multiple match-strings, we do an additional check + // to make sure there is a NodePathMatcher for this node. + ConstMessageRef constDataRef; + if (data.IsUseFiltersOkay()) constDataRef = nextChild->GetData(); + if (((GetEntries().GetNumItems() == 1)&&((data.IsUseFiltersOkay() == false)||(iter.GetValue().GetFilter()() == NULL)))||(MatchesNode(*nextChild, constDataRef, data.GetRootDepth()))) + { + int nextDepth; + if ((constDataRef() == NULL)||(constDataRef() == nextChild->GetData()())) nextDepth = data.CallCallbackMethod(*nextChild); // the usual/simple case + else + { + // Hey, the QueryFilter retargetted the ConstMessageRef! So we need the callback to see the modified Message, not the original one. + // We'll do that the sneaky way, by temporarily swapping out (nextChild)'s MessageRef, and then swapping it back in afterwards. + MessageRef origNodeMsg = nextChild->GetData(); + nextChild->SetData(CastAwayConstFromRef(constDataRef), NULL, false); + nextDepth = data.CallCallbackMethod(*nextChild); + nextChild->SetData(origNodeMsg, NULL, false); + } + + if (nextDepth < ((int)nextChild->GetDepth())-1) + { + depth = nextDepth; + return true; + } + matched = true; + if (recursed) break; // done both possible actions, so be lazy + } + } + } + else + { + if (recursed == false) + { + // If we match a non-terminal clause in the path, recurse to the child. + int nextDepth = DoTraversalAux(data, *nextChild); + if (nextDepth < ((int)nextChild->GetDepth())-1) + { + depth = nextDepth; + return true; + } + recursed = true; + if (matched) break; // done both possible actions, so be lazy + } + } + } + } + } + } + } + return false; +} + +DataNodeRef +StorageReflectSession :: +GetNewDataNode(const String & name, const MessageRef & initialValue) +{ + static DataNodeRef::ItemPool _nodePool; + + DataNodeRef ret(_nodePool.ObtainObject()); + if (ret()) ret()->Init(name, initialValue); + return ret; +} + +void +StorageReflectSession :: +JettisonOutgoingSubtrees(const String * optMatchString) +{ + TCHECKPOINT; + + AbstractMessageIOGateway * gw = GetGateway()(); + if (gw) + { + StringMatcher sm; + if ((optMatchString == NULL)||(sm.SetPattern(*optMatchString) == B_NO_ERROR)) + { + Queue & oq = gw->GetOutgoingMessageQueue(); + for (int i=oq.GetNumItems()-1; i>=0; i--) // must do this backwards! + { + Message * msg = oq.GetItemAt(i)->GetItemPointer(); + if ((msg)&&(msg->what == PR_RESULT_DATATREES)) + { + bool removeIt = false; + const char * batchID = NULL; (void) msg->FindString(PR_NAME_TREE_REQUEST_ID, &batchID); + if (optMatchString) + { + if ((batchID)&&(sm.Match(batchID))) removeIt = true; + } + else if (batchID == NULL) removeIt = true; + + if (removeIt) oq.RemoveItemAt(i); + } + } + } + } +} + +void +StorageReflectSession :: +JettisonOutgoingResults(const NodePathMatcher * matcher) +{ + TCHECKPOINT; + + AbstractMessageIOGateway * gw = GetGateway()(); + if (gw) + { + Queue & oq = gw->GetOutgoingMessageQueue(); + for (int i=oq.GetNumItems()-1; i>=0; i--) // must do this backwards! + { + Message * msg = oq.GetItemAt(i)->GetItemPointer(); + if ((msg)&&(msg->what == PR_RESULT_DATAITEMS)) + { + if (matcher) + { + // Remove any PR_NAME_REMOVED_DATAITEMS entries that match... + int nextr = 0; + const String * rname; + while(msg->FindString(PR_NAME_REMOVED_DATAITEMS, nextr, &rname) == B_NO_ERROR) + { + if (matcher->MatchesPath(rname->Cstr(), NULL, NULL)) msg->RemoveData(PR_NAME_REMOVED_DATAITEMS, nextr); + else nextr++; + } + + // Remove all matching items from the Message. (Yes, the iterator can handle this! :^)) + for (MessageFieldNameIterator iter = msg->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + const String & nextFieldName = iter.GetFieldName(); + if (matcher->GetNumFilters() > 0) + { + MessageRef nextSubMsgRef; + for (uint32 j=0; msg->FindMessage(nextFieldName, j, nextSubMsgRef) == B_NO_ERROR; /* empty */) + { + if (matcher->MatchesPath(nextFieldName(), nextSubMsgRef(), NULL)) msg->RemoveData(nextFieldName, 0); + else j++; + } + } + else if (matcher->MatchesPath(nextFieldName(), NULL, NULL)) msg->RemoveName(nextFieldName); + } + } + else msg->Clear(); + + if (msg->HasNames() == false) (void) oq.RemoveItemAt(i); + } + } + } +} + +status_t +StorageReflectSession :: CloneDataNodeSubtree(const DataNode & node, const String & destPath, bool allowOverwriteData, bool allowCreateNode, bool quiet, bool addToTargetIndex, const String * optInsertBefore, const ITraversalPruner * optPruner) +{ + TCHECKPOINT; + + { + MessageRef payload = node.GetData(); + if ((optPruner)&&(optPruner->MatchPath(destPath, payload) == false)) return B_NO_ERROR; + if ((payload() == NULL)||(SetDataNode(destPath, payload, allowOverwriteData, allowCreateNode, quiet, addToTargetIndex, optInsertBefore) != B_NO_ERROR)) return B_ERROR; + } + + // Then clone all of his children + for (DataNodeRefIterator iter = node.GetChildIterator(); iter.HasData(); iter++) + { + // Note that we don't deal with the index-cloning here; we do it separately (below) instead, for efficiency + if ((iter.GetValue()())&&(CloneDataNodeSubtree(*iter.GetValue()(), destPath+'/'+(*iter.GetKey()), false, true, quiet, false, NULL, optPruner) != B_NO_ERROR)) return B_ERROR; + } + + // Lastly, if he has an index, make sure the clone ends up with an equivalent index + const Queue * index = node.GetIndex(); + if (index) + { + DataNode * clone = GetDataNode(destPath); + if (clone) + { + uint32 idxLen = index->GetNumItems(); + for (uint32 i=0; iInsertIndexEntryAt(i, this, (*index)[i]()->GetNodeName()) != B_NO_ERROR) return B_ERROR; + } + else return B_ERROR; + } + + return B_NO_ERROR; +} + +// Recursive helper function +status_t +StorageReflectSession :: SaveNodeTreeToMessage(Message & msg, const DataNode * node, const String & path, bool saveData, uint32 maxDepth, const ITraversalPruner * optPruner) const +{ + TCHECKPOINT; + + { + MessageRef payload = node->GetData(); + if ((optPruner)&&(optPruner->MatchPath(path, payload) == false)) return B_NO_ERROR; + if ((saveData)&&(msg.AddMessage(PR_NAME_NODEDATA, payload) != B_NO_ERROR)) return B_ERROR; + } + + if ((node->HasChildren())&&(maxDepth > 0)) + { + // Save the node-index, if there is one + const Queue * index = node->GetIndex(); + if (index) + { + uint32 indexSize = index->GetNumItems(); + if (indexSize > 0) + { + MessageRef indexMsgRef(GetMessageFromPool()); + if ((indexMsgRef() == NULL)||(msg.AddMessage(PR_NAME_NODEINDEX, indexMsgRef) != B_NO_ERROR)) return B_ERROR; + Message * indexMsg = indexMsgRef(); + for (uint32 i=0; iAddString(PR_NAME_KEYS, (*index)[i]()->GetNodeName()) != B_NO_ERROR) return B_ERROR; + } + } + + // Then save the children, recursing to each one as necessary + { + MessageRef childrenMsgRef(GetMessageFromPool()); + if ((childrenMsgRef() == NULL)||(msg.AddMessage(PR_NAME_NODECHILDREN, childrenMsgRef) != B_NO_ERROR)) return B_ERROR; + for (DataNodeRefIterator childIter = node->GetChildIterator(); childIter.HasData(); childIter++) + { + DataNode * child = childIter.GetValue()(); + if (child) + { + String childPath(path); + if (childPath.HasChars()) childPath += '/'; + childPath += child->GetNodeName(); + + MessageRef childMsgRef(GetMessageFromPool()); + if ((childMsgRef() == NULL)||(childrenMsgRef()->AddMessage(child->GetNodeName(), childMsgRef) != B_NO_ERROR)||(SaveNodeTreeToMessage(*childMsgRef(), child, childPath, true, maxDepth-1, optPruner) != B_NO_ERROR)) return B_ERROR; + } + } + } + } + return B_NO_ERROR; +} + +status_t +StorageReflectSession :: RestoreNodeTreeFromMessage(const Message & msg, const String & path, bool loadData, bool appendToIndex, uint32 maxDepth, const ITraversalPruner * optPruner, bool quiet) +{ + TCHECKPOINT; + + if (loadData) + { + MessageRef payload; + if (msg.FindMessage(PR_NAME_NODEDATA, payload) != B_NO_ERROR) return B_ERROR; + if ((optPruner)&&(optPruner->MatchPath(path, payload) == false)) return B_NO_ERROR; + if (SetDataNode(path, payload, true, true, quiet, appendToIndex) != B_NO_ERROR) return B_ERROR; + } + else + { + MessageRef junk = CastAwayConstFromRef(GetEmptyMessageRef()); + if ((optPruner)&&(optPruner->MatchPath(path, junk) == false)) return B_NO_ERROR; + } + + MessageRef childrenRef; + if ((maxDepth > 0)&&(msg.FindMessage(PR_NAME_NODECHILDREN, childrenRef) == B_NO_ERROR)&&(childrenRef())) + { + // First recurse to the indexed nodes, adding them as indexed children + Hashtable indexLookup; + { + MessageRef indexRef; + if (msg.FindMessage(PR_NAME_NODEINDEX, indexRef) == B_NO_ERROR) + { + const String * nextFieldName; + for (int i=0; indexRef()->FindString(PR_NAME_KEYS, i, &nextFieldName) == B_NO_ERROR; i++) + { + MessageRef nextChildRef; + if (childrenRef()->FindMessage(*nextFieldName, nextChildRef) == B_NO_ERROR) + { + String childPath(path); + if (childPath.HasChars()) childPath += '/'; + childPath += *nextFieldName; + if (RestoreNodeTreeFromMessage(*nextChildRef(), childPath, true, true, maxDepth-1, optPruner, quiet) != B_NO_ERROR) return B_ERROR; + if (indexLookup.Put(nextFieldName, i) != B_NO_ERROR) return B_ERROR; + } + } + } + } + + // Then recurse to the non-indexed child nodes + { + for (MessageFieldNameIterator iter = childrenRef()->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + const String & nextFieldName = iter.GetFieldName(); + if (indexLookup.ContainsKey(&nextFieldName) == false) + { + MessageRef nextChildRef; + if ((childrenRef()->FindMessage(nextFieldName, nextChildRef) == B_NO_ERROR)&&(nextChildRef())) + { + String childPath(path); + if (childPath.HasChars()) childPath += '/'; + childPath += nextFieldName; + if (RestoreNodeTreeFromMessage(*nextChildRef(), childPath, true, false, maxDepth-1, optPruner, quiet) != B_NO_ERROR) return B_ERROR; + } + } + } + } + } + return B_NO_ERROR; +} + +status_t StorageReflectSession :: RemoveParameter(const String & paramName, bool & retUpdateDefaultMessageRoute) +{ + if (_parameters.HasName(paramName) == false) return B_ERROR; // FogBugz #6348: DO NOT remove paramName until the end of this method! + + if (paramName.StartsWith("SUBSCRIBE:")) + { + String str = paramName.Substring(10); + _subscriptions.AdjustStringPrefix(str, DEFAULT_PATH_PREFIX); + if (_subscriptions.RemovePathString(str) == B_NO_ERROR) + { + // Remove the references from this subscription from all nodes + NodePathMatcher temp; + (void) temp.PutPathString(str, ConstQueryFilterRef()); + (void) temp.DoTraversal((PathMatchCallback)DoSubscribeRefCallbackFunc, this, GetGlobalRoot(), false, (void *)-1L); + } + } + else if (paramName == PR_NAME_REFLECT_TO_SELF) SetRoutingFlag(MUSCLE_ROUTING_FLAG_REFLECT_TO_SELF, false); + else if (paramName == PR_NAME_ROUTE_GATEWAY_TO_NEIGHBORS) SetRoutingFlag(MUSCLE_ROUTING_FLAG_GATEWAY_TO_NEIGHBORS, false); + else if (paramName == PR_NAME_ROUTE_NEIGHBORS_TO_GATEWAY) SetRoutingFlag(MUSCLE_ROUTING_FLAG_NEIGHBORS_TO_GATEWAY, false); + else if (paramName == PR_NAME_DISABLE_SUBSCRIPTIONS) SetSubscriptionsEnabled(true); + else if (paramName == PR_NAME_MAX_UPDATE_MESSAGE_ITEMS) _maxSubscriptionMessageItems = DEFAULT_MAX_SUBSCRIPTION_MESSAGE_SIZE; // back to the default + else if (paramName == PR_NAME_REPLY_ENCODING) + { + MessageIOGateway * gw = dynamic_cast(GetGateway()()); + if (gw) gw->SetOutgoingEncoding(MUSCLE_MESSAGE_ENCODING_DEFAULT); + } + else if ((paramName == PR_NAME_KEYS)||(paramName == PR_NAME_FILTERS)) + { + _defaultMessageRouteMessage.RemoveName(paramName); + retUpdateDefaultMessageRoute = true; + } + + return _parameters.RemoveName(paramName); // FogBugz #6348: MUST BE DONE LAST, because this call may clear (paramName) +} + +void StorageReflectSession :: AddApplicationSpecificParametersToParametersResultMessage(Message & msg) const +{ + (void) msg; +} + +void StorageReflectSession :: TallyNodeBytes(const DataNode & n, uint32 & retNumNodes, uint32 & retNodeBytes) const +{ + retNumNodes++; + retNodeBytes += n.GetData()()->FlattenedSize(); + for (DataNodeRefIterator iter = n.GetChildIterator(); iter.HasData(); iter++) TallyNodeBytes(*iter.GetValue()(), retNumNodes, retNodeBytes); +} + +void StorageReflectSession :: PrintFactoriesInfo() const +{ + printf("There are " UINT32_FORMAT_SPEC " factories attached:\n", GetFactories().GetNumItems()); + for (HashtableIterator iter(GetFactories()); iter.HasData(); iter++) + { + const ReflectSessionFactory & f = *iter.GetValue()(); + printf(" Factory [%p] is listening at [%s] (%sid=" UINT32_FORMAT_SPEC ").\n", &f, iter.GetKey().ToString()(), f.IsReadyToAcceptSessions()?"ReadyToAcceptSessions, ":"", f.GetFactoryID()); + } +} + +void StorageReflectSession :: PrintSessionsInfo() const +{ + const Hashtable & t = GetSessions(); + printf("There are " UINT32_FORMAT_SPEC " sessions attached:\n", t.GetNumItems()); + uint32 totalNumOutMessages = 0, totalNumOutBytes = 0, totalNumNodes = 0, totalNumNodeBytes = 0; + for (HashtableIterator iter(t); iter.HasData(); iter++) + { + AbstractReflectSession * ars = iter.GetValue()(); + uint32 numOutMessages = 0, numOutBytes = 0, numNodes = 0, numNodeBytes = 0; + const AbstractMessageIOGateway * gw = ars->GetGateway()(); + if (gw) + { + const Queue & q = gw->GetOutgoingMessageQueue(); + numOutMessages = q.GetNumItems(); + for (uint32 i=0; iFlattenedSize(); + } + + StorageReflectSession * srs = dynamic_cast(iter.GetValue()()); + const DataNode * dn = srs ? srs->GetSessionNode()() : NULL; + if (dn) TallyNodeBytes(*dn, numNodes, numNodeBytes); + + String stateStr; + if (ars->IsConnectingAsync()) stateStr = stateStr.AppendWord("ConnectingAsync", ", "); + if (ars->IsConnected()) stateStr = stateStr.AppendWord("Connected", ", "); + if (ars->IsExpendable()) stateStr = stateStr.AppendWord("Expendable", ", "); + if (ars->IsReadyForInput()) stateStr = stateStr.AppendWord("IsReadyForInput", ", "); + if (ars->HasBytesToOutput()) stateStr = stateStr.AppendWord("HasBytesToOutput", ", "); + if (ars->WasConnected()) stateStr = stateStr.AppendWord("WasConnected", ", "); + if (stateStr.HasChars()) stateStr = stateStr.Prepend(", "); + printf(" Session [%s] (rfd=%i,wfd=%i) is [%s]: (" UINT32_FORMAT_SPEC " outgoing Messages, " UINT32_FORMAT_SPEC " Message-bytes, " UINT32_FORMAT_SPEC " nodes, " UINT32_FORMAT_SPEC " node-bytes%s)\n", iter.GetKey()->Cstr(), ars->GetSessionReadSelectSocket().GetFileDescriptor(), ars->GetSessionWriteSelectSocket().GetFileDescriptor(), ars->GetSessionDescriptionString()(), numOutMessages, numOutBytes, numNodes, numNodeBytes, stateStr()); + totalNumOutMessages += numOutMessages; + totalNumOutBytes += numOutBytes; + totalNumNodes += numNodes; + totalNumNodeBytes += numNodeBytes; + } + printf("------------------------------------------------------------\n"); + printf("Totals: " UINT32_FORMAT_SPEC " messages, " UINT32_FORMAT_SPEC " message-bytes, " UINT32_FORMAT_SPEC " nodes, " UINT32_FORMAT_SPEC " node-bytes.\n", totalNumOutMessages, totalNumOutBytes, totalNumNodes, totalNumNodeBytes); +} + +}; // end namespace muscle + diff --git a/reflector/StorageReflectSession.h b/reflector/StorageReflectSession.h new file mode 100644 index 00000000..f5febf3f --- /dev/null +++ b/reflector/StorageReflectSession.h @@ -0,0 +1,592 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleStorageReflectSession_h +#define MuscleStorageReflectSession_h + +#include "reflector/DataNode.h" +#include "reflector/DumbReflectSession.h" +#include "reflector/StorageReflectConstants.h" +#include "regex/PathMatcher.h" + +namespace muscle { + +/** + * This is a factory class that returns new StorageReflectSession objects. + */ +class StorageReflectSessionFactory : public ReflectSessionFactory, private CountedObject +{ +public: + /** Default constructor. The maximum incoming message size is set to "unlimited" by default. */ + StorageReflectSessionFactory(); + + /** Returns a new StorageReflectSession */ + virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & factoryInfo); + + /** Sets the maximum-bytes-per-incoming-message limit that we will set on the StorageReflectSession + * objects that we create. + * @param maxIncomingMessageBytes The maximum byte size of incoming flattened messages to allow. + */ + void SetMaxIncomingMessageSize(uint32 maxIncomingMessageBytes) {_maxIncomingMessageSize = maxIncomingMessageBytes;} + + /** Returns our current setting for the maximum incoming message size for sessions we produce. */ + uint32 GetMaxIncomingMessageSize() const {return _maxIncomingMessageSize;} + +protected: + /** If we have a limited maximum size for incoming messages, then this method + * demand-allocate the session's gateway, and set its max incoming message size if possible. + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory or the created gateway + * wasn't a MessageIOGateway) + */ + status_t SetMaxIncomingMessageSizeFor(AbstractReflectSession * session) const; + +private: + uint32 _maxIncomingMessageSize; +}; + +/** This class is an interface to an object that can prune the traversals used + * by RestoreNodeTreeFromMessage(), SaveNodeTreeToMessage(), and CloneDataNodeSubtree() + * so that only a subset of the traversal is done. + */ +class ITraversalPruner +{ +public: + /** Default Constructor */ + ITraversalPruner() {/* empty */} + + /** Destructor */ + virtual ~ITraversalPruner() {/* empty */} + + /** Should be implemented to return true iff we should traverse the node specified by (path) + * and his descendants. If this method returns false, the node specified by (path) + * will not be traversed, nor will any of his descendants. + * @param path The relative path of the node that is about to be traversed. + * @param nodeData A reference to the Message to be associated with this node. + * If desired, this can be replaced with a different MessageRef instead + * (but be careful not to modify the Message that (nodeData) points to; + * instead, allocate a new Message and set (nodeData) to point to it. + */ + virtual bool MatchPath(const String & path, MessageRef & nodeData) const = 0; +}; + +#define DECLARE_MUSCLE_TRAVERSAL_CALLBACK(sessionClass, funcName) \ + int funcName(DataNode & node, void * userData); \ + static int funcName##Func(StorageReflectSession * This, DataNode & node, void * userData) {return ((sessionClass *)This)->funcName(node, userData);} + +/** An intelligent AbstractReflectSession that knows how to + * store data on the server, and filter using wildcards. This class + * is used by the muscled server program to handle incoming connections. + * See StorageReflectConstants.h and/or "The Beginner's Guide.html" + * for details. + */ +class StorageReflectSession : public DumbReflectSession, private CountedObject +{ +public: + /** Default constructor. */ + StorageReflectSession(); + + /** Virtual Destructor. */ + virtual ~StorageReflectSession(); + + /** Called after the constructor, when the session is ready to interact with the server. + * @return B_NO_ERROR if everything is okay to go, B_ERROR if there was a problem + * setting up, or if the IP address of our client has been banned. + */ + virtual status_t AttachedToServer(); + + /** Implemented to remove our nodes from the server-side database and do misc cleanup. */ + virtual void AboutToDetachFromServer(); + + /** Called when a new message is received from our IO gateway. */ + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + + /** Overridden to call PushSubscriptionMessages() */ + virtual void AfterMessageReceivedFromGateway(const MessageRef & msg, void * userData); + + /** Returns a human-readable label for this session type: "Session" */ + virtual const char * GetTypeName() const {return "Session";} + + /** Prints to stdout a report of what sessions are currently present on this server, and how + * much memory each of them is currently using for various things. Useful for understanding + * what your RAM is being used for. + */ + void PrintSessionsInfo() const; + + /** Prints to stdout a report of what ReflectSessionFactories are currently present on this server, + * and what interfaces and ports they are listening on. + */ + void PrintFactoriesInfo() const; + + /** Returns a read-only reference to our parameters message */ + const Message & GetParametersConst() const {return _parameters;} + +protected: + /** + * Create or Set the value of a data node. + * @param nodePath Should be the path relative to the home dir (e.g. "MyNode/Child1/Grandchild2") + * @param dataMsgRef The value to set the node to + * @param allowOverwriteData Indicates whether existing node-data may be overwritten. If false, the method will fail if the specified node already exists. + * @param allowCreateNode indicates whether new nodes may be created. (If false, the method will fail if any node in the specified node path doesn't already exist) + * @param quiet If set to true, subscribers won't be updated regarding this change to the database. + * @param addToIndex If set to true, this node will be inserted under its parent as a new indexed node, rather than doing the regular add/replace bit. + * @param optInsertBefore If (addToIndex) is true, this may be the name of the node to insert this new node before in the index. + * If NULL, the new node will be appended to the end of the index. If (addToIndex) is false, this argument is ignored. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t SetDataNode(const String & nodePath, const MessageRef & dataMsgRef, bool allowOverwriteData=true, bool allowCreateNode=true, bool quiet=false, bool addToIndex=false, const String *optInsertBefore=NULL); + + /** Remove all nodes that match (nodePath). + * @param nodePath A relative path indicating node(s) to remove. Wildcarding is okay. + * @param filterRef If non-NULL, we'll use the given QueryFilter object to filter out our result set. + * Only nodes whose Messages match the QueryFilter will be removed. Default is a NULL reference. + * @param quiet If set to true, subscribers won't be updated regarding this change to the database + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t RemoveDataNodes(const String & nodePath, const ConstQueryFilterRef & filterRef = ConstQueryFilterRef(), bool quiet = false); + + /** + * Recursively saves a given subtree of the node database into the given Message object, for safe-keeping. + * (It's a bit more efficient than it looks, since all data Messages are reference counted rather than copied) + * @param msg the Message to save the subtree into. This object can later be provided to RestoreNodeTreeFromMessage() to restore the subtree. + * @param node The node to begin recursion from (i.e. the root of the subtree) + * @param path The path to prepend to the paths of children of the node. Used in the recursion; you typically want to pass in "" here. + * @param saveData Whether or not the payload Message of (node) should be saved. The payload Messages of (node)'s children will always be saved no matter what, as long as (maxDepth) is greater than zero. + * @param maxDepth How many levels of children should be saved to the Message. If left as MUSCLE_NO_LIMIT (the default), + * the entire subtree will be saved; otherwise the tree will be clipped to at most (maxDepth) levels. + * If (maxDepth) is zero, only (node) will be saved. + * @param optPruner If set non-NULL, this object will be used as a callback to prune the traversal, and optionally + * to filter the data that gets saved into (msg). + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t SaveNodeTreeToMessage(Message & msg, const DataNode * node, const String & path, bool saveData, uint32 maxDepth = MUSCLE_NO_LIMIT, const ITraversalPruner * optPruner = NULL) const; + + /** + * Recursively creates or updates a subtree of the node database from the given Message object. + * (It's a bit more efficient than it looks, since all data Messages are reference counted rather than copied) + * @param msg the Message to restore the subtree into. This Message is typically one that was created earlier by SaveNodeTreeToMessage(). + * @param path The relative path of the root node, e.g. "" is your home session node. + * @param loadData Whether or not the payload Message of (node) should be restored. The payload Messages of (node)'s children will always be restored no matter what. + * @param appendToIndex Used in the recursion to handle restoring indexed nodes. You will usually want to Leave it as false when you call this method. + * @param maxDepth How many levels of children should be restored from the Message. If left as MUSCLE_NO_LIMIT (the default), + * the entire subtree will be restored; otherwise the tree will be clipped to at most (maxDepth) levels. + * If (maxDepth) is zero, only (node) will be restored. + * @param optPruner If set non-NULL, this object will be used as a callback to prune the traversal, and optionally + * to filter the data that gets loaded from (msg). + * @param quiet If set to true, subscribers won't be updated regarding this change to the database + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t RestoreNodeTreeFromMessage(const Message & msg, const String & path, bool loadData, bool appendToIndex = false, uint32 maxDepth = MUSCLE_NO_LIMIT, const ITraversalPruner * optPruner = NULL, bool quiet = false); + + /** + * Create and insert a new node into one or more ordered child indices in the node tree. + * This method is similar to calling MessageReceivedFromGateway() with a PR_COMMAND_INSERTORDEREDDATA + * Message, only it gives more information back about what happened. + * @param insertMsg a PR_COMMAND_INSERTORDEREDDATA Message specifying what insertions should be done. + * @param optRetNewNodes If non-NULL, any newly-created DataNodes will be adde to this table for your inspection. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t InsertOrderedData(const MessageRef & insertMsg, Hashtable * optRetNewNodes); + + /** + * Utility method: Adds a new child node to the specified parent node. + * This method calls through to parentNode.InsertOrderedChild(), but also updates the StorageReflectSession's own internal state as necessary. + * @param parentNode The node to add the new child node to. + * @param data Reference to a message to create a new child node for. + * @param optInsertBefore if non-NULL, the name of the child to put the new child before in our index. If NULL, (or the specified child cannot be found) the new child will be appended to the end of the index. + * @param optAddNewChildren If non-NULL, any newly formed nodes will be added to this hashtable, keyed on their absolute node path. + * @return B_NO_ERROR on success, B_ERROR if out of memory + */ + status_t InsertOrderedChildNode(DataNode & parentNode, const String * optInsertBefore, const MessageRef & data, Hashtable * optAddNewChildren); + + /** + * This typedef represents the proper signature of a node-tree traversal callback function. + * Functions with this signature may be used with the NodePathMatcher::DoTraversal() method. + * @param This The StorageReflectSession doing the traveral, as specified in the DoTraversal() call + * @param node The DataNode that was matched the criteria of the traversal + * @param userData the same value that was passed in to the DoTraversal() method. + * @return The callback function should return the depth at which the traversal should continue after + * the callback is done. So to allow the traversal to continue normally, return (node.GetDepth()), + * or to terminate the traversal immediately, return 0, or to resume the search at the next + * session, return 2, or etc. + */ + typedef int (*PathMatchCallback)(StorageReflectSession * This, DataNode & node, void * userData); + + /** A slightly extended version of PathMatcher that knows how to handle DataNodes directly. */ + class NodePathMatcher : public PathMatcher + { + public: + /** Default constructor. */ + NodePathMatcher() {/* empty */} + + /** Destructor. */ + ~NodePathMatcher() {/* empty */} + + /** + * Returns true iff the given node matches our query. + * @param node the node to check to see if it matches + * @param optData Reference to a Message to use for QueryFilter filtering, or NULL to disable filtering. + * Note that a filter may optionally retarget this ConstMessageRef to point to a different + * Message, but it is not allowed to modify the Message that (optData) points to. + * @param rootDepth the depth at which the traversal started (i.e. 0 if started at root) + */ + bool MatchesNode(DataNode & node, ConstMessageRef & optData, int rootDepth) const; + + /** + * Does a depth-first traversal of the node tree, starting with (node) as the root. + * @param cb The callback function to call whenever a node is encountered in the traversal + * that matches at least one of our path strings. + * @param This pointer to our owner StorageReflectSession object. + * @param node The node to begin the traversal at. + * @param useFilters If true, we will only call (cb) on nodes whose Messages match our filter; otherwise + * we'll call (cb) on any node whose path matches, regardless of filtering status. + * @param userData Any value you wish; it will be passed along to the callback method. + * @returns The number of times (cb) was called by this traversal. + */ + uint32 DoTraversal(PathMatchCallback cb, StorageReflectSession * This, DataNode & node, bool useFilters, void * userData); + + /** + * Returns the number of path-strings that we contain that match (node). + * Note this is a bit more expensive than MatchesNode(), as we can't use short-circuit boolean logic here! + * @param node A node to check against our set of path-matcher strings. + * @param optData If non-NULL, any QueryFilters will use this Message to filter against. + * @param nodeDepth the depth at which the traversal started (i.e. 0 if started at root) + */ + uint32 GetMatchCount(DataNode & node, const Message * optData, int nodeDepth) const; + + private: + // This little structy class is used to pass context data around more efficiently + class TraversalContext + { + public: + TraversalContext(PathMatchCallback cb, StorageReflectSession * This, bool useFilters, void * userData, int rootDepth) : _cb(cb), _This(This), _useFilters(useFilters), _userData(userData), _rootDepth(rootDepth), _visitCount(0) {/* empty */} + + int CallCallbackMethod(DataNode & nextChild) {_visitCount++; return _cb(_This, nextChild, _userData);} + uint32 GetVisitCount() const {return _visitCount;} + int GetRootDepth() const {return _rootDepth;} + bool IsUseFiltersOkay() const {return _useFilters;} + + private: + PathMatchCallback _cb; + StorageReflectSession * _This; + bool _useFilters; + void * _userData; + int _rootDepth; + uint32 _visitCount; + }; + + int DoTraversalAux(TraversalContext & data, DataNode & node); + bool PathMatches(DataNode & node, ConstMessageRef & optData, const PathMatcherEntry & entry, int rootDepth) const; + bool CheckChildForTraversal(TraversalContext & data, DataNode * nextChild, int & depth); + }; + + friend class DataNode; + + /** + * Returns true iff we have the given PR_PRIVILEGE_* privilege. + * @return Default implementation looks at the PR_NAME_PRIVILEGE_BITS parameter. + */ + virtual bool HasPrivilege(int whichPriv) const; + + /** + * Returns the given Message to our client, inside an error message + * with the given error code. + */ + void BounceMessage(uint32 errorCode, const MessageRef & msgRef); + + /** Removes our nodes from the tree and removes our subscriptions from our neighbors. Called by the destructor. + */ + void Cleanup(); + + /** Convenience method: Adds sessions that contain nodes that match the given pattern to the passed-in Hashtable. + * @param nodePath the node path to match against. May be absolute (e.g. "/0/1234/frc*") or relative (e.g. "blah") + * If the nodePath is a zero-length String, all sessions will match. + * @param filter If non-NULL, only nodes whose data Messages match this filter will have their sessions added + * to the (retSessions) table. + * @param retSessions A table that will on return contain the set of matching sessions, keyed by their session ID strings. + * @param matchSelf If true, we will include as a candidate for pattern matching. Otherwise we won't. + * @param maxResults Maximum number of matching sessions to return. Defaults to MUSCLE_NO_LIMIT. + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t FindMatchingSessions(const String & nodePath, const ConstQueryFilterRef & filter, Hashtable & retSessions, bool matchSelf, uint32 maxResults = MUSCLE_NO_LIMIT) const; + + /** Convenience method: Same as FindMatchingSessions(), but finds only the first matching session. + * Returns a reference to the first matching session on success, or a NULL reference on failue. + */ + AbstractReflectSessionRef FindMatchingSession(const String & nodePath, const ConstQueryFilterRef & filter, bool matchSelf) const; + + /** Convenience method: Passes the given Message on to the sessions who match the given nodePath. + * (that is, any sessions who have nodes that match (nodePath) will have their MessageReceivedFromSession() + * method called with the given Message) + * @param msgRef the Message to pass on. + * @param nodePath the node path to match against. May be absolute (e.g. "/0/1234/frc*") or relative (e.g. "blah") + * If the nodePath is a zero-length String, all sessions will match. + * @param filter If non-NULL, only nodes whose data Messages match this filter will receive the Message. + * @param matchSelf If true, we will include as a candidate for pattern matching. Otherwise we won't. + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t SendMessageToMatchingSessions(const MessageRef & msgRef, const String & nodePath, const ConstQueryFilterRef & filter, bool matchSelf); + + /** Convenience method: Adds nodes that match the specified path to the passed-in Queue. + * @param nodePath the node path to match against. May be absolute (e.g. "/0/1234/frc*") or relative (e.g. "blah"). + If it's a relative path, only nodes in the current session's subtree will be searched. + * @param filter If non-NULL, only nodes whose data Messages match this filter will be added to the (retMatchingNodes) table. + * @param retMatchingNodes A Queue that will on return contain the list of matching nodes. + * @param maxResults Maximum number of matching nodes to return. Defaults to MUSCLE_NO_LIMIT. + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t FindMatchingNodes(const String & nodePath, const ConstQueryFilterRef & filter, Queue & retMatchingNodes, uint32 maxResults = MUSCLE_NO_LIMIT) const; + + /** Convenience method: Same as FindMatchingNodes(), but finds only the first matching node. + * Returns a reference to the first matching node on success, or a NULL reference on failue. + */ + DataNodeRef FindMatchingNode(const String & nodePath, const ConstQueryFilterRef & filter) const; + + /** Convenience method (used by some customized daemons) -- Given a source node and a destination path, + * Make (path) a deep, recursive clone of (node). + * @param sourceNode Reference to a DataNode to clone. + * @param destPath Path of where the newly created node subtree will appear. Should be relative to our home node. + * @param allowOverwriteData If true, we will clobber any previously existing node at the destination path. + * Otherwise, the existence of a pre-existing node there will cause us to fail. + * @param allowCreateNode If true, we will create a node at the destination path if necessary. + * Otherwise, the non-existence of a pre-existing node there will cause us to fail. + * @param quiet If false, no subscribers will be notified of the changes we make. + * @param addToTargetIndex If true, the newly created subtree will be added to the target node using InsertOrderedChild(). + * If false, it will be added using PutChild(). + * @param optInsertBefore If (addToTargetIndex) is true, this argument will be passed on to InsertOrderedChild(). + * Otherwise, this argument is ignored. + * @param optPruner If non-NULL, this object can be used as a callback to prune the traversal or filter + * the MessageRefs cloned. + * @return B_NO_ERROR on success, or B_ERROR on failure (may leave a partially cloned subtree on failure) + */ + status_t CloneDataNodeSubtree(const DataNode & sourceNode, const String & destPath, bool allowOverwriteData=true, bool allowCreateNode=true, bool quiet=false, bool addToTargetIndex=false, const String * optInsertBefore = NULL, const ITraversalPruner * optPruner = NULL); + + /** Tells other sessions that we have modified (node) in our node subtree. + * @param node The node that has been modfied. + * @param oldData If the node is being modified, this argument contains the node's previously + * held data. If it is being created, this is a NULL reference. If the node + * is being destroyed, this will contain the node's current data. + * @param isBeingRemoved If true, this node is about to go away. + */ + virtual void NotifySubscribersThatNodeChanged(DataNode & node, const MessageRef & oldData, bool isBeingRemoved); + + /** Tells other sessions that we have changed the index of (node) in our node subtree. + * @param node The node whose index was changed. + * @param op The INDEX_OP_* opcode of the change. (Note that currently INDEX_OP_CLEARED is never used here) + * @param index The index at which the operation took place (not defined for clear operations) + * @param key The key of the operation (aka the name of the associated node) + */ + virtual void NotifySubscribersThatNodeIndexChanged(DataNode & node, char op, uint32 index, const String & key); + + /** Called by NotifySubscribersThatNodeChanged(), to tell us that (node) has been + * created, modified, or is about to be destroyed. + * @param node The node that was modified, created, or is about to be destroyed. + * @param oldData If the node is being modified, this argument contains the node's previously + * held data. If it is being created, this is a NULL reference. If the node + * is being destroyed, this will contain the node's current data. + * @param isBeingRemoved True iff this node is about to be destroyed. + */ + virtual void NodeChanged(DataNode & node, const MessageRef & oldData, bool isBeingRemoved); + + /** Called by NotifySubscribersThatIndexChanged() to tell us how (node)'s index has been modified. + * @param node The node whose index was changed. + * @param op The INDEX_OP_* opcode of the change. (Note that currently INDEX_OP_CLEARED is never used here) + * @param index The index at which the operation took place (not defined for clear operations) + * @param key The key of the operation (aka the name of the associated node) + */ + virtual void NodeIndexChanged(DataNode & node, char op, uint32 index, const String & key); + + /** + * Takes any messages that were created in the NodeChanged() callbacks and + * sends them to their owner's MessageReceivedFromSession() method for + * processing and eventual forwarding to the client. + */ + void PushSubscriptionMessages(); + + /** Auxilliary helper method for PushSubscriptionMessages() */ + void PushSubscriptionMessage(MessageRef & msgRef); + + /** + * Executes a data-gathering tree traversal based on the PR_NAME_KEYS specified in the given message. + * @param getMsg a Message whose PR_NAME_KEYS field specifies which nodes we are interested in. + */ + void DoGetData(const Message & getMsg); + + /** + * Executes a node-removal traversal using the given NodePathMatcher. + * Note that you may find it easier to call RemoveDataNodes() than to call this method directly. + * @param matcher Reference to the NodePathMatcher object to use to guide the node-removal traversal. + * @param quiet If set to true, subscribers won't be updated regarding this change to the database + */ + void DoRemoveData(NodePathMatcher & matcher, bool quiet = false); + + /** Auxilliary helper function. */ + void SendGetDataResults(MessageRef & msg); + + /** + * If set false, we won't receive subscription updates. + * @param e Whether or not we wish to get update messages from our subscriptions. + */ + void SetSubscriptionsEnabled(bool e) {_subscriptionsEnabled = e;} + + /** Returns true iff our "subscriptions enabled" flag is set. Default state is of this flag is true. */ + bool GetSubscriptionsEnabled() const {return _subscriptionsEnabled;} + + /** Called when a PR_COMMAND_GETPARAMETERS Message is received from our client. After filling the usual + * data into the PR_RESULTS_PARAMETERS reply Message, the StorageReflectSession class will call this method, + * giving the subclass an opportunity to add additional (application-specific) data to the Message if it wants to. + * Default implementation is a no-op. + * @param parameterResultsMsg The PR_RESULT_PARAMETERS Message that is about to be sent back to the client. + */ + virtual void AddApplicationSpecificParametersToParametersResultMessage(Message & parameterResultsMsg) const; + + /** + * Convenience method: Uses the given path to lookup a single node in the node tree + * and return it. As of MUSCLE v4.11, wildcarding is supported in the path argument. + * If (path) begins with a '/', the search will begin with the root node of the tree; + * if not, it will begin with this session's node. Returns NULL on failure. + * @param path The fully specified path to a single node in the database. + * @return A pointer to the specified DataNode, or NULL if the node wasn't found. + */ + DataNode * GetDataNode(const String & path) const {return _sessionDir() ? _sessionDir()->FindFirstMatchingNode(path()) : NULL;} + + /** + * Call this to get a new DataNode, instead of using the DataNode ctor directly. + * @param nodeName The name to be given to the new DataNode that will be created. + * @param initialValue The Message payload to be given to the new DataNode that will be created. + * @return A reference to the new DataNode or a NULL reference if out of memory. + */ + DataNodeRef GetNewDataNode(const String & nodeName, const MessageRef & initialValue); + + /** + * Call this when you are done with a DataNode, instead of the DataNode destructor. + * @param node The DataNode to delete or recycle. + */ + void ReleaseDataNode(DataNode * node); + + /** + * This method goes through the outgoing-messages list looking for PR_RESULT_DATAITEMS + * messages. For each such message that it finds, it will remove all result items + * (both update items and items in PR_NAME_REMOVED_DATAITEMS) that match the expressions + * in the given NodePathMatcher. If the NodePathMatcher argument is NULL, all results + * will be removed. Any PR_RESULT_DATAITEMS messages that become empty will be removed + * completely from the queue. + * @param matcher Specifies which data items to delete from messages in the outgoing message queue. + */ + void JettisonOutgoingResults(const NodePathMatcher * matcher); + + /** + * This method goes through the outgoing-messages list looking for PR_RESULT_SUBTREE + * messages. For each such message that it finds, it will see if the message's PR_NAME_REQUEST_TREE_ID + * field matches the pattern specified by (optMatchString), and if it does, it will remove + * that Message. If (optMatchString) is NULL, on the other hand, all PR_RESULT_SUBTREE + * Messages that have no PR_NAME_REQUEST_TREE_ID field will be removed. + * @param optMatchString Optional pattern to match PR_NAME_REQUEST_TREE_ID strings against. If + * NULL, all PR_RESULT_SUBTREE Messages with no PR_NAME_TREE_REQUEST_ID field will match. + */ + void JettisonOutgoingSubtrees(const String * optMatchString); + + /** Returns a reference to our session node */ + DataNodeRef GetSessionNode() const {return _sessionDir;} + + /** Returns a reference to our parameters message */ + Message & GetParameters() {return _parameters;} + + /** Returns a reference to the global root node of the database */ + DataNode & GetGlobalRoot() const {return *(_sharedData->_root());} + + /** Macro to declare the proper callback declarations for the message-passing callback. */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, PassMessageCallback); /** Matching nodes are sent the given message. */ + +private: + void NodeChangedAux(DataNode & modifiedNode, bool isBeingRemoved); + void UpdateDefaultMessageRoute(); + status_t RemoveParameter(const String & paramName, bool & retUpdateDefaultMessageRoute); + int PassMessageCallbackAux(DataNode & node, const MessageRef & msgRef, bool matchSelfOkay); + void TallyNodeBytes(const DataNode & n, uint32 & retNumNodes, uint32 & retNodeBytes) const; + + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, KickClientCallback); /** Sessions of matching nodes are EndSession()'d */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, InsertOrderedDataCallback); /** Matching nodes have ordered data inserted into them as child nodes */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, ReorderDataCallback); /** Matching nodes area reordered in their parent's index */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, GetSubtreesCallback); /** Matching nodes are added in to the user Message as archived subtrees */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, GetDataCallback); /** Matching nodes are added to the (userData) Message */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, RemoveDataCallback); /** Matching nodes are placed in a list (userData) for later removal. */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, DoSubscribeRefCallback); /** Matching nodes are ref'd or unref'd with subscribed session IDs */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, ChangeQueryFilterCallback); /** Matching nodes are ref'd or unref'd depending on the QueryFilter change */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, FindSessionsCallback); /** Sessions of matching nodes are added to the given Hashtable */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, FindNodesCallback); /** Matching nodes are added to the given Queue */ + DECLARE_MUSCLE_TRAVERSAL_CALLBACK(StorageReflectSession, SendMessageCallback); /** Similar to PassMessageCallback except matchSelf is an argument */ + + /** + * Called by SetParent() to tell us that (node) has been created at a given location. + * We then respond by letting any matching subscriptions add their mark to the node. + * Private because subclasses should override NodeChanged(), not this. + * @param node the new Node that has been added to the database. + */ + void NodeCreated(DataNode & node); + + /** Tells other sessions that we have a new node available. + * Private because subclasses should override NotifySubscriberThatNodeChanged(), not this. + * @param newNode The newly available node. + */ + void NotifySubscribersOfNewNode(DataNode & newNode); + + /** This class holds data that needs to be shared by all attached instances + * of the StorageReflectSession class. An instance of this class is stored + * on demand in the central-state Message. + */ + class StorageReflectSessionSharedData + { + public: + StorageReflectSessionSharedData(const DataNodeRef & root) : _root(root), _subsDirty(false) {/* empty */} + + DataNodeRef _root; + bool _subsDirty; + }; + + /** Sets up the global root and other shared data */ + StorageReflectSessionSharedData * InitSharedData(); + + /** our current parameter set */ + Message _parameters; + + /** cached to be sent when a subscription triggers */ + MessageRef _nextSubscriptionMessage; + + /** cached to be sent when an index subscription triggers */ + MessageRef _nextIndexSubscriptionMessage; + + /** Points to shared data object; this object is the same for all StorageReflectSessions */ + StorageReflectSessionSharedData * _sharedData; + + /** this session's subdir (grandchild of _globalRoot) */ + DataNodeRef _sessionDir; + + /** Our session's set of active subscriptions */ + NodePathMatcher _subscriptions; + + /** Where user messages get sent if no PR_NAME_KEYS field is present */ + NodePathMatcher _defaultMessageRoute; + Message _defaultMessageRouteMessage; + + /** Whether or not we set to report subscription updates or not */ + bool _subscriptionsEnabled; + + /** Maximum number of subscription update fields per PR_RESULT message */ + uint32 _maxSubscriptionMessageItems; + + /** Optimization flag: set true the first time we index a node */ + bool _indexingPresent; + + /** The number of database nodes we currently have created */ + uint32 _currentNodeCount; + + /** The maximum number of database nodes we are allowed to create */ + uint32 _maxNodeCount; + + /** Our node class needs access to our internals too */ + friend class StorageReflectSession :: NodePathMatcher; +}; + +}; // end namespace muscle + +#endif + diff --git a/regex/FilePathExpander.cpp b/regex/FilePathExpander.cpp new file mode 100644 index 00000000..fa60ed5b --- /dev/null +++ b/regex/FilePathExpander.cpp @@ -0,0 +1,66 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "regex/StringMatcher.h" +#include "system/SystemInfo.h" // for GetFilePathSeparator() +#include "util/Directory.h" +#include "util/FilePathInfo.h" +#include "util/String.h" + +namespace muscle { + +status_t ExpandFilePathWildCardsAux(const String & curDir, const String & path, Queue & outputPaths, bool isSimpleFormat) +{ + const char * fs = GetFilePathSeparator(); + int32 sepIdx = path.IndexOf(fs); + String firstClause = (sepIdx >= 0) ? path.Substring(0, sepIdx) : path; + String restOfString = (sepIdx >= 0) ? path.Substring(sepIdx+1) : GetEmptyString(); + + StringMatcher sm(firstClause, isSimpleFormat); + Directory dir(curDir()); + if (dir.IsValid()) + { + if (CanWildcardStringMatchMultipleValues(firstClause)) + { + while(1) + { + const char * fn = dir.GetCurrentFileName(); + if (fn) + { + if ((strcmp(fn, ".") != 0)&&(strcmp(fn, "..") != 0)&&((fn[0] != '.')||(firstClause.StartsWith(".")))&&(sm.Match(fn))) + { + String childPath = String(dir.GetPath())+fn; + if (restOfString.HasChars()) + { + if (ExpandFilePathWildCardsAux(childPath, restOfString, outputPaths, isSimpleFormat) != B_NO_ERROR) return B_ERROR; + } + else if (outputPaths.AddTail(childPath) != B_NO_ERROR) return B_ERROR; + } + dir++; + } + else break; + } + } + else + { + String childPath = String(dir.GetPath())+firstClause; + if (FilePathInfo(childPath()).Exists()) + { + if (restOfString.HasChars()) + { + if (ExpandFilePathWildCardsAux(childPath, restOfString, outputPaths, isSimpleFormat) != B_NO_ERROR) return B_ERROR; + } + else if (outputPaths.AddTail(childPath) != B_NO_ERROR) return B_ERROR; + } + } + } + return B_NO_ERROR; +} + +status_t ExpandFilePathWildCards(const String & path, Queue & outputPaths, bool isSimpleFormat) +{ + const char * fs = GetFilePathSeparator(); + if (path.StartsWith(fs)) return ExpandFilePathWildCardsAux(fs, path()+1, outputPaths, isSimpleFormat); // start at root + else return ExpandFilePathWildCardsAux(".", path, outputPaths, isSimpleFormat); // start at current folder +} + +}; // end namespace muscle diff --git a/regex/FilePathExpander.h b/regex/FilePathExpander.h new file mode 100644 index 00000000..95b393cf --- /dev/null +++ b/regex/FilePathExpander.h @@ -0,0 +1,24 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleFilePathExpander_h +#define MuscleFilePathExpander_h + +#include "util/Queue.h" +#include "util/String.h" + +namespace muscle { + +/** Given a file path (e.g. "*.wav" or "/tmp/myfiles/foo_*.txt"), traverses the local filesystem + * and adds to (outputPaths) the expanded path of any matching files or folders that were discovered. + * @param path A potentially wildcarded file path (absolute or relative) + * @param outputPaths On successful return, this will contain all matching files and folders discovered. + * @param isSimpleFormat if true, a simple globbing syntax is expected in (expression). + * Otherwise, the full regex syntax will be expected. Defaults to true. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ExpandFilePathWildCards(const String & path, Queue & outputPaths, bool isSimpleFormat = true); + +}; // end namespace muscle + + +#endif diff --git a/regex/PathMatcher.cpp b/regex/PathMatcher.cpp new file mode 100644 index 00000000..54462b69 --- /dev/null +++ b/regex/PathMatcher.cpp @@ -0,0 +1,233 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "regex/PathMatcher.h" +#include "util/StringTokenizer.h" + +namespace muscle { + +StringMatcherQueueRef::ItemPool _stringMatcherQueuePool; +StringMatcherQueueRef::ItemPool * GetStringMatcherQueuePool() {return &_stringMatcherQueuePool;} + +void PathMatcher :: AdjustStringPrefix(String & path, const char * optPrepend) const +{ + if (path.HasChars()) + { + if (path[0] == '/') path = path.Substring(1); + else if (optPrepend) + { + String temp(optPrepend); // gcc/BeOS chokes on more compact code than this :^P + temp += '/'; + temp += path; + path = temp; + } + } +} + +status_t PathMatcher :: RemovePathString(const String & wildpath) +{ + PathMatcherEntry temp; + if (_entries.Remove(wildpath, temp) == B_NO_ERROR) + { + if (temp.GetFilter()()) _numFilters--; + return B_NO_ERROR; + } + return B_ERROR; +} + +status_t PathMatcher :: PutPathString(const String & path, const ConstQueryFilterRef & filter) +{ + TCHECKPOINT; + + if (path.HasChars()) + { + StringMatcherQueue * newQ = GetStringMatcherQueuePool()->ObtainObject(); + if (newQ) + { + StringMatcherQueueRef qRef(newQ); + + StringMatcherRef::ItemPool * smPool = GetStringMatcherPool(); + String temp; + int32 lastSlashPos = -1; + int32 slashPos = 0; + while(slashPos >= 0) + { + slashPos = path.IndexOf('/', lastSlashPos+1); + temp = path.Substring(lastSlashPos+1, (slashPos >= 0) ? slashPos : (int32)path.Length()); + StringMatcherRef smRef; + if (strcmp(temp(), "*")) + { + smRef.SetRef(smPool->ObtainObject()); + if ((smRef() == NULL)||(smRef()->SetPattern(temp()) != B_NO_ERROR)) return B_ERROR; + } + if (newQ->AddTail(smRef) != B_NO_ERROR) return B_ERROR; + lastSlashPos = slashPos; + } + if (_entries.Put(path, PathMatcherEntry(qRef, filter)) == B_NO_ERROR) + { + if (filter()) _numFilters++; + return B_NO_ERROR; + } + } + } + return B_ERROR; +} + +status_t PathMatcher :: PutPathsFromMessage(const char * pathFieldName, const char * optFilterFieldName, const Message & msg, const char * prependIfNoLeadingSlash) +{ + TCHECKPOINT; + + status_t ret = B_NO_ERROR; + + ConstQueryFilterRef filter; // declared here so that queries can "bleed down" the list without being specified multiple times + String str; + for (uint32 i=0; msg.FindString(pathFieldName, i, str) == B_NO_ERROR; i++) + { + if (optFilterFieldName) + { + MessageRef filterMsgRef; + if (msg.FindMessage(optFilterFieldName, i, filterMsgRef) == B_NO_ERROR) filter = GetGlobalQueryFilterFactory()()->CreateQueryFilter(*filterMsgRef()); + } + if (PutPathFromString(str, filter, prependIfNoLeadingSlash) != B_NO_ERROR) ret = B_ERROR; + } + return ret; +} + +status_t PathMatcher :: SetFilterForEntry(const String & path, const ConstQueryFilterRef & newFilter) +{ + PathMatcherEntry * pme = _entries.Get(path); + if (pme == NULL) return B_ERROR; + + if ((newFilter() != NULL) != (pme->GetFilter()() != NULL)) _numFilters += ((newFilter() != NULL) ? 1 : -1); // FogBugz #5803 + pme->SetFilter(newFilter); + return B_NO_ERROR; +} + +status_t PathMatcher :: PutPathFromString(const String & str, const ConstQueryFilterRef & filter, const char * prependIfNoLeadingSlash) +{ + String s = str; + AdjustStringPrefix(s, prependIfNoLeadingSlash); + return PutPathString(s, filter); +} + +status_t PathMatcher :: PutPathsFromMatcher(const PathMatcher & matcher) +{ + TCHECKPOINT; + + for (HashtableIterator iter(matcher.GetEntries(), HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + if (_entries.Put(iter.GetKey(), iter.GetValue()) == B_NO_ERROR) + { + if (iter.GetValue().GetFilter()()) _numFilters++; + } + else return B_ERROR; + } + return B_NO_ERROR; +} + +bool PathMatcher :: MatchesPath(const char * path, const Message * optMessage, const DataNode * optNode) const +{ + TCHECKPOINT; + + uint32 numClauses = GetPathDepth(path); + for (HashtableIterator iter(_entries, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + const StringMatcherQueue * nextSubscription = iter.GetValue().GetParser()(); + if ((nextSubscription)&&(nextSubscription->GetNumItems() == numClauses)) + { + bool matched = true; // default + + StringTokenizer tok(path+((path[0]=='/')?1:0), "/"); + for (uint32 j=0; jGetItemAt(j)->GetItemPointer(); + if ((nextToken == NULL)||((nextMatcher)&&(nextMatcher->Match(nextToken) == false))) + { + matched = false; + break; + } + } + + if (matched) + { + ConstMessageRef constMsg(optMessage, false); + const QueryFilter * filter = iter.GetValue().GetFilter()(); + if ((filter == NULL)||(optMessage == NULL)||(filter->Matches(constMsg, optNode))) return true; + } + } + } + return false; +} + +// Returns a pointer into (path) after the (depth)'th '/' char +const char * GetPathClause(int depth, const char * path) +{ + for (int i=0; iToString(); + else ret += "(null)"; + } + return ret; +} + +/** Returns a human-readable string representing this PathMatcherEntry, for easier debugging */ +String PathMatcherEntry :: ToString() const +{ + String ret; + if (_parser()) ret += _parser()->ToString().Prepend("Parser=[").Append("]"); + if (_filter()) + { + char buf[128]; sprintf(buf, "%sfilter=%p", ret.HasChars()?" ":"", _filter()); + ret += buf; + } + return ret; +} + +}; // end namespace muscle diff --git a/regex/PathMatcher.h b/regex/PathMatcher.h new file mode 100644 index 00000000..6f66d328 --- /dev/null +++ b/regex/PathMatcher.h @@ -0,0 +1,204 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MusclePathMatcher_h +#define MusclePathMatcher_h + +#include "util/Queue.h" +#include "regex/QueryFilter.h" +#include "regex/StringMatcher.h" +#include "message/Message.h" + +namespace muscle { + +DECLARE_REFTYPES(StringMatcher); + +/** A reference-countable list of references to StringMatcher objects */ +class StringMatcherQueue : public Queue, public RefCountable +{ +public: + /** Constructor. */ + StringMatcherQueue() {/* empty */} + + /** Returns a human-readable string representing this StringMatcherQueue, for easier debugging */ + String ToString() const; +}; + +/** Type for a reference to a queue of StringMatcher objects. */ +DECLARE_REFTYPES(StringMatcherQueue); + +/** Returns a point to a singleton ObjectPool that can be used + * to minimize the number of StringMatcherQueue allocations and deletions + * by recycling the StringMatcherQueue objects + */ +StringMatcherQueueRef::ItemPool * GetStringMatcherQueuePool(); + +/** This class represents one entry in a PathMatcher object. It contains the StringMatcher objects + * that test a wildcarded path, and optionally, the QueryFilter object that tests the content + * of matching Messages. + */ +class PathMatcherEntry +{ +public: + /** Default constructor */ + PathMatcherEntry() {/* empty */} + + /** Constructor + * @param parser Reference to the list of StringMatcher objects that represent our wildcarded path. + * @param filter Optional reference to the QueryFilter object that filters matching nodes by content. + */ + PathMatcherEntry(const StringMatcherQueueRef & parser, const ConstQueryFilterRef & filter) : _parser(parser), _filter(filter) {/* empty */} + + /** Returns a reference to our list of StringMatchers. */ + StringMatcherQueueRef GetParser() const {return _parser;} + + /** Returns a reference to our QueryFilter object. May be a NULL reference. */ + ConstQueryFilterRef GetFilter() const {return _filter;} + + /** Sets our QueryFilter object to the specified reference. Pass in a NULL reference to remove any existing QueryFilter. */ + void SetFilter(const ConstQueryFilterRef & filter) {_filter = filter;} + + /** Returns true iff we our filter matches the given Message, or if either (optMsg) or our filter is NULL. */ + bool FilterMatches(ConstMessageRef & optMsg, const DataNode * optNode) const + { + const QueryFilter * filter = GetFilter()(); + return ((filter == NULL)||(optMsg() == NULL)||(filter->Matches(optMsg, optNode))); + } + + /** Returns a human-readable string representing this PathMatcherEntry, for easier debugging */ + String ToString() const; + +private: + StringMatcherQueueRef _parser; + ConstQueryFilterRef _filter; +}; + +/** This class is used to do efficient regex-pattern-matching of one or more query strings (e.g. ".*./.*./j*remy/fries*") + * against various path strings (e.g. "12.18.240.15/123/jeremy/friesner"). A given path string is said to 'match' + * if it matches at least one of the query strings added to this object. + * Note that the search strings are always treated as relative paths -- if you pass in a search string with + * a leading slash, then it will be interpeted as a relative query with the first level of the query looking + * for nodes with name "". + * As of MUSCLE 2.40, this class also supports QueryFilter objects, so that only nodes whose Messages match the + * criteria of the QueryFilter are considered to match the query. This filtering is optional -- specify a null + * ConstQueryFilterRef to disable it. + */ +class PathMatcher : public RefCountable +{ +public: + /** Default Constructor. Creates a PathMatcher with no query strings in it */ + PathMatcher() : _numFilters(0) {/* empty */} + + /** Destructor */ + ~PathMatcher() {/* empty */} + + /** Removes all path nodes from this object */ + void Clear() {_entries.Clear(); _numFilters = 0;} + + /** Parses the given query string (e.g. "12.18.240.15/1234/beshare/j*") to this PathMatcher's set of query strings. + * Note that the search strings are always treated as relative paths -- if you pass in a search string with + * a leading slash, then it will be interpeted as a relative query with the first level of the query looking + * for nodes with name "". + * @param path a string of form "x/y/z/...", representing a pattern-matching function. + * @param filter Reference to a QueryFilter object to use to filter Messages that match our path. If the + * reference is a NULL reference, then no filtering will be done. + * @return B_NO_ERROR on success, B_ERROR if out of memory. + */ + status_t PutPathString(const String & path, const ConstQueryFilterRef & filter); + + /** Adds all of (matcher)'s StringMatchers to this matcher */ + status_t PutPathsFromMatcher(const PathMatcher & matcher); + + /** Adds zero or more wild paths to this matcher based on the contents of a string field in a Message. + * @param pathFieldName the name of a string field to look for node path expressions in. + * @param optFilterFieldName If non-NULL, the name of a Message field to look for archived QueryFilter objects (one for each node path expression) in. + * @param msg the Message to look for node path expressions in + * @param optPrependIfNoLeadingSlash If non-NULL, a '/' and this string will be prepended to any found path string that doesn't start with a '/' character. + * @return B_NO_ERROR on success, or B_ERROR if out of memory. + */ + status_t PutPathsFromMessage(const char * pathFieldName, const char * optFilterFieldName, const Message & msg, const char * optPrependIfNoLeadingSlash); + + /** Convenience method: Essentially the same as PutPathString(), except that (path) is first run through + * AdjustPathString() before being added to the path matcher. So this method has the same semantics as + * PutPathsFromMessage(), except that it adds a single string instead of a list of Strings. + * @param path Matching-string to add to this matcher. + * @param optPrependIfNoLeadingSlash If non-NULL, a '/' and this string will be prepended to any found path string that doesn't start with a '/' character. + * @param filter Reference to a QueryFilter object to use for content-based Message filter, or a NULL reference if + * no additional filtering is necessary. + * @return B_NO_ERROR on success, or B_ERROR if out of memory. + */ + status_t PutPathFromString(const String & path, const ConstQueryFilterRef & filter, const char * optPrependIfNoLeadingSlash); + + /** Removes the given path string and its associated StringMatchers from this matcher. + * @param wildpath the path string to remove + * @return B_NO_ERROR if the given path string was found and removed, or B_ERROR if it couldn't be found. + */ + status_t RemovePathString(const String & wildpath); + + /** + * Returns true iff the given fully qualified path string matches our query. + * @param path the path string to check to see if it matches + * @param optMessage if non-NULL, this Message will be tested by the QueryFilter objects. + * @param optNode this DataNode pointer will be passed to the QueryFilter objects. + */ + bool MatchesPath(const char * path, const Message * optMessage, const DataNode * optNode) const; + + /** + * Utility method. + * If (w) starts with '/', this method will remove the slash. + * If it doesn't start with a '/', and (optPrepend) is non-NULL, + * this method will prepend (optPrepend+"/") to (w). + * @param w A string to inspect, and potentially modify. + * @param optPrepend If non-NULL, a string to potentially prepend to (w) + */ + void AdjustStringPrefix(String & w, const char * optPrepend) const; + + /** Sets the new QueryFilter to be associated with the PathMatcherEntry that has the specified name. + * @param path Name of the PathMatcherEntry to update the filter of + * @param newFilter The new filter to give to PathMatcherEntry. May be a NULL ref if no filter is desired. + * @note You should always set an entries filter via this method rather than calling SetEntry() on + * the PathMatcherEntry object directly; that way, the PathMatcher class can keep its filters-count + * up to date. + * @returns B_NO_ERROR on success, or B_ERROR if an entry with the given path could not be found. + */ + status_t SetFilterForEntry(const String & path, const ConstQueryFilterRef & newFilter); + + /** Returns a read-only reference to our table of PathMatcherEntries. */ + const Hashtable & GetEntries() const {return _entries;} + + /** Returns the number of QueryFilters we are currently using. */ + uint32 GetNumFilters() const {return _numFilters;} + +private: + Hashtable _entries; + uint32 _numFilters; // count how many filters are installed; so we can optimize when there are none +}; +DECLARE_REFTYPES(PathMatcher); + +/** Returns a pointer into (path) after the (depth)'th '/' char + * @param depth the depth in the path to search for (0 == root, 1 == first level, 2 == second, etc.) + * @param path the path string (e.g. "/x/y/z/...") to search through + * @returns a pointer into (path), or NULL on failure. + */ +const char * GetPathClause(int depth, const char * path); + +/** As above, but returns a String object for just the given clause, + * instead of the entire remainder of the string. This version is + * somewhat less efficient, but easier to user. + * @param depth the depth in the path to search for (0 == root, 1 == first level, 2 == second, etc.) + * @param path the path string (e.g. "/x/y/z/...") to search through + * @returns The string that is the (nth) item in the path, or "" if the depth is invalid. + */ +String GetPathClauseString(int depth, const char * path); + +/** Returns the number of clauses in the given path string. + * @param path A path string. Any leading slash will be ignored. + * @returns The number of clauses in (path). (a.k.a the 'depth' of (path)) + * For example, "" and "/" return 0, "/test" returns 1, "test/me" + * returns 2, "/test/me/thoroughly" returns 3. + * + */ +int GetPathDepth(const char * path); + +}; // end namespace muscle + +#endif diff --git a/regex/QueryFilter.cpp b/regex/QueryFilter.cpp new file mode 100644 index 00000000..b9817c9d --- /dev/null +++ b/regex/QueryFilter.cpp @@ -0,0 +1,440 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "regex/QueryFilter.h" +#include "regex/StringMatcher.h" +#include "util/MiscUtilityFunctions.h" // for MemMem() + +namespace muscle { + +QueryFilter :: ~QueryFilter() +{ + // empty +} + +status_t QueryFilter :: SaveToArchive(Message & archive) const +{ + archive.what = TypeCode(); + return B_NO_ERROR; +} + +status_t QueryFilter :: SetFromArchive(const Message & archive) +{ + return AcceptsTypeCode(archive.what) ? B_NO_ERROR : B_ERROR; +} + +status_t WhatCodeQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((QueryFilter::SaveToArchive(archive) == B_NO_ERROR) && + (archive.AddInt32("min", _minWhatCode) == B_NO_ERROR) && + ((_maxWhatCode == _minWhatCode)||(archive.AddInt32("max", _maxWhatCode) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; +} + +status_t WhatCodeQueryFilter :: SetFromArchive(const Message & archive) +{ + if (QueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("min", _minWhatCode) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("max", _maxWhatCode) != B_NO_ERROR) _maxWhatCode = _minWhatCode; + return B_NO_ERROR; +} + +status_t ValueQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((QueryFilter::SaveToArchive(archive) == B_NO_ERROR) && + (archive.AddString("fn", _fieldName) == B_NO_ERROR) && + ((_index == 0)||(archive.AddInt32("idx", _index) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; +} + +status_t ValueQueryFilter :: SetFromArchive(const Message & archive) +{ + if (QueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("idx", _index) != B_NO_ERROR) _index = 0; + return archive.FindString("fn", _fieldName); +} + +status_t ValueExistsQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((ValueQueryFilter::SaveToArchive(archive) == B_NO_ERROR)&&((_typeCode == B_ANY_TYPE)||(archive.AddInt32("type", _typeCode) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; +} + +status_t ValueExistsQueryFilter :: SetFromArchive(const Message & archive) +{ + if (ValueQueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("type", _typeCode) != B_NO_ERROR) _typeCode = B_ANY_TYPE; + return B_NO_ERROR; +} + +status_t MultiQueryFilter :: SaveToArchive(Message & archive) const +{ + if (QueryFilter::SaveToArchive(archive) != B_NO_ERROR) return B_ERROR; + + uint32 numChildren = _children.GetNumItems(); + for (uint32 i=0; iCreateQueryFilter(*next()); + if ((kid() == NULL)||(_children.AddTail(kid) != B_NO_ERROR)) return B_ERROR; + } + return B_NO_ERROR; +} + +status_t AndOrQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((MultiQueryFilter::SaveToArchive(archive) == B_NO_ERROR)&& + ((_minMatches == MUSCLE_NO_LIMIT)||(archive.AddInt32("min", _minMatches) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; +} + +status_t AndOrQueryFilter :: SetFromArchive(const Message & archive) +{ + if (MultiQueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("min", _minMatches) != B_NO_ERROR) _minMatches = MUSCLE_NO_LIMIT; + return B_NO_ERROR; +} + +bool AndOrQueryFilter :: Matches(ConstMessageRef & msg, const DataNode * optNode) const +{ + const Queue & kids = GetChildren(); + uint32 numKids = kids.GetNumItems(); + uint32 matchCount = 0; + uint32 threshold = muscleMin(_minMatches, numKids); + for (uint32 i=0; i (numKids-i)) return false; // might as well give up, even all-true wouldn't get us there now + + const QueryFilter * next = kids[i](); + if ((next)&&(next->Matches(msg, optNode))&&(++matchCount == threshold)) return true; + } + return false; +} + +status_t NandNotQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((MultiQueryFilter::SaveToArchive(archive) == B_NO_ERROR)&& + ((_maxMatches == 0)||(archive.AddInt32("max", _maxMatches) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; +} + +status_t NandNotQueryFilter :: SetFromArchive(const Message & archive) +{ + if (MultiQueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_ERROR; + if (archive.FindInt32("max", _maxMatches) != B_NO_ERROR) _maxMatches = 0; + return B_NO_ERROR; +} + +bool NandNotQueryFilter :: Matches(ConstMessageRef & msg, const DataNode * optNode) const +{ + const Queue & kids = GetChildren(); + uint32 numKids = kids.GetNumItems(); + uint32 matchCount = 0; + uint32 threshold = muscleMin(_maxMatches, numKids); + for (uint32 i=0; i (numKids-i)) return true; // might as well give up, even all-true wouldn't get us there now + + const QueryFilter * next = kids[i](); + if ((next)&&(next->Matches(msg, optNode))&&(++matchCount > threshold)) return false; + } + return (matchCount < numKids); +} + +bool XorQueryFilter :: Matches(ConstMessageRef & msg, const DataNode * optNode) const +{ + const Queue & kids = GetChildren(); + uint32 numKids = kids.GetNumItems(); + uint32 matchCount = 0; + for (uint32 i=0; iMatches(msg, optNode))) matchCount++; + } + return ((matchCount % 2) != 0) ? true : false; +} + +status_t MessageQueryFilter :: SaveToArchive(Message & archive) const +{ + if (ValueQueryFilter::SaveToArchive(archive) != B_NO_ERROR) return B_NO_ERROR; + if ((_childFilter())&&(archive.AddArchiveMessage("kid", *_childFilter()) != B_NO_ERROR)) return B_ERROR; + return B_NO_ERROR; +} + +status_t MessageQueryFilter :: SetFromArchive(const Message & archive) +{ + if (ValueQueryFilter::SetFromArchive(archive) != B_NO_ERROR) return B_NO_ERROR; + + MessageRef subMsg; + if (archive.FindMessage("kid", subMsg) == B_NO_ERROR) + { + _childFilter = GetGlobalQueryFilterFactory()()->CreateQueryFilter(*subMsg()); + if (_childFilter() == NULL) return B_ERROR; + } + else _childFilter.Reset(); + + return B_NO_ERROR; +} + +bool MessageQueryFilter :: Matches(ConstMessageRef & msg, const DataNode * optNode) const +{ + MessageRef subMsg; + if (msg()->FindMessage(GetFieldName(), GetIndex(), subMsg) != B_NO_ERROR) return false; + if (_childFilter() == NULL) return true; + + ConstMessageRef constSubMsg = subMsg; + return _childFilter()->Matches(constSubMsg, optNode); +} + +status_t StringQueryFilter :: SaveToArchive(Message & archive) const +{ + return ((ValueQueryFilter::SaveToArchive(archive) == B_NO_ERROR)&& + (archive.AddString("val", _value) == B_NO_ERROR)&& + ((_assumeDefault == false)||(archive.AddString("val", _default) == B_NO_ERROR))) ? archive.AddInt8("op", _op) : B_ERROR; +} + +status_t StringQueryFilter :: SetFromArchive(const Message & archive) +{ + FreeMatcher(); + _default.Clear(); + _assumeDefault = (archive.FindString("val", 1, _default) == B_NO_ERROR); + return ((ValueQueryFilter::SetFromArchive(archive) == B_NO_ERROR)&&(archive.FindString("val", _value) == B_NO_ERROR)) ? archive.FindInt8("op", _op) : B_ERROR; +} + +bool StringQueryFilter :: Matches(ConstMessageRef & msg, const DataNode *) const +{ + const String * ps; + if (msg()->FindString(GetFieldName(), GetIndex(), &ps) != B_NO_ERROR) + { + if (_assumeDefault) ps = &_default; + else return false; + } + + const String & s = *ps; + switch(_op) + { + case OP_EQUAL_TO: return s == _value; + case OP_LESS_THAN: return s < _value; + case OP_GREATER_THAN: return s > _value; + case OP_LESS_THAN_OR_EQUAL_TO: return s <= _value; + case OP_GREATER_THAN_OR_EQUAL_TO: return s >= _value; + case OP_NOT_EQUAL_TO: return s != _value; + case OP_STARTS_WITH: return s.StartsWith(_value); + case OP_ENDS_WITH: return s.EndsWith(_value); + case OP_CONTAINS: return (s.IndexOf(_value) >= 0); + case OP_START_OF: return _value.StartsWith(s); + case OP_END_OF: return _value.EndsWith(s); + case OP_SUBSTRING_OF: return (_value.IndexOf(s) >= 0); + case OP_EQUAL_TO_IGNORECASE: return s.EqualsIgnoreCase(_value); + case OP_LESS_THAN_IGNORECASE: return (s.CompareToIgnoreCase(_value) < 0); + case OP_GREATER_THAN_IGNORECASE: return (s.CompareToIgnoreCase(_value) > 0); + case OP_LESS_THAN_OR_EQUAL_TO_IGNORECASE: return (s.CompareToIgnoreCase(_value) <= 0); + case OP_GREATER_THAN_OR_EQUAL_TO_IGNORECASE: return (s.CompareToIgnoreCase(_value) >= 0); + case OP_NOT_EQUAL_TO_IGNORECASE: return (s.EqualsIgnoreCase(_value) == false); + case OP_STARTS_WITH_IGNORECASE: return s.StartsWithIgnoreCase(_value); + case OP_ENDS_WITH_IGNORECASE: return s.EndsWithIgnoreCase(_value); + case OP_CONTAINS_IGNORECASE: return (s.IndexOfIgnoreCase(_value) >= 0); + case OP_START_OF_IGNORECASE: return _value.StartsWith(s); + case OP_END_OF_IGNORECASE: return _value.EndsWith(s); + case OP_SUBSTRING_OF_IGNORECASE: return (_value.IndexOf(s) >= 0); + case OP_SIMPLE_WILDCARD_MATCH: return DoMatch(s); + case OP_REGULAR_EXPRESSION_MATCH: return DoMatch(s); + default: /* do nothing */ break; + } + return false; +} + +void StringQueryFilter :: FreeMatcher() +{ + delete _matcher; + _matcher = NULL; +} + +bool StringQueryFilter :: DoMatch(const String & s) const +{ + if (_matcher == NULL) + { + switch(_op) + { + case OP_SIMPLE_WILDCARD_MATCH: + _matcher = newnothrow StringMatcher(_value, true); + if (_matcher == NULL) WARN_OUT_OF_MEMORY; + break; + + case OP_REGULAR_EXPRESSION_MATCH: + _matcher = newnothrow StringMatcher(_value, false); + if (_matcher == NULL) WARN_OUT_OF_MEMORY; + break; + } + } + return _matcher ? _matcher->Match(s()) : false; +} + +status_t RawDataQueryFilter :: SaveToArchive(Message & archive) const +{ + if ((ValueQueryFilter::SaveToArchive(archive) != B_NO_ERROR)||(archive.AddInt8("op", _op) != B_NO_ERROR)||((_typeCode != B_ANY_TYPE)&&(archive.AddInt32("type", _typeCode) != B_NO_ERROR))) return B_ERROR; + + const ByteBuffer * bb = _value(); + if (bb) + { + uint32 numBytes = bb->GetNumBytes(); + const uint8 * bytes = bb->GetBuffer(); + if ((bytes)&&(numBytes > 0)&&(archive.AddData("val", B_RAW_TYPE, bytes, numBytes) != B_NO_ERROR)) return B_ERROR; + } + + const ByteBuffer * dd = _default(); + if (dd) + { + uint32 numBytes = dd->GetNumBytes(); + const uint8 * bytes = dd->GetBuffer(); + if ((bytes)&&(archive.AddData("def", B_RAW_TYPE, bytes, numBytes) != B_NO_ERROR)) return B_ERROR; // I'm deliberately not checking (numBytes>0) here! + } + + return B_NO_ERROR; +} + +status_t RawDataQueryFilter :: SetFromArchive(const Message & archive) +{ + if ((ValueQueryFilter::SetFromArchive(archive) != B_NO_ERROR)||(archive.FindInt8("op", _op) != B_NO_ERROR)) return B_ERROR; + if (archive.FindInt32("type", _typeCode) != B_NO_ERROR) _typeCode = B_ANY_TYPE; + + _value.Reset(); + const void * data; + uint32 numBytes; + if (archive.FindData("val", B_RAW_TYPE, &data, &numBytes) == B_NO_ERROR) + { + _value = GetByteBufferFromPool(numBytes, (const uint8 *) data); + if (_value() == NULL) return B_ERROR; + } + + _default.Reset(); + if (archive.FindData("def", B_RAW_TYPE, &data, &numBytes) == B_NO_ERROR) + { + _default = GetByteBufferFromPool(numBytes, (const uint8 *) data); + if (_default() == NULL) return B_ERROR; + } + + return B_NO_ERROR; +} + +bool RawDataQueryFilter :: Matches(ConstMessageRef & msg, const DataNode *) const +{ + const void * hb; + uint32 hisNumBytes; + if (msg()->FindData(GetFieldName(), _typeCode, &hb, &hisNumBytes) != B_NO_ERROR) + { + if (_default()) + { + hb = _default()->GetBuffer(); + hisNumBytes = _default()->GetNumBytes(); + } + else return false; + } + + const uint8 * hisBytes = (const uint8 *) hb; + uint32 myNumBytes = _value() ? _value()->GetNumBytes() : 0; + const uint8 * myBytes = _value() ? _value()->GetBuffer() : NULL; + uint32 clen = muscleMin(myNumBytes, hisNumBytes); + if (myBytes == NULL) return false; + + switch(_op) + { + case OP_EQUAL_TO: return ((hisNumBytes == myNumBytes)&&(memcmp(myBytes, hisBytes, clen) == 0)); + + case OP_LESS_THAN: + { + int mret = memcmp(hisBytes, myBytes, clen); + if (mret < 0) return true; + return (mret == 0) ? (hisNumBytes < myNumBytes) : false; + } + + case OP_GREATER_THAN: + { + int mret = memcmp(hisBytes, myBytes, clen); + if (mret > 0) return true; + return (mret == 0) ? (hisNumBytes > myNumBytes) : false; + } + + case OP_LESS_THAN_OR_EQUAL_TO: + { + int mret = memcmp(hisBytes, myBytes, clen); + if (mret <= 0) return true; + return (mret == 0) ? (hisNumBytes <= myNumBytes) : false; + } + + case OP_GREATER_THAN_OR_EQUAL_TO: + { + int mret = memcmp(hisBytes, myBytes, clen); + if (mret >= 0) return true; + return (mret == 0) ? (hisNumBytes >= myNumBytes) : false; + } + + case OP_NOT_EQUAL_TO: return ((hisNumBytes != myNumBytes)||(memcmp(myBytes, hisBytes, clen) != 0)); + case OP_STARTS_WITH: return ((myNumBytes <= hisNumBytes)&&(memcmp(myBytes, hisBytes, clen) == 0)); + case OP_ENDS_WITH: return ((myNumBytes <= hisNumBytes)&&(memcmp(&myBytes[myNumBytes-clen], &hisBytes[hisNumBytes-clen], clen) == 0)); + case OP_CONTAINS: return (MemMem(hisBytes, hisNumBytes, myBytes, myNumBytes) != NULL); + case OP_START_OF: return ((hisNumBytes <= myNumBytes)&&(memcmp(hisBytes, myBytes, clen) == 0)); + case OP_END_OF: return ((hisNumBytes <= myNumBytes)&&(memcmp(&hisBytes[hisNumBytes-clen], &myBytes[myNumBytes-clen], clen) == 0)); + case OP_SUBSET_OF: return (MemMem(myBytes, myNumBytes, hisBytes, hisNumBytes) != NULL); + default: /* do nothing */ break; + } + return false; +} + +QueryFilterRef QueryFilterFactory :: CreateQueryFilter(const Message & msg) const +{ + QueryFilterRef ret = CreateQueryFilter(msg.what); + if ((ret())&&(ret()->SetFromArchive(msg) != B_NO_ERROR)) ret.Reset(); + return ret; +} + +QueryFilterRef MuscleQueryFilterFactory :: CreateQueryFilter(uint32 typeCode) const +{ + QueryFilter * f = NULL; + switch(typeCode) + { + case QUERY_FILTER_TYPE_WHATCODE: f = newnothrow WhatCodeQueryFilter; break; + case QUERY_FILTER_TYPE_VALUEEXISTS: f = newnothrow ValueExistsQueryFilter; break; + case QUERY_FILTER_TYPE_BOOL: f = newnothrow BoolQueryFilter; break; + case QUERY_FILTER_TYPE_DOUBLE: f = newnothrow DoubleQueryFilter; break; + case QUERY_FILTER_TYPE_FLOAT: f = newnothrow FloatQueryFilter; break; + case QUERY_FILTER_TYPE_INT64: f = newnothrow Int64QueryFilter; break; + case QUERY_FILTER_TYPE_INT32: f = newnothrow Int32QueryFilter; break; + case QUERY_FILTER_TYPE_INT16: f = newnothrow Int16QueryFilter; break; + case QUERY_FILTER_TYPE_INT8: f = newnothrow Int8QueryFilter; break; + case QUERY_FILTER_TYPE_POINT: f = newnothrow PointQueryFilter; break; + case QUERY_FILTER_TYPE_RECT: f = newnothrow RectQueryFilter; break; + case QUERY_FILTER_TYPE_STRING: f = newnothrow StringQueryFilter; break; + case QUERY_FILTER_TYPE_MESSAGE: f = newnothrow MessageQueryFilter; break; + case QUERY_FILTER_TYPE_RAWDATA: f = newnothrow RawDataQueryFilter; break; + case QUERY_FILTER_TYPE_NANDNOT: f = newnothrow NandNotQueryFilter; break; + case QUERY_FILTER_TYPE_ANDOR: f = newnothrow AndOrQueryFilter; break; + case QUERY_FILTER_TYPE_XOR: f = newnothrow XorQueryFilter; break; + default: return QueryFilterRef(); /* unknown type code! */ + } + if (f == NULL) WARN_OUT_OF_MEMORY; + return QueryFilterRef(f); +} + +static MuscleQueryFilterFactory _defaultQueryFilterFactory; +static QueryFilterFactoryRef _customQueryFilterFactoryRef; + +QueryFilterFactoryRef GetGlobalQueryFilterFactory() +{ + if (_customQueryFilterFactoryRef()) return _customQueryFilterFactoryRef; + else return QueryFilterFactoryRef(&_defaultQueryFilterFactory, false); +} + +void SetGlobalQueryFilterFactory(const QueryFilterFactoryRef & newFactory) +{ + _customQueryFilterFactoryRef = newFactory; +} + +}; // end namespace muscle diff --git a/regex/QueryFilter.h b/regex/QueryFilter.h new file mode 100644 index 00000000..ee55e773 --- /dev/null +++ b/regex/QueryFilter.h @@ -0,0 +1,842 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQueryFilter_h +#define MuscleQueryFilter_h + +#include "util/Queue.h" +#include "util/ByteBuffer.h" +#include "message/Message.h" + +namespace muscle { + +class DataNode; +class StringMatcher; + +/** Enumeration of QueryFilter type codes for the included QueryFilter classes */ +enum { + QUERY_FILTER_TYPE_WHATCODE = 1902537776, // 'qfl0' + QUERY_FILTER_TYPE_VALUEEXISTS, + QUERY_FILTER_TYPE_BOOL, + QUERY_FILTER_TYPE_DOUBLE, + QUERY_FILTER_TYPE_FLOAT, + QUERY_FILTER_TYPE_INT64, + QUERY_FILTER_TYPE_INT32, + QUERY_FILTER_TYPE_INT16, + QUERY_FILTER_TYPE_INT8, + QUERY_FILTER_TYPE_POINT, + QUERY_FILTER_TYPE_RECT, + QUERY_FILTER_TYPE_STRING, + QUERY_FILTER_TYPE_MESSAGE, + QUERY_FILTER_TYPE_RAWDATA, + QUERY_FILTER_TYPE_NANDNOT, + QUERY_FILTER_TYPE_ANDOR, + QUERY_FILTER_TYPE_XOR, + // add more codes here... + LAST_QUERY_FILTER_TYPE +}; + +/** Interface for any object that can examine a Message and tell whether it + * matches some criterion. Used primarily for filtering queries based on content. + */ +class QueryFilter : public RefCountable +{ +public: + /** Default constructor */ + QueryFilter() {/* empty */} + + /** Destructor */ + virtual ~QueryFilter(); + + /** Dumps our state into the given (archive). Default implementation + * just writes our TypeCode() into the 'what' of the Message + * and returns B_NO_ERROR. + * @param archive the Message to write our state into. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t SaveToArchive(Message & archive) const; + + /** Restores our state from the given (archive). Default implementation + * returns B_NO_ERROR iff the archive's what code matches our TypeCode(). + * @param archive The archive to restore our state from. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + virtual status_t SetFromArchive(const Message & archive); + + /** Should be overridden to return the appropriate QUERY_FILTER_TYPE_* code. */ + virtual uint32 TypeCode() const = 0; + + /** Must be implemented to return true iff (msg) matches the criterion. + * @param msg Reference to a read-only Message to check to see whether it matches our criteria or not. The QueryFilter is allowed to + * retarget this ConstMessageRef to point at a different Message if it wants to; the different Message will be used in the + * resulting query output. + * @param optNode The DataNode object the matching is being done on, or NULL if the DataNode is not available. + * @returns true iff the Messages matches, else false. + */ + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const = 0; + + /** Returns true iff we can be instantiated using a Message with the given + * 'what' code. Default implementation returns true iff (what) equals the + * value returned by our own TypeCode() method. + */ + virtual bool AcceptsTypeCode(uint32 what) const {return TypeCode() == what;} +}; +DECLARE_REFTYPES(QueryFilter); + +/** This filter tests the 'what' value of the Message. */ +class WhatCodeQueryFilter : public QueryFilter +{ +public: + /** Default constructor + * @param what The 'what' code to match on. Only Messages with this 'what' code will be matched. + */ + WhatCodeQueryFilter(uint32 what = 0) : _minWhatCode(what), _maxWhatCode(what) {/* empty */} + + /** Default constructor + * @param minWhat The minimum 'what' code to match on. Only messages with 'what' codes greater than or equal to this code will be matched. + * @param maxWhat The maximum 'what' code to match on. Only messages with 'what' codes less than or equal to this code will be matched. + */ + WhatCodeQueryFilter(uint32 minWhat, uint32 maxWhat) : _minWhatCode(minWhat), _maxWhatCode(maxWhat) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const {(void) optNode; return muscleInRange(msg()->what, _minWhatCode, _maxWhatCode);} + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_WHATCODE;} + +private: + uint32 _minWhatCode; + uint32 _maxWhatCode; +}; + +/** Semi-abstract base class for all query filters that test a single item in a Message */ +class ValueQueryFilter : public QueryFilter +{ +public: + /** Default constructor */ + ValueQueryFilter() : _index(0) {/* Empty */} + + /** Constructor. + * @param fieldName Name of the field in the Message to look at + * @param index Index of the item within the field to look at. Defaults to zero (i.e. the first item) + */ + ValueQueryFilter(const String & fieldName, uint32 index = 0) : _fieldName(fieldName), _index(index) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + + /** Sets our index-in-field setting. */ + void SetIndex(uint32 index) {_index = index;} + + /** Returns our current index-in-field setting, as set by SetIndex() or in our constructor */ + uint32 GetIndex() const {return _index;} + + /** Sets the field name to use. */ + void SetFieldName(const String & fieldName) {_fieldName = fieldName;} + + /** Returns the current field name, as set by SetFieldName() or in our constructor. */ + const String & GetFieldName() const {return _fieldName;} + +private: + String _fieldName; + uint32 _index; +}; + +/** This filter merely checks to see if the specified value exists in the target Message. */ +class ValueExistsQueryFilter : public ValueQueryFilter +{ +public: + /** Default constructor. + * @param typeCode Type code to check for. Default to B_ANY_TYPE (a wildcard value) + */ + ValueExistsQueryFilter(uint32 typeCode = B_ANY_TYPE) : _typeCode(typeCode) {/* empty */} + + /** Constructor + * @param fieldName Field name to look for + * @param typeCode type code to look for, or B_ANY_TYPE if you don't care what the type code is. + * @param index Optional index of the item within the field. Defaults to zero. + */ + ValueExistsQueryFilter(const String & fieldName, uint32 typeCode = B_ANY_TYPE, uint32 index = 0) : ValueQueryFilter(fieldName, index), _typeCode(typeCode) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_VALUEEXISTS;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const {(void) optNode; const void * junk; return (msg()->FindData(GetFieldName(), _typeCode, &junk, NULL) == B_NO_ERROR);} + + /** Sets the type code that we will look for in the target Message. + * @param typeCode the type code to look for. Use B_ANY_TYPE to indicate that you don't care what the type code is. + */ + void SetTypeCode(uint32 typeCode) {_typeCode = typeCode;} + + /** Returns the type code we are to look in the target Message for. + * Note that this method is different from TypeCode()! + */ + uint32 GetTypeCode() const {return _typeCode;} + +private: + uint32 _typeCode; +}; + + +/** Enumeration of mask operations available to NumericQueryFilter classes */ +enum { + NQF_MASK_OP_NONE = 0, + NQF_MASK_OP_AND, + NQF_MASK_OP_OR, + NQF_MASK_OP_XOR, + NQF_MASK_OP_NAND, + NQF_MASK_OP_NOR, + NQF_MASK_OP_XNOR, + NUM_NQF_MASK_OPS +}; + +template inline DataType NQFDoMaskOp(uint8 maskOp, const DataType & msgVal, const DataType & mask) +{ + switch(maskOp) + { + case NQF_MASK_OP_NONE: return msgVal; + case NQF_MASK_OP_AND: return msgVal & mask; + case NQF_MASK_OP_OR: return msgVal | mask; + case NQF_MASK_OP_XOR: return msgVal ^ mask; + case NQF_MASK_OP_NAND: return ~(msgVal & mask); + case NQF_MASK_OP_NOR: return ~(msgVal | mask); + case NQF_MASK_OP_XNOR: return ~(msgVal ^ mask); + default: return msgVal; + } +} + +// Dummy specializations for mask operations, for types that don't have bitwise operations defined. +template<> inline Point NQFDoMaskOp(uint8 /*maskOp*/, const Point & /*msgVal*/, const Point & /*argVal*/) {return Point();} +template<> inline Rect NQFDoMaskOp(uint8 /*maskOp*/, const Rect & /*msgVal*/, const Rect & /*argVal*/) {return Rect();} +template<> inline float NQFDoMaskOp(uint8 /*maskOp*/, const float & /*msgVal*/, const float & /*argVal*/) {return float();} +template<> inline double NQFDoMaskOp(uint8 /*maskOp*/, const double & /*msgVal*/, const double & /*argVal*/) {return double();} + +/** This templated class is used to generate a number of numeric-comparison-query classes, all of which are quite similar to each other. */ +template +class NumericQueryFilter : public ValueQueryFilter +{ +public: + /** Default constructor. Sets our value to its default (usually zero), and the operator to OP_EQUAL_TO. */ + NumericQueryFilter() : _value(), _mask(), _op(OP_EQUAL_TO), _maskOp(NQF_MASK_OP_NONE), _assumeDefault(false) {/* empty */} + + /** Constructor. This constructor will create a QueryFilter that only returns true from Match() + * If the matched Message has the field item with the specified value in it. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The value to compare to the value found in the Message. + * @param index Optional index of the item within the field. Defaults to zero. + */ + NumericQueryFilter(const String & fieldName, uint8 op, DataType value, uint32 index = 0) : ValueQueryFilter(fieldName, index), _value(value), _mask(), _op(op), _maskOp(NQF_MASK_OP_NONE), _assumeDefault(false) {/* empty */} + + /** Constructor. This constructor is similar to the constructor shown above, + * except that when this constructor is used, if the specified item does not exist in + * the matched Message, this QueryFilter will act as if the Message contained the + * specified assumedValue. This is useful when the Message has been encoded with + * the expectation that missing fields should be assumed equivalent to well known + * default values. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The value to compare to the value found in the Message. + * @param index Index of the item within the field. Defaults to zero. + * @param assumedValue The value to pretend that the Message contained, if we don't find our value in the Message. + */ + NumericQueryFilter(const String & fieldName, uint8 op, DataType value, uint32 index, const DataType & assumedValue) : ValueQueryFilter(fieldName, index), _value(value), _mask(), _op(op), _maskOp(NQF_MASK_OP_NONE), _assumeDefault(true), _default(assumedValue) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const + { + return ((ValueQueryFilter::SaveToArchive(archive) == B_NO_ERROR)&& + (archive.CAddInt8("op", _op) == B_NO_ERROR)&& + (archive.CAddInt8("mop", _maskOp) == B_NO_ERROR)&& + (archive.AddData("val", DataTypeCode, &_value, sizeof(_value)) == B_NO_ERROR)&& + (archive.AddData("msk", DataTypeCode, &_mask, sizeof(_mask)) == B_NO_ERROR)&& + ((_assumeDefault == false)||(archive.AddData("val", DataTypeCode, &_default, sizeof(_default)) == B_NO_ERROR))) ? B_NO_ERROR : B_ERROR; + } + + virtual status_t SetFromArchive(const Message & archive) + { + _assumeDefault = false; + + const void * dt; + uint32 numBytes; + if ((ValueQueryFilter::SetFromArchive(archive) == B_NO_ERROR)&&(archive.FindData("val", DataTypeCode, &dt, &numBytes) == B_NO_ERROR)&&(numBytes == sizeof(_value))) + { + _op = archive.GetInt8("op"); + _value = *((DataType *)dt); + _maskOp = archive.GetInt8("mop"); + _mask = ((archive.FindData("msk", DataTypeCode, &dt, &numBytes) == B_NO_ERROR)&&(numBytes == sizeof(_mask))) ? *((DataType *)dt) : DataType(); + + if (archive.FindData("val", DataTypeCode, 1, &dt, &numBytes) == B_NO_ERROR) + { + _assumeDefault = true; + _default = *((DataType *)dt); + } + return B_NO_ERROR; + } + return B_ERROR; + } + + virtual uint32 TypeCode() const {return ClassTypeCode;} + + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const + { + (void) optNode; // shut compiler and DOxygen up + + const DataType * valueInMsg; + + const void * p; + if (msg()->FindData(GetFieldName(), DataTypeCode, GetIndex(), &p, NULL) == B_NO_ERROR) valueInMsg = (const DataType *)p; + else if (_assumeDefault) valueInMsg = &_default; + else return false; + + return (_maskOp == NQF_MASK_OP_NONE) ? MatchesAux(*valueInMsg) : MatchesAux(NQFDoMaskOp(_maskOp, *valueInMsg, _mask)); + } + + /** Set the operator to use. + * @param op One of the OP_* values enumerated below. + */ + void SetOperator(uint8 op) {_op = op;} + + /** Returns the currently specified operator, as specified in the constructor or in SetOperator() */ + uint8 GetOperator() const {return _op;} + + /** Set the value to compare against. + * @param value The new value. + */ + void SetValue(DataType value) {_value = value;} + + /** Returns the currently specified value, as specified in the constructor or in SetValue() */ + DataType GetValue() const {return _value;} + + /** Operators defined for our expressions */ + enum { + OP_EQUAL_TO = 0, /**< This operator represents '==' */ + OP_LESS_THAN, /**< This operator represents '<' */ + OP_GREATER_THAN, /**< This operator represents '>' */ + OP_LESS_THAN_OR_EQUAL_TO, /**< This operator represents '<=' */ + OP_GREATER_THAN_OR_EQUAL_TO, /**< This operator represents '>=' */ + OP_NOT_EQUAL_TO, /**< This operator represents '!=' */ + NUM_NUMERIC_OPERATORS /**< This is a guard value */ + }; + + /** Returns true iff this filter will assume a default value if it can't find an actual value in the Message it tests. */ + bool IsAssumedDefault() const {return _assumeDefault;} + + /** Sets the assumed default value to the specified value. + * @param d The value to match against if we don't find a value in the Message. + */ + void SetAssumedDefault(const DataType & d) {_default = d; _assumeDefault = true;} + + /** Unsets the assumed default. After calling this, our Match() method will simply always + * return false if the specified data item is not found in the Message. + */ + void UnsetAssumedDefault() {_default = DataType(); _assumeDefault = false;} + + /** Sets the mask operation to perform on the discovered data value before applying the OP_* test. + * Note that mask operations are not defined for floats, doubles, Points, or Rects. + * @param maskOp a NQF_MASK_OP_* value. Default value is NQF_MASK_OP_NONE. + * @param maskValue The mask value to apply to the discovered data value, before applying the OP_* test. + */ + void SetMask(uint8 maskOp, const DataType & maskValue) {_maskOp = maskOp, _mask = maskValue;} + + /** Returns this QueryFilter's current NQF_MASK_OP_* setting. */ + uint8 GetMaskOp() const {return _maskOp;} + + /** Returns this QueryFilter's current mask value. */ + uint8 GetMaskValue() const {return _mask;} + +private: + bool MatchesAux(const DataType & valueInMsg) const + { + switch(_op) + { + case OP_EQUAL_TO: return (valueInMsg == _value); + case OP_LESS_THAN: return (valueInMsg < _value); + case OP_GREATER_THAN: return (valueInMsg > _value); + case OP_LESS_THAN_OR_EQUAL_TO: return (valueInMsg <= _value); + case OP_GREATER_THAN_OR_EQUAL_TO: return (valueInMsg >= _value); + case OP_NOT_EQUAL_TO: return (valueInMsg != _value); + default: /* do nothing */ break; + } + return false; + } + + DataType _value; + DataType _mask; + uint8 _op, _maskOp; + + bool _assumeDefault; + DataType _default; +}; + +typedef NumericQueryFilter BoolQueryFilter; +typedef NumericQueryFilter DoubleQueryFilter; +typedef NumericQueryFilter FloatQueryFilter; +typedef NumericQueryFilter Int64QueryFilter; +typedef NumericQueryFilter Int32QueryFilter; +typedef NumericQueryFilter Int16QueryFilter; +typedef NumericQueryFilter Int8QueryFilter; +typedef NumericQueryFilter PointQueryFilter; +typedef NumericQueryFilter RectQueryFilter; + +/** A semi-abstract base class for any QueryFilter that holds a list of references to child filters. */ +class MultiQueryFilter : public QueryFilter +{ +public: + /** Default constructor. */ + MultiQueryFilter() {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + + /** Returns a read-only reference to our Queue of child ConstQueryFilterRefs. */ + const Queue & GetChildren() const {return _children;} + + /** Returns a read/write reference to our Queue of child ConstQueryFilterRefs. */ + Queue & GetChildren() {return _children;} + +private: + Queue _children; +}; + +/** This class matches iff at least (n) of its children match. As such, it can be used as an OR operator, + * an AND operator, or something in-between the two. + */ +class AndOrQueryFilter : public MultiQueryFilter +{ +public: + /** Default constructor. Creates an AND filter with no children. + * @param minMatches The minimum number of children that must match before this filter considers + * the match to be valid. Default to MUSCLE_NO_LIMIT, meaning all children must match. + */ + AndOrQueryFilter(uint32 minMatches = MUSCLE_NO_LIMIT) : _minMatches(minMatches) {/* empty */} + + /** Convenience constructor. Note that you usually want to manually add at least one more child as well. + * @param isAnd If true, the operation will be an 'and' operation. Otherwise it will be an 'or' operation. + * @param child First argument to the operation + */ + AndOrQueryFilter(bool isAnd, const ConstQueryFilterRef & child) : _minMatches(isAnd ? MUSCLE_NO_LIMIT : 1) + { + GetChildren().AddTail(child); + } + + /** Convenience constructor for simple binary 'or' or 'and' operations. + * @param isAnd If true, the operation will be an 'and' operation. Otherwise it will be an 'or' operation. + * @param child1 First argument to the operation + * @param child2 Second argument to the operation + */ + AndOrQueryFilter(bool isAnd, const ConstQueryFilterRef & child1, const ConstQueryFilterRef & child2) : _minMatches(isAnd ? MUSCLE_NO_LIMIT : 1) + { + GetChildren().AddTail(child1); + GetChildren().AddTail(child2); + } + + /** Convenience constructor for simple ternary 'or' or 'and' operations. + * @param isAnd If true, the operation will be an 'and' operation. Otherwise it will be an 'or' operation. + * @param child1 First argument to the operation + * @param child2 Second argument to the operation + * @param child3 Third argument to the operation + */ + AndOrQueryFilter(bool isAnd, const ConstQueryFilterRef & child1, const ConstQueryFilterRef & child2, const ConstQueryFilterRef & child3) : _minMatches(isAnd ? MUSCLE_NO_LIMIT : 1) + { + GetChildren().AddTail(child1); + GetChildren().AddTail(child2); + GetChildren().AddTail(child3); + } + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_ANDOR;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; + + /** Set the minimum number of children that must match the target Message in order for this + * filter to match the target Message. If the specified number is greater than the number of + * child filters held by this QueryFilter, then this filter matches only if every one of the child + * filters match. + * @param minMatches How many child filters must match, at a minimum. + */ + void SetMinMatchCount(uint32 minMatches) {_minMatches = minMatches;} + + /** Returns the minimum-match-count for this filter, as specified in the constructor or by SetMinMatchCount(). */ + uint32 GetMinMatchCount() const {return _minMatches;} + +private: + uint32 _minMatches; +}; + +/** This class matches iff at most (n) of its children match. As such, it can be used as a NAND operator, + * a NOT operator, or something in-between the two. + */ +class NandNotQueryFilter : public MultiQueryFilter +{ +public: + /** Default constructor. Creates an NAND filter with no children. + * @param maxMatches The maximum number of children that may match before this filter considers + * the match to be invalid. Defaults to 0, meaning no children may match. + */ + NandNotQueryFilter(uint32 maxMatches = 0) : _maxMatches(maxMatches) {/* empty */} + + /** Convenience constructor for simple unary 'not' operation. + * @param child Child whose logic we should negate. This child is added to our child list, and the MaxMatchCount is set to zero. + */ + NandNotQueryFilter(const ConstQueryFilterRef & child) : _maxMatches(0) + { + GetChildren().AddTail(child); + } + + /** Convenience constructor for simple binary 'nand' operation. MaxMatchCount is set to one. + * @param child1 First argument to the operation + * @param child2 Second argument to the operation + */ + NandNotQueryFilter(const ConstQueryFilterRef & child1, const ConstQueryFilterRef & child2) : _maxMatches(1) + { + GetChildren().AddTail(child1); + GetChildren().AddTail(child2); + } + + /** Convenience constructor for simple ternary 'nand' operation. MaxMatchCount is set to one. + * @param child1 First argument to the operation + * @param child2 Second argument to the operation + * @param child3 Third argument to the operation + */ + NandNotQueryFilter(const ConstQueryFilterRef & child1, const ConstQueryFilterRef & child2, const ConstQueryFilterRef & child3) : _maxMatches(1) + { + GetChildren().AddTail(child1); + GetChildren().AddTail(child2); + GetChildren().AddTail(child3); + } + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_NANDNOT;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; + + /** Set the maximum number of children that may match the target Message in order for this + * filter to match the target Message. If the specified number is greater than the number of + * child filters held by this QueryFilter, then this filter fails to match only if every one + * of the child filters match. + * @param maxMatches Maximum number of child filters that may match. + */ + void SetMaxMatchCount(uint32 maxMatches) {_maxMatches = maxMatches;} + + /** Returns the maximum-match-count for this filter, as specified in the constructor or by SetMaxMatchCount(). */ + uint32 GetMaxMatchCount() const {return _maxMatches;} + +private: + uint32 _maxMatches; +}; + +/** This class matches only if an odd number of its children match. */ +class XorQueryFilter : public MultiQueryFilter +{ +public: + /** Default constructor. You'll want to add children to this object manually. */ + XorQueryFilter() {/* empty */} + + /** Convenience constructor for simple binary 'xor' operation. + * @param child1 First argument to the operation + * @param child2 Second argument to the operation + */ + XorQueryFilter(const ConstQueryFilterRef & child1, const ConstQueryFilterRef & child2) + { + GetChildren().AddTail(child1); + GetChildren().AddTail(child2); + } + + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_XOR;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; +}; + +/** This class matches iff the specified sub-Message exists in our target Message, + * and (optionally) our child ConstQueryFilterRef can match that sub-Message. + */ +class MessageQueryFilter : public ValueQueryFilter +{ +public: + /** Default constructor. The child filter is left NULL, so that any child Message will match */ + MessageQueryFilter() {/* Empty */} + + /** Constructor. + * @param childFilter Reference to the filter to use to match the sub-Message found at (fieldName:index) + * @param fieldName Name of the field in the Message to look at + * @param index Index of the item within the field to look at. Defaults to zero (i.e. the first item) + */ + MessageQueryFilter(const ConstQueryFilterRef & childFilter, const String & fieldName, uint32 index = 0) : ValueQueryFilter(fieldName, index), _childFilter(childFilter) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_MESSAGE;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; + + /** Set the sub-filter to use on the target's sub-Message. + * @param childFilter Filter to use, or a NULL reference to indicate that any sub-Message found should match. + */ + void SetChildFilter(const ConstQueryFilterRef & childFilter) {_childFilter = childFilter;} + + /** Returns our current sub-filter as set in our constructor or in SetChildFilter() */ + ConstQueryFilterRef GetChildFilter() const {return _childFilter;} + +private: + ConstQueryFilterRef _childFilter; +}; + +/** This class matches on string field values. */ +class StringQueryFilter : public ValueQueryFilter +{ +public: + /** Default constructor. The string is set to "", and the operator is set to OP_EQUAL_TO. */ + StringQueryFilter() : _op(OP_EQUAL_TO), _assumeDefault(false), _matcher(NULL) {/* Empty */} + + /** Constructor. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The string to compare to the string found in the Message. + * @param index Optional index of the item within the field. Defaults to zero. + */ + StringQueryFilter(const String & fieldName, uint8 op, const String & value, uint32 index = 0) : ValueQueryFilter(fieldName, index), _value(value), _op(op), _assumeDefault(false), _matcher(NULL) {/* empty */} + + /** Constructor. This constructor is similar to the constructor shown above, + * except that when this constructor is used, if the specified item does not exist in + * the matched Message, this QueryFilter will act as if the Message contained the + * specified assumedValue. This is useful when the Message has been encoded with + * the expectation that missing fields should be assumed equivalent to well known + * default values. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The string to compare to the string found in the Message. + * @param index Optional index of the item within the field. Defaults to zero. + * @param assumedValue The value to pretend that the Message contained, if we don't find our value in the Message. + */ + StringQueryFilter(const String & fieldName, uint8 op, const String & value, uint32 index, const String & assumedValue) : ValueQueryFilter(fieldName, index), _value(value), _op(op), _assumeDefault(true), _default(assumedValue), _matcher(NULL) {/* empty */} + + /** Destructor */ + ~StringQueryFilter() {FreeMatcher();} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_STRING;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; + + /** Set the operator to use. + * @param op One of the OP_* values enumerated below. + */ + void SetOperator(uint8 op) {if (op != _op) {_op = op; FreeMatcher();}} + + /** Returns the currently specified operator, as specified in the constructor or in SetOperator() */ + uint8 GetOperator() const {return _op;} + + /** Set the value to compare against. + * @param value The new value. + */ + void SetValue(const String & value) {if (value != _value) {_value = value; FreeMatcher();}} + + /** Returns the currently specified value, as specified in the constructor or in SetValue() */ + const String & GetValue() const {return _value;} + + enum { + OP_EQUAL_TO = 0, /**< This token represents '==', e.g. nextValue==myValue (case sensitive) */ + OP_LESS_THAN, /**< This token represents '<', e.g. nextValue', e.g. nextValue>myValue (case sensitive) */ + OP_LESS_THAN_OR_EQUAL_TO, /**< This token represents '<=', e.g. nextValue<=myValue (case sensitive) */ + OP_GREATER_THAN_OR_EQUAL_TO, /**< This token represents '>=', e.g. nextValue>=myValue (case sensitive) */ + OP_NOT_EQUAL_TO, /**< This token represents '!=', e.g. nextValue!=myValue (case sensitive) */ + OP_STARTS_WITH, /**< This token represents a prefix match, e.g. nextValue.StartsWith(myValue) (case sensitive) */ + OP_ENDS_WITH, /**< This token represents a suffix match, e.g. nextValue.EndsWith(myValue) (case sensitive) */ + OP_CONTAINS, /**< This token represents an infix match, e.g. (nextValue.IndexOf(myValue)>=0) (case sensitive) */ + OP_START_OF, /**< This token represents an inverse prefix match, e.g. myValue.StartsWith(nextValue) (case sensitive) */ + OP_END_OF, /**< This token represents an inverse suffix match, e.g. myValue.StartsWith(nextValue) (case sensitive) */ + OP_SUBSTRING_OF, /**< This token represents an inverse infix match, e.g. myValue.StartsWith(nextValue) (case sensitive) */ + OP_EQUAL_TO_IGNORECASE, /**< This token is the same as OP_EQUAL_TO, except it is case insensitive */ + OP_LESS_THAN_IGNORECASE, /**< This token is the same as OP_LESS_THAN, except it is case insensitive */ + OP_GREATER_THAN_IGNORECASE, /**< This token is the same as OP_GREATER_THAN, except it is case insensitive */ + OP_LESS_THAN_OR_EQUAL_TO_IGNORECASE, /**< This token is the same as OP_LESS_THAN_OR_EQUAL_TO, except it is case insensitive */ + OP_GREATER_THAN_OR_EQUAL_TO_IGNORECASE, /**< This token is the same as OP_GREATER_THAN_OR_EQUAL_TO, except it is case insensitive */ + OP_NOT_EQUAL_TO_IGNORECASE, /**< This token is the same as OP_GREATER_THAN_OR_EQUAL_TO, except it is case insensitive */ + OP_STARTS_WITH_IGNORECASE, /**< This token is the same as OP_STARTS_WITH, except it is case insensitive */ + OP_ENDS_WITH_IGNORECASE, /**< This token is the same as OP_ENDS_WITH, except it is case insensitive */ + OP_CONTAINS_IGNORECASE, /**< This token is the same as OP_CONTAINS, except it is case insensitive */ + OP_START_OF_IGNORECASE, /**< This token is the same as OP_START_OF, except it is case insensitive */ + OP_END_OF_IGNORECASE, /**< This token is the same as OP_END_OF, except it is case insensitive */ + OP_SUBSTRING_OF_IGNORECASE, /**< This token is the same as OP_SUBSTRING_OF, except it is case insensitive */ + OP_SIMPLE_WILDCARD_MATCH, /**< This token represents a wildcard match, e.g. StringMatcher(myValue, true).Matches(nextValue) */ + OP_REGULAR_EXPRESSION_MATCH, /**< This token represents a proper regex match, e.g. StringMatcher(myValue, false).Matches(nextValue) */ + NUM_STRING_OPERATORS /**< This is a guard token */ + }; + + /** Returns true iff this filter will assume a default value if it can't find an actual value in the Message it tests. */ + bool IsAssumedDefault() const {return _assumeDefault;} + + /** Sets the assumed default value to the specified value. + * @param d The value to match against if we don't find a value in the Message. + */ + void SetAssumedDefault(const String & d) {_default = d; _assumeDefault = true;} + + /** Unsets the assumed default. After calling this, our Match() method will simply always + * return false if the specified data item is not found in the Message. + */ + void UnsetAssumedDefault() {_default.Clear(); _assumeDefault = false;} + +private: + void FreeMatcher(); + bool DoMatch(const String & s) const; + + String _value; + uint8 _op; + + bool _assumeDefault; + String _default; + + mutable StringMatcher * _matcher; +}; + +/** This class matches on raw data buffers. */ +class RawDataQueryFilter : public ValueQueryFilter +{ +public: + /** Default constructor. The byte buffer is set to a zero-length/NULL buffer, and the operator is set to OP_EQUAL_TO. */ + RawDataQueryFilter() : _op(OP_EQUAL_TO) {/* Empty */} + + /** Constructor. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The string to compare to the string found in the Message. + * @param typeCode Typecode to look for in the target Message. Default is B_ANY_TYPE, indicating that any type code is acceptable. + * @param index Optional index of the item within the field. Defaults to zero. + */ + RawDataQueryFilter(const String & fieldName, uint8 op, const ByteBufferRef & value, uint32 typeCode = B_ANY_TYPE, uint32 index = 0) : ValueQueryFilter(fieldName, index), _value(value), _op(op), _typeCode(typeCode) {/* empty */} + + /** Constructor. This constructor is similar to the constructor shown above, + * except that when this constructor is used, if the specified item does not exist in + * the matched Message, this QueryFilter will act as if the Message contained the + * specified assumedValue. This is useful when the Message has been encoded with + * the expectation that missing fields should be assumed equivalent to well known + * default values. + * @param fieldName Field name to look under. + * @param op The operator to use (should be one of the OP_* values enumerated below) + * @param value The string to compare to the string found in the Message. + * @param typeCode Typecode to look for in the target Message. Default is B_ANY_TYPE, indicating that any type code is acceptable. + * @param index Optional index of the item within the field. Defaults to zero. + * @param assumedValue The value to use if no actual value is found at the specified location in the Message we are filtering. + */ + RawDataQueryFilter(const String & fieldName, uint8 op, const ByteBufferRef & value, uint32 typeCode, uint32 index, const ByteBufferRef & assumedValue) : ValueQueryFilter(fieldName, index), _value(value), _op(op), _typeCode(typeCode), _default(assumedValue) {/* empty */} + + virtual status_t SaveToArchive(Message & archive) const; + virtual status_t SetFromArchive(const Message & archive); + virtual uint32 TypeCode() const {return QUERY_FILTER_TYPE_RAWDATA;} + virtual bool Matches(ConstMessageRef & msg, const DataNode * optNode) const; + + /** Set the operator to use. + * @param op One of the OP_* values enumerated below. + */ + void SetOperator(uint8 op) {_op = op;} + + /** Returns the currently specified operator, as specified in the constructor or in SetOperator() */ + uint8 GetOperator() const {return _op;} + + /** Set the value to compare against. + * @param value The new value. + */ + void SetValue(const ByteBufferRef & value) {_value = value;} + + /** Sets the type code that we will look for in the target Message. + * @param typeCode the type code to look for. Use B_ANY_TYPE to indicate that you don't care what the type code is. + */ + void SetTypeCode(uint32 typeCode) {_typeCode = typeCode;} + + /** Returns the type code we are to look in the target Message for. + * Note that this method is different from TypeCode()! + */ + uint32 GetTypeCode() const {return _typeCode;} + + /** Returns the currently specified value, as specified in the constructor or in SetValue() */ + ByteBufferRef GetValue() const {return _value;} + + enum { + OP_EQUAL_TO = 0, /**< This token represents '==' */ + OP_LESS_THAN, /**< This token represents '<' */ + OP_GREATER_THAN, /**< This token represents '>' */ + OP_LESS_THAN_OR_EQUAL_TO, /**< This token represents '<=' */ + OP_GREATER_THAN_OR_EQUAL_TO, /**< This token represents '>=' */ + OP_NOT_EQUAL_TO, /**< This token represents '!=' */ + OP_STARTS_WITH, /**< This token represents a prefix match, e.g. nextValue.StartsWith(myValue) */ + OP_ENDS_WITH, /**< This token represents a suffix match, e.g. nextValue.EndsWith(myValue) */ + OP_CONTAINS, /**< This token represents an infix match, e.g. (nextValue.IndexOf(myValue)>=0) */ + OP_START_OF, /**< This token represents an inverse prefix match, e.g. myValue.StartsWith(nextValue) */ + OP_END_OF, /**< This token represents an inverse suffix match, e.g. myValue.EndsWith(nextValue) */ + OP_SUBSET_OF, /**< This token represents an inverse infix match, e.g. (myValue.IndexOf(nextValue)>=0) */ + NUM_RAWDATA_OPERATORS /**< This is a guard value */ + }; + + /** Call this to specify an assumed default value that should be used when the + * Message we are matching against doesn't have an actual value itself. + * Call this with a NULL reference if you don't want to use an assumed default value. + */ + void SetAssumedDefault(const ByteBufferRef & bufRef) {_default = bufRef;} + + /** Returns the current assumed default value, or a NULL reference if there is none. */ + const ByteBufferRef & GetAssumedDefault() const {return _default;} + +private: + ByteBufferRef _value; + uint8 _op; + uint32 _typeCode; + ByteBufferRef _default; +}; + +/** Interface for any object that knows how to instantiate QueryFilter objects */ +class QueryFilterFactory : public RefCountable +{ +public: + /** Default constructor */ + QueryFilterFactory() {/* empty */} + + /** Destructor */ + virtual ~QueryFilterFactory() {/* empty */} + + /** Attempts to create and return a QueryFilter object from the given typeCode. + * @param typeCode One of the QUERY_FILTER_TYPE_* values enumerated above. + * @returns Reference to the new QueryFilter object on success, or a NULL reference on failure. + */ + virtual QueryFilterRef CreateQueryFilter(uint32 typeCode) const = 0; + + /** Convenience method: Attempts to create, populate, and return a QueryFilter object from + * the given Message, by first calling CreateQueryFilter(msg.what), + * and then calling SetFromArchive(msg) on the return QueryFilter object. + * @param msg A Message object that was previously filled out by the SaveToArchive() method + * of a QueryFilter object. + * @returns Reference to the new QueryFilter object on success, or a NULL reference on failure. + * @note Do not override this method in subclasses; override CreateQueryFilter(uint32) instead. + */ + QueryFilterRef CreateQueryFilter(const Message & msg) const; +}; +DECLARE_REFTYPES(QueryFilterFactory); + +/** This class is MUSCLE's built-in implementation of a QueryFilterFactory. + * It knows how to create all of the filter types listed in the QUERY_FILTER_TYPE_* + * enum above. + */ +class MuscleQueryFilterFactory : public QueryFilterFactory +{ +public: + /** Default ctor */ + MuscleQueryFilterFactory() {/* empty */} + + virtual QueryFilterRef CreateQueryFilter(uint32 typeCode) const; +}; + +/** Returns a reference to the globally installed QueryFilterFactory object + * that is used to create QueryFilter objects. This method is guaranteed + * never to return a NULL reference -- even if you call + * SetglobalQueryFilterFactory(QueryFilterFactoryRef()), this method + * will fall back to returning a reference to a MuscleQueryFilterFactory + * object (which is also what it does by default). + */ +QueryFilterFactoryRef GetGlobalQueryFilterFactory(); + +/** Call this method if you want to install a custom QueryFilterFactory + * object as the global QueryFilterFactory. Calling this method with + * a NULL reference will revert the system back to using the default + * MuscleQueryFilterFactory object. + */ +void SetGlobalQueryFilterFactory(const QueryFilterFactoryRef & newFactory); + +}; // end namespace muscle + +#endif diff --git a/regex/SegmentedStringMatcher.cpp b/regex/SegmentedStringMatcher.cpp new file mode 100644 index 00000000..b273eb12 --- /dev/null +++ b/regex/SegmentedStringMatcher.cpp @@ -0,0 +1,92 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "regex/SegmentedStringMatcher.h" +#include "util/StringTokenizer.h" + +namespace muscle { + +static SegmentedStringMatcherRef::ItemPool _segmentedStringMatcherPool; +SegmentedStringMatcherRef::ItemPool * GetSegmentedStringMatcherPool() {return &_segmentedStringMatcherPool;} + +SegmentedStringMatcherRef GetSegmentedStringMatcherFromPool() {return SegmentedStringMatcherRef(_segmentedStringMatcherPool.ObtainObject());} +SegmentedStringMatcherRef GetSegmentedStringMatcherFromPool(const String & matchString, bool isSimpleFormat, const char * sc) +{ + SegmentedStringMatcherRef ret(_segmentedStringMatcherPool.ObtainObject()); + if ((ret())&&(ret()->SetPattern(matchString, isSimpleFormat, sc) != B_NO_ERROR)) ret.Reset(); + return ret; +} + +SegmentedStringMatcher::SegmentedStringMatcher() : _negate(false) +{ + // empty +} + +SegmentedStringMatcher :: SegmentedStringMatcher(const String & str, bool simple, const char * sc) : _negate(false) +{ + (void) SetPattern(str, simple, sc); +} + +SegmentedStringMatcher :: ~SegmentedStringMatcher() +{ + // empty +} + +void SegmentedStringMatcher :: Clear() +{ + _negate = false; + _pattern.Clear(); + _sepChars.Clear(); + _segments.Clear(); +} + +status_t SegmentedStringMatcher::SetPattern(const String & s, bool isSimple, const char * sc) +{ + Clear(); + + StringTokenizer tok(s(), sc); + const char * t; + while((t = tok()) != NULL) + { + if ((isSimple)&&(strcmp(t, "*") == 0)) + { + if (_segments.AddTail(StringMatcherRef()) != B_NO_ERROR) {Clear(); return B_ERROR;} + } + else + { + StringMatcherRef subMatcherRef = GetStringMatcherFromPool(t, isSimple); + if ((subMatcherRef() == NULL)||(_segments.AddTail(subMatcherRef) != B_NO_ERROR)) {Clear(); return B_ERROR;} + } + } + _pattern = s; + _sepChars = sc; + return B_NO_ERROR; +} + +bool SegmentedStringMatcher::MatchAux(const char * const str) const +{ + StringTokenizer tok(str, _sepChars()); + for (uint32 i=0; i<_segments.GetNumItems(); i++) + { + const char * t = tok(); + if (t == NULL) return false; + + const StringMatcher * sm = _segments[i](); + if ((sm)&&(sm->Match(t) == false)) return false; + } + return true; +} + +String SegmentedStringMatcher :: ToString() const +{ + String ret; + for (uint32 i=0; i<_segments.GetNumItems(); i++) + { + if (ret.HasChars()) ret += '/'; + const StringMatcher * sm = _segments[i](); + ret += sm ? sm->ToString() : "*"; + } + return ret; +} + +}; // end namespace muscle diff --git a/regex/SegmentedStringMatcher.h b/regex/SegmentedStringMatcher.h new file mode 100644 index 00000000..a08a60de --- /dev/null +++ b/regex/SegmentedStringMatcher.h @@ -0,0 +1,102 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSegmentedStringMatcher_h +#define MuscleSegmentedStringMatcher_h + +#include "regex/StringMatcher.h" + +namespace muscle { + +/** Similar to a StringMatcher, but this version segments both the wild card expression and + * the paths to be matched against into sections. For example, if the wild card expression + * is "*foo/bar*" and the string to be matched against is "foot/ball", the SegmentedStringMatcher + * will try to match "foo*" against "foot" and then "bar*" against "ball", instead of trying + * to match "*foo/bar*" against "foot/ball". This can be useful when you want to operate + * on segmented paths with level-by-level matching semantics. + */ +class SegmentedStringMatcher : public RefCountable +{ +public: + /** Default Constructor. */ + SegmentedStringMatcher(); + + /** A constructor that sets the given expression. See SetPattern() for argument semantics. */ + SegmentedStringMatcher(const String & matchString, bool isSimpleFormat = true, const char * segmentSeparatorChars = "/"); + + /** Destructor */ + ~SegmentedStringMatcher(); + + /** + * Set a new wildcard pattern or regular expression for this SegmentedStringMatcher to use in future Match() calls. + * @param expression The new globbing pattern or regular expression to match with. It may be segmented using any of the + * separator characters specified in our constructor. + * @param isSimpleFormat If you wish to use the formal regex syntax, instead of the simple syntax, set isSimpleFormat to false. + * @param segmentSeparatorChars The set of characters that denote sub-keys within the key string. This string will be passed to + * our StringTokenizer. Defaults to "/". + * @return B_NO_ERROR on success, B_ERROR on error (e.g. expression wasn't parsable, or out of memory) + */ + status_t SetPattern(const String & expression, bool isSimpleFormat=true, const char * segmentSeparatorChars = "/"); + + /** Returns the pattern String as it was previously passed in to SetPattern() */ + const String & GetPattern() const {return _pattern;} + + /** Returns true iff this SegmentedStringMatcher's pattern specifies exactly one possible string. + * (i.e. the pattern is just plain old text, with no wildcards or other pattern matching logic specified) + */ + bool IsPatternUnique() const; + + /** Returns this SegmentedStringMatcher's separator chars, as passed in to the ctor or SetPattern(). */ + const String & GetSeparatorChars() const {return _sepChars;} + + /** Returns true iff (string) is matched by the current expression. + * @param matchString a string to match against using our current expression. + * @return true iff (matchString) matches, false otherwise. + */ + bool Match(const char * const matchString) const {return _negate ? !MatchAux(matchString) : MatchAux(matchString);} + + /** Convenience method: Same as above, but takes a String object instead of a (const char *). */ + inline bool Match(const String & matchString) const {return Match(matchString());} + + /** If set true, Match() will return the logical opposite of what + * it would otherwise return; e.g. it will return true only when + * the given string doesn't match the pattern. + * Default state is false. Note that this flag is also set by + * SetPattern(..., true), based on whether or not the pattern + * string starts with a tilde. + */ + void SetNegate(bool negate) {_negate = negate;} + + /** Returns the current state of our negate flag. */ + bool IsNegate() const {return _negate;} + + /** Clears this SegmentedStringMatcher to its default state. */ + void Clear(); + + /** Returns a human-readable string representing this StringMatcher, for debugging purposes. */ + String ToString() const; + +private: + bool MatchAux(const char * const matchString) const; + + String _pattern; + String _sepChars; + bool _negate; + Queue _segments; +}; +DECLARE_REFTYPES(SegmentedStringMatcher); + +SegmentedStringMatcherRef::ItemPool * GetSegmentedStringMatcherPool(); + +/** Convenience method. Returns a StringMatcher object from the default StringMatcher pool, + * or a NULL reference on failure (out of memory?) + */ +SegmentedStringMatcherRef GetSegmentedStringMatcherFromPool(); + +/** Convenience method. Obtains a StringMatcher object from the default StringMatcher pool, calls SetPattern() on it + * with the given arguments, and returns it, or a NULL reference on failure (out of memory, or a parse error?) + */ +SegmentedStringMatcherRef GetSegmentedStringMatcherFromPool(const String & matchString, bool isSimpleFormat = true, const char * segmentSeparatorChars = "/"); + +}; // end namespace muscle + +#endif diff --git a/regex/StringMatcher.cpp b/regex/StringMatcher.cpp new file mode 100644 index 00000000..8f26c227 --- /dev/null +++ b/regex/StringMatcher.cpp @@ -0,0 +1,315 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "regex/StringMatcher.h" +#include "util/String.h" +#include "util/StringTokenizer.h" + +namespace muscle { + +static StringMatcherRef::ItemPool _stringMatcherPool; +StringMatcherRef::ItemPool * GetStringMatcherPool() {return &_stringMatcherPool;} + +StringMatcherRef GetStringMatcherFromPool() {return StringMatcherRef(_stringMatcherPool.ObtainObject());} + +StringMatcherRef GetStringMatcherFromPool(const String & matchString, bool isSimpleFormat) +{ + StringMatcherRef ret(_stringMatcherPool.ObtainObject()); + if ((ret())&&(ret()->SetPattern(matchString, isSimpleFormat) != B_NO_ERROR)) ret.Reset(); + return ret; +} + +StringMatcher::StringMatcher() : _bits(0) +{ + // empty +} + +StringMatcher :: StringMatcher(const String & str, bool simple) : _bits(0) +{ + (void) SetPattern(str, simple); +} + +StringMatcher :: StringMatcher(const StringMatcher & rhs) : RefCountable(rhs), _bits(0) +{ + *this = rhs; +} + +StringMatcher :: ~StringMatcher() +{ + Reset(); +} + +void StringMatcher :: Reset() +{ + if (IsBitSet(STRINGMATCHER_BIT_REGEXVALID)) regfree(&_regExp); + _bits = 0; + _ranges.Clear(); + _pattern.Clear(); +} + +StringMatcher & StringMatcher :: operator = (const StringMatcher & rhs) +{ + (void) SetPattern(rhs._pattern, rhs.IsBitSet(STRINGMATCHER_BIT_SIMPLE)); + return *this; +} + +status_t StringMatcher :: SetPattern(const String & s, bool isSimple) +{ + TCHECKPOINT; + + _pattern = s; + SetBit(STRINGMATCHER_BIT_SIMPLE, isSimple); + + SetBit(STRINGMATCHER_BIT_CANMATCHMULTIPLEVALUES, isSimple?CanWildcardStringMatchMultipleValues(_pattern):HasRegexTokens(_pattern)); + + const char * str = _pattern(); + String regexPattern; + _ranges.Clear(); + if (isSimple) + { + // Special case: if the first char is a tilde, ignore it, but set the negate-bit. + if (str[0] == '~') + { + SetBit(STRINGMATCHER_BIT_NEGATE, true); + str++; + } + else SetBit(STRINGMATCHER_BIT_NEGATE, false); + + // Special case for strings of form e.g. "<15-23>", which is interpreted to + // match integers in the range 15-23, inclusive. Yeah, it's an ungraceful + // hack, but also quite useful since regex won't do that in general. + if (str[0] == '<') + { + const char * rBracket = strchr(str+1, '>'); + if ((rBracket)&&(*(rBracket+1)=='\0')) // the right-bracket must be the last char in the string! + { + StringTokenizer clauses(&str[1], ","); + const char * clause; + while((clause=clauses()) != NULL) + { + uint32 min = 0, max = MUSCLE_NO_LIMIT; // defaults to be used in case one side of the dash is missing (e.g. "-36" means <=36, or "36-" means >=36) + + const char * dash = strchr(clause, '-'); + if (dash) + { + String beforeDash; + if (dash>clause) {beforeDash.SetCstr(clause, dash-clause); beforeDash = beforeDash.Trim();} + + String afterDash(dash+1); afterDash = afterDash.Trim(); + + if (beforeDash.HasChars()) min = atoi(beforeDash()); + if (afterDash.HasChars()) max = atoi(afterDash()); + } + else if (clause[0] != '>') min = max = atoi(String(clause).Trim()()); + + _ranges.AddTail(IDRange(min,max)); + } + } + } + + if (_ranges.IsEmpty()) + { + if ((str[0] == '\\')&&(str[1] == '<')) str++; // special case escape of initial < for "\<15-23>" + + regexPattern = "^("; + + bool escapeMode = false; + for (const char * ptr = str; *ptr != '\0'; ptr++) + { + char c = *ptr; + + if (escapeMode) escapeMode = false; + else + { + switch(c) + { + case ',': c = '|'; break; // commas are treated as union-bars + case '.': regexPattern += '\\'; break; // dots are considered literals, so escape those + case '*': regexPattern += '.'; break; // hmmm. + case '?': c = '.'; break; // question marks mean any-single-char + case '\\': escapeMode = true; break; // don't transform the next character! + } + } + regexPattern += c; + } + if (escapeMode) regexPattern += '\\'; // just in case the user left a trailing backslash + regexPattern += ")$"; + } + } + else SetBit(STRINGMATCHER_BIT_NEGATE, false); + + // Free the old regular expression, if any + if (IsBitSet(STRINGMATCHER_BIT_REGEXVALID)) + { + regfree(&_regExp); + SetBit(STRINGMATCHER_BIT_REGEXVALID, false); + } + + // And compile the new one + if (_ranges.IsEmpty()) + { + bool isValid = (regcomp(&_regExp, regexPattern.HasChars() ? regexPattern() : str, REG_EXTENDED) == 0); + SetBit(STRINGMATCHER_BIT_REGEXVALID, isValid); + return isValid ? B_NO_ERROR : B_ERROR; + } + else return B_NO_ERROR; // for range queries, we don't need a valid regex +} + +bool StringMatcher :: Match(const char * const str) const +{ + TCHECKPOINT; + + bool ret = false; // pessimistic default + + if (_ranges.IsEmpty()) + { + if (IsBitSet(STRINGMATCHER_BIT_REGEXVALID)) ret = (regexec(&_regExp, str, 0, NULL, 0) != REG_NOMATCH); + } + else if (muscleInRange(str[0], '0', '9')) + { + uint32 id = (uint32) atoi(str); + for (uint32 i=0; i<_ranges.GetNumItems(); i++) + { + const IDRange & r = _ranges[i]; + if (muscleInRange(id, r.GetMin(), r.GetMax())) {ret = true; break;} + } + } + + return IsBitSet(STRINGMATCHER_BIT_NEGATE) ? (!ret) : ret; +} + +String StringMatcher :: ToString() const +{ + String s; + if (IsBitSet(STRINGMATCHER_BIT_NEGATE)) s = "~"; + + if (_ranges.IsEmpty()) return s+_pattern; + else + { + s += '<'; + for (uint32 i=0; i<_ranges.GetNumItems(); i++) + { + if (i > 0) s += ','; + const IDRange & r = _ranges[i]; + uint32 min = r.GetMin(); + uint32 max = r.GetMax(); + char buf[128]; + if (max > min) sprintf(buf, UINT32_FORMAT_SPEC "-" UINT32_FORMAT_SPEC, min, max); + else sprintf(buf, UINT32_FORMAT_SPEC, min); + s += buf; + } + s += '>'; + return s; + } +} + +bool IsRegexToken(char c, bool isFirstCharInString) +{ + switch(c) + { + // muscle 2.50: fixed to match exactly the chars specified in muscle/regex/regex/regcomp.c + case '[': case ']': case '*': case '?': case '\\': case ',': case '|': case '(': case ')': + case '=': case '^': case '+': case '$': case '{': case '}': case '-': // note: deliberately not including ':' + return true; + + case '<': case '~': // these chars are only special if they are the first character in the string + return isFirstCharInString; + + default: + return false; + } +} + +String EscapeRegexTokens(const String & s, const char * optTokens) +{ + TCHECKPOINT; + + const char * str = s.Cstr(); + + String ret; + bool isFirst = true; + while(*str) + { + if (optTokens ? (strchr(optTokens, *str) != NULL) : IsRegexToken(*str, isFirst)) ret += '\\'; + isFirst = false; + ret += *str; + str++; + } + return ret; +} + +String RemoveEscapeChars(const String & s) +{ + uint32 len = s.Length(); + String ret; (void) ret.Prealloc(len); + bool lastWasEscape = false; + for (uint32 i=0; i= 'A')&&(next <= 'Z')) + { + char buf[5]; + sprintf(buf, "[%c%c]", next, next+('a'-'A')); + ret += buf; + changed = true; + } + else if ((next >= 'a')&&(next <= 'z')) + { + char buf[5]; + sprintf(buf, "[%c%c]", next, next+('A'-'a')); + ret += buf; + changed = true; + } + else ret += next; + } + if (changed) str = ret; + return changed; +} + +}; // end namespace muscle diff --git a/regex/StringMatcher.h b/regex/StringMatcher.h new file mode 100644 index 00000000..4f0ca482 --- /dev/null +++ b/regex/StringMatcher.h @@ -0,0 +1,230 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleStringMatcher_h +#define MuscleStringMatcher_h + +#include +#include "util/Queue.h" +#include "util/RefCount.h" +#include "util/String.h" + +#ifdef __BEOS__ +# if __POWERPC__ +# include "regex/regex/regex.h" // use included regex if system doesn't provide one +# else +# include +# endif +#else +# ifdef WIN32 +# include "regex/regex/regex.h" +# else +# include +# endif +#endif + +namespace muscle { + +/** This class uses the regex library to implement "simple" string matching (similar to filename globbing in bash) as well as full regular expression pattern-matching. */ +class StringMatcher : public RefCountable +{ +public: + /** Default Constructor. */ + StringMatcher(); + + /** A constructor that sets the given expression. See SetPattern() for argument semantics. + * @param expression the pattern string to use in future pattern-matches. + * @param isSimpleFormat if true, a simple globbing syntax is expected in (expression). + * Otherwise, the full regex syntax will be expected. Defaults to true. + */ + StringMatcher(const String & expression, bool isSimpleFormat = true); + + /** Copy constructor */ + StringMatcher(const StringMatcher & rhs); + + /** Destructor */ + ~StringMatcher(); + + /** Equality constructor. */ + bool operator == (const StringMatcher & rhs) const {return ((_bits == rhs._bits)&&(_pattern == rhs._pattern));} + + /** Inequality constructor */ + bool operator != (const StringMatcher & rhs) const {return !(*this == rhs);} + + /** Assignment operator */ + StringMatcher & operator = (const StringMatcher & rhs); + + /** + * Set a new wildcard pattern or regular expression for this StringMatcher to use in future Match() calls. + * + * Simple patterns specified here also have a special case: If you set a pattern of the form + * "", where x and y are ASCII representations of positive integers, then this StringMatcher will only + * match ASCII representations of integers in that range. So "<19-21>" would match "19", "20", and "21" only. + * Also, "<-19>" will match integers less than or equal to 19, and "<21->" will match all integers greater + * than or equal to 21. "<->" will match everything, same as "*". + * + * Note: In v5.84 and above, this syntax has been extended further to allow multiple ranges. For example, + * "<19-21,25,30-50>" would match the IDs 19 through 21, 25, and 30-50, all inclusive. + * + * Also, simple patterns that begin with a tilde (~) will be logically negated, e.g. ~A* will match all + * strings that do NOT begin with the character A. + * + * @param expression The new globbing pattern or regular expression to match with. + * @param isSimpleFormat If you wish to use the formal regex syntax, + * instead of the simple syntax, set isSimpleFormat to false. + * @return B_NO_ERROR on success, B_ERROR on error (e.g. expression wasn't parsable, or out of memory) + */ + status_t SetPattern(const String & expression, bool isSimpleFormat=true); + + /** Returns the pattern String as it was previously passed in to SetPattern() */ + const String & GetPattern() const {return _pattern;} + + /** Returns true iff this StringMatcher's pattern specifies exactly one possible string. + * (i.e. the pattern is just plain old text, with no wildcards or other pattern matching logic specified) + * @note StringMatchers using numeric ranges are never considered unique, because e.g. looking up the + * string "<5>" in a Hashtable would not return a child node whose node-name is "5". + */ + bool IsPatternUnique() const {return ((_ranges.IsEmpty())&&(IsBitSet(STRINGMATCHER_BIT_CANMATCHMULTIPLEVALUES|STRINGMATCHER_BIT_NEGATE) == false));} + + /** Returns true iff (string) is matched by the current expression. + * @param matchString a string to match against using our current expression. + * @return true iff (matchString) matches, false otherwise. + */ + bool Match(const char * const matchString) const; + + /** Convenience method: Same as above, but takes a String object instead of a (const char *). */ + inline bool Match(const String & matchString) const {return Match(matchString());} + + /** If set true, Match() will return the logical opposite of what + * it would otherwise return; e.g. it will return true only when + * the given string doesn't match the pattern. + * Default state is false. Note that this flag is also set by + * SetPattern(..., true), based on whether or not the pattern + * string starts with a tilde. + */ + void SetNegate(bool negate) {SetBit(STRINGMATCHER_BIT_NEGATE, negate);} + + /** Returns the current state of our negate flag. */ + bool IsNegate() const {return IsBitSet(STRINGMATCHER_BIT_NEGATE);} + + /** Returns the true iff our current pattern is of the "simple" variety, or false if it is of the "official regex" variety. */ + bool IsSimple() const {return IsBitSet(STRINGMATCHER_BIT_SIMPLE);} + + /** Resets this StringMatcher to the state it would be in if created with default arguments. */ + void Reset(); + + /** Returns a human-readable string representing this StringMatcher, for debugging purposes. */ + String ToString() const; + + /** Returns a hash code for this StringMatcher */ + inline uint32 HashCode() const {return _pattern.HashCode() + _bits;} + +private: + void SetBit(uint8 bit, bool set) {if (set) _bits |= bit; else _bits &= ~(bit);} + bool IsBitSet(uint8 bit) const {return (_bits & bit) != 0;} + + enum { + STRINGMATCHER_BIT_REGEXVALID = (1<<0), + STRINGMATCHER_BIT_NEGATE = (1<<1), + STRINGMATCHER_BIT_CANMATCHMULTIPLEVALUES = (1<<2), + STRINGMATCHER_BIT_SIMPLE = (1<<3), + }; + + class IDRange + { + public: + IDRange() : _min(0), _max(0) {/* empty */} + IDRange(uint32 min, uint32 max) : _min(muscleMin(min,max)), _max(muscleMax(min,max)) {/* empty */} + + uint32 GetMin() const {return _min;} + uint32 GetMax() const {return _max;} + + private: + uint32 _min; + uint32 _max; + }; + + uint8 _bits; + String _pattern; + regex_t _regExp; + Queue _ranges; +}; +DECLARE_REFTYPES(StringMatcher); + +/** Returns a point to a singleton ObjectPool that can be used + * to minimize the number of StringMatcher allocations and deletions + * by recycling the StringMatcher objects + */ +StringMatcherRef::ItemPool * GetStringMatcherPool(); + +/** Convenience method. Returns a StringMatcher object from the default StringMatcher pool, + * or a NULL reference on failure (out of memory?) + */ +StringMatcherRef GetStringMatcherFromPool(); + +/** Convenience method. Obtains a StringMatcher object from the default StringMatcher pool, calls SetPattern() on it + * with the given arguments, and returns it, or a NULL reference on failure (out of memory, or a parse error?) + */ +StringMatcherRef GetStringMatcherFromPool(const String & matchString, bool isSimpleFormat = true); + +// Some regular expression utility functions + +/** Returns (str), except the returned string has a backslash inserted in front of any char in (str) + * that is "special" to the regex pattern matching. + * @param str The string to check for special regex chars. + * @param optTokens If non-NULL, only characters in this string will be treated as regex tokens + * and escaped. If left as NULL (the default), then the standard set of regex + * tokens will be escaped. + * @returns the modified String with escaped regex chars + */ +String EscapeRegexTokens(const String & str, const char * optTokens = NULL); + +/** This does essentially the opposite of EscapeRegexTokens(): It removes from the string + * and backslashes that are not immediately preceeded by another backslash. + */ +String RemoveEscapeChars(const String & str); + +/** Returns true iff any "special" chars are found in (str). + * @param str The string to check for special regex chars. + * @return True iff any special regex chars were found in (str). + */ +bool HasRegexTokens(const char * str); + +/** As above, but takes a String object instead of a (const char *) */ +inline bool HasRegexTokens(const String & str) {return HasRegexTokens(str());} + +/** Returns true iff the specified wild card pattern string can match more than one possible value-string. + * @param str The pattern string to check + * @return True iff the string might match more than one value-string; false if not. + */ +bool CanWildcardStringMatchMultipleValues(const char * str); + +/** As above, but takes a String object instead of a (const char *) */ +inline bool CanWildcardStringMatchMultipleValues(const String & str) {return CanWildcardStringMatchMultipleValues(str());} + +/** Returns true iff (c) is a regular expression "special" char as far as StringMatchers are concerned. + * @param c an ASCII char + * @param isFirstCharInString true iff (c) is the first char in a pattern string. (important because + * StringMatchers recognize certain chars as special only if they are the first in the string) + * @return true iff (c) is a special regex char. + */ +bool IsRegexToken(char c, bool isFirstCharInString); + +/** Given a regular expression, makes it case insensitive by + * replacing every occurance of a letter with a upper-lower combo, + * e.g. Hello -> [Hh][Ee][Ll][Ll][Oo] + * @param str a string to check for letters, and possibly modify to make case-insensitive + * @return true iff anything was changed, false if no changes were necessary. + */ +bool MakeRegexCaseInsensitive(String & str); + +/** Given a regular expression, returns a case insensitive version. + * Same as MakeRegexCaseInsensitive(), except the value is returned directly. + * @param str a String to modify to make case-insensitive + * @return the modified String. + */ +inline String ToCaseInsensitive(const String & str) {String r = str; MakeRegexCaseInsensitive(r); return r;} + +}; // end namespace muscle + + +#endif diff --git a/regex/regex/COPYRIGHT b/regex/regex/COPYRIGHT new file mode 100644 index 00000000..30c1f7a4 --- /dev/null +++ b/regex/regex/COPYRIGHT @@ -0,0 +1,20 @@ +Copyright 1992, 1993, 1994, 1997 Henry Spencer. All rights reserved. +This software is not subject to any license of the American Telephone +and Telegraph Company or of the Regents of the University of California. + +Permission is granted to anyone to use this software for any purpose on +any computer system, and to alter it and redistribute it, subject +to the following restrictions: + +1. The author is not responsible for the consequences of use of this + software, no matter how awful, even if they arise from flaws in it. + +2. The origin of this software must not be misrepresented, either by + explicit claim or by omission. Since few users ever read sources, + credits must appear in the documentation. + +3. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original software. Since few users + ever read sources, credits must appear in the documentation. + +4. This notice may not be removed or altered. diff --git a/regex/regex/Makefile b/regex/regex/Makefile new file mode 100644 index 00000000..157aa527 --- /dev/null +++ b/regex/regex/Makefile @@ -0,0 +1,130 @@ +# You probably want to take -DREDEBUG out of CFLAGS, and put something like +# -O in, *after* testing (-DREDEBUG strengthens testing by enabling a lot of +# internal assertion checking and some debugging facilities). +# Put -Dconst= in for a pre-ANSI compiler. +# Do not take -DPOSIX_MISTAKE out. +# REGCFLAGS isn't important to you (it's for my use in some special contexts). +CFLAGS=-I. -DPOSIX_MISTAKE -DREDEBUG $(REGCFLAGS) + +# If you have a pre-ANSI compiler, put -o into MKHFLAGS. If you want +# the Berkeley __P macro, put -b in. +MKHFLAGS= + +# Flags for linking but not compiling, if any. +LDFLAGS= + +# Extra libraries for linking, if any. +LIBS= + +# Internal stuff, should not need changing. +OBJPRODN=regcomp.o regexec.o regerror.o regfree.o +OBJS=$(OBJPRODN) split.o debug.o regexmain.o +H=cclass.h cname.h regex2.h utils.h +REGSRC=regcomp.c regerror.c regexec.c regfree.c +ALLSRC=$(REGSRC) engine.c debug.c regexmain.c split.c + +# Stuff that matters only if you're trying to lint the package. +LINTFLAGS=-I. -Dstatic= -Dconst= -DREDEBUG +LINTC=regcomp.c regexec.c regerror.c regfree.c debug.c regexmain.c +JUNKLINT=possible pointer alignment|null effect + +# arrangements to build forward-reference header files +.SUFFIXES: .ih .h +.c.ih: + sh ./mkh $(MKHFLAGS) -p $< >$@ + +default: r + +lib: purge $(OBJPRODN) + rm -f libregex.a + ar crv libregex.a $(OBJPRODN) + +purge: + rm -f *.o + +# stuff to build regex.h +REGEXH=regex.h +REGEXHSRC=regex2.h $(REGSRC) +$(REGEXH): $(REGEXHSRC) mkh + sh ./mkh $(MKHFLAGS) -i _REGEX_H_ $(REGEXHSRC) >regex.tmp + cmp -s regex.tmp regex.h 2>/dev/null || cp regex.tmp regex.h + rm -f regex.tmp + +# dependencies +$(OBJPRODN) debug.o: utils.h regex.h regex2.h +regcomp.o: cclass.h cname.h regcomp.ih +regexec.o: engine.c engine.ih +regerror.o: regerror.ih +debug.o: debug.ih +regexmain.o: regexmain.ih + +# tester +re: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@ + +# regression test +r: re tests + ./re &1 | egrep -v '$(JUNKLINT)' | tee lint + +fullprint: + ti README WHATSNEW notes todo | list + ti *.h | list + list *.c + list regex.3 regex.7 + +print: + ti README WHATSNEW notes todo | list + ti *.h | list + list reg*.c engine.c + + +mf.tmp: Makefile + sed '/^REGEXH=/s/=.*/=regex.h/' Makefile | sed '/#DEL$$/d' >$@ + +DTRH=cclass.h cname.h regex2.h utils.h +PRE=COPYRIGHT README WHATSNEW +POST=mkh regex.3 regex.7 tests $(DTRH) $(ALLSRC) fake/*.[ch] +FILES=$(PRE) Makefile $(POST) +DTR=$(PRE) Makefile=mf.tmp $(POST) +dtr: $(FILES) mf.tmp + makedtr $(DTR) >$@ + rm mf.tmp + +cio: $(FILES) + cio $(FILES) + +rdf: $(FILES) + rcsdiff -c $(FILES) 2>&1 | p + +# various forms of cleanup +tidy: + rm -f junk* core core.* *.core dtr *.tmp lint + +clean: tidy + rm -f *.o *.s *.ih re libregex.a + +# don't do this one unless you know what you're doing +spotless: clean + rm -f mkh regex.h diff --git a/regex/regex/README b/regex/regex/README new file mode 100644 index 00000000..e6ce3734 --- /dev/null +++ b/regex/regex/README @@ -0,0 +1,32 @@ +alpha3.8 release. +Tue Aug 10 15:51:48 EDT 1999 +henry@spsystems.net (formerly henry@zoo.toronto.edu) + +See WHATSNEW for change listing. + +installation notes: +-------- +Read the comments at the beginning of Makefile before running. + +Utils.h contains some things that just might have to be modified on +some systems, as well as a nested include (ugh) of . + +The "fake" directory contains quick-and-dirty fakes for some header +files and routines that old systems may not have. Note also that +-DUSEBCOPY will make utils.h substitute bcopy() for memmove(). + +After that, "make r" will build regcomp.o, regexec.o, regfree.o, +and regerror.o (the actual routines), bundle them together into a test +program, and run regression tests on them. No output is good output. + +"make lib" builds just the .o files for the actual routines (when +you're happy with testing and have adjusted CFLAGS for production), +and puts them together into libregex.a. You can pick up either the +library or *.o ("make lib" makes sure there are no other .o files left +around to confuse things). + +Main.c, debug.c, split.c are used for regression testing but are not part +of the RE routines themselves. + +Regex.h goes in /usr/include. All other .h files are internal only. +-------- diff --git a/regex/regex/WHATSNEW b/regex/regex/WHATSNEW new file mode 100644 index 00000000..12953433 --- /dev/null +++ b/regex/regex/WHATSNEW @@ -0,0 +1,108 @@ +New in alpha3.8: Bug fix for signed/unsigned mixup, found and fixed +by the FreeBSD folks. + +New in alpha3.7: A bit of cleanup aimed at maximizing portability, +possibly at slight cost in efficiency. "ul" suffixes and "unsigned long" +no longer appear, in particular. + +New in alpha3.6: A couple more portability glitches fixed. + +New in alpha3.5: Active development of this code has been stopped -- +I'm working on a complete reimplementation -- but folks have found some +minor portability glitches and the like, hence this release to fix them. +One penalty: slightly reduced compatibility with old compilers, because +the ANSI C `unsigned long' type and `ul' constant suffix are used in a +few places (I could avoid this but it would be considerably more work). + +New in alpha3.4: The complex bug alluded to below has been fixed (in a +slightly kludgey temporary way that may hurt efficiency a bit; this is +another "get it out the door for 4.4" release). The tests at the end of +the tests file have accordingly been uncommented. The primary sign of +the bug was that something like a?b matching ab matched b rather than ab. +(The bug was essentially specific to this exact situation, else it would +have shown up earlier.) + +New in alpha3.3: The definition of word boundaries has been altered +slightly, to more closely match the usual programming notion that "_" +is an alphabetic. Stuff used for pre-ANSI systems is now in a subdir, +and the makefile no longer alludes to it in mysterious ways. The +makefile has generally been cleaned up some. Fixes have been made +(again!) so that the regression test will run without -DREDEBUG, at +the cost of weaker checking. A workaround for a bug in some folks' + has been added. And some more things have been added to +tests, including a couple right at the end which are commented out +because the code currently flunks them (complex bug; fix coming). +Plus the usual minor cleanup. + +New in alpha3.2: Assorted bits of cleanup and portability improvement +(the development base is now a BSDI system using GCC instead of an ancient +Sun system, and the newer compiler exposed some glitches). Fix for a +serious bug that affected REs using many [] (including REG_ICASE REs +because of the way they are implemented), *sometimes*, depending on +memory-allocation patterns. The header-file prototypes no longer name +the parameters, avoiding possible name conflicts. The possibility that +some clot has defined CHAR_MIN as (say) `-128' instead of `(-128)' is +now handled gracefully. "uchar" is no longer used as an internal type +name (too many people have the same idea). Still the same old lousy +performance, alas. + +New in alpha3.1: Basically nothing, this release is just a bookkeeping +convenience. Stay tuned. + +New in alpha3.0: Performance is no better, alas, but some fixes have been +made and some functionality has been added. (This is basically the "get +it out the door in time for 4.4" release.) One bug fix: regfree() didn't +free the main internal structure (how embarrassing). It is now possible +to put NULs in either the RE or the target string, using (resp.) a new +REG_PEND flag and the old REG_STARTEND flag. The REG_NOSPEC flag to +regcomp() makes all characters ordinary, so you can match a literal +string easily (this will become more useful when performance improves!). +There are now primitives to match beginnings and ends of words, although +the syntax is disgusting and so is the implementation. The REG_ATOI +debugging interface has changed a bit. And there has been considerable +internal cleanup of various kinds. + +New in alpha2.3: Split change list out of README, and moved flags notes +into Makefile. Macro-ized the name of regex(7) in regex(3), since it has +to change for 4.4BSD. Cleanup work in engine.c, and some new regression +tests to catch tricky cases thereof. + +New in alpha2.2: Out-of-date manpages updated. Regerror() acquires two +small extensions -- REG_ITOA and REG_ATOI -- which avoid debugging kludges +in my own test program and might be useful to others for similar purposes. +The regression test will now compile (and run) without REDEBUG. The +BRE \$ bug is fixed. Most uses of "uchar" are gone; it's all chars now. +Char/uchar parameters are now written int/unsigned, to avoid possible +portability problems with unpromoted parameters. Some unsigned casts have +been introduced to minimize portability problems with shifting into sign +bits. + +New in alpha2.1: Lots of little stuff, cleanup and fixes. The one big +thing is that regex.h is now generated, using mkh, rather than being +supplied in the distribution; due to circularities in dependencies, +you have to build regex.h explicitly by "make h". The two known bugs +have been fixed (and the regression test now checks for them), as has a +problem with assertions not being suppressed in the absence of REDEBUG. +No performance work yet. + +New in alpha2: Backslash-anything is an ordinary character, not an +error (except, of course, for the handful of backslashed metacharacters +in BREs), which should reduce script breakage. The regression test +checks *where* null strings are supposed to match, and has generally +been tightened up somewhat. Small bug fixes in parameter passing (not +harmful, but technically errors) and some other areas. Debugging +invoked by defining REDEBUG rather than not defining NDEBUG. + +New in alpha+3: full prototyping for internal routines, using a little +helper program, mkh, which extracts prototypes given in stylized comments. +More minor cleanup. Buglet fix: it's CHAR_BIT, not CHAR_BITS. Simple +pre-screening of input when a literal string is known to be part of the +RE; this does wonders for performance. + +New in alpha+2: minor bits of cleanup. Notably, the number "32" for the +word width isn't hardwired into regexec.c any more, the public header +file prototypes the functions if __STDC__ is defined, and some small typos +in the manpages have been fixed. + +New in alpha+1: improvements to the manual pages, and an important +extension, the REG_STARTEND option to regexec(). diff --git a/regex/regex/cclass.h b/regex/regex/cclass.h new file mode 100644 index 00000000..9c2e44b9 --- /dev/null +++ b/regex/regex/cclass.h @@ -0,0 +1,22 @@ +/* character-class table */ +struct cclass { + char *name; + char *chars; + char *multis; +}; + +static struct cclass cclasses[] = { + {"alnum", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", ""}, + {"alpha", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", ""}, + {"blank", " \t", ""}, + {"cntrl", "\007\b\t\n\v\f\r\1\2\3\4\5\6\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177", ""}, + {"digit", "0123456789", ""}, + {"graph", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", ""}, + {"lower", "abcdefghijklmnopqrstuvwxyz", ""}, + {"print", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ", ""}, + {"punct", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", ""}, + {"space", "\t\n\v\f\r ", ""}, + {"upper", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", ""}, + {"xdigit", "0123456789ABCDEFabcdef", ""}, + {NULL, 0, ""} +}; diff --git a/regex/regex/cname.h b/regex/regex/cname.h new file mode 100644 index 00000000..32b15cbb --- /dev/null +++ b/regex/regex/cname.h @@ -0,0 +1,104 @@ +/* character-name table */ +struct cname { + char *name; + char code; +}; + +static struct cname cnames[] = { + {"NUL", '\0'}, + {"SOH", '\001'}, + {"STX", '\002'}, + {"ETX", '\003'}, + {"EOT", '\004'}, + {"ENQ", '\005'}, + {"ACK", '\006'}, + {"BEL", '\007'}, + {"alert", '\007'}, + {"BS", '\010'}, + {"backspace", '\b'}, + {"HT", '\011'}, + {"tab", '\t'}, + {"LF", '\012'}, + {"newline", '\n'}, + {"VT", '\013'}, + {"vertical-tab", '\v'}, + {"FF", '\014'}, + {"form-feed", '\f'}, + {"CR", '\015'}, + {"carriage-return", '\r'}, + {"SO", '\016'}, + {"SI", '\017'}, + {"DLE", '\020'}, + {"DC1", '\021'}, + {"DC2", '\022'}, + {"DC3", '\023'}, + {"DC4", '\024'}, + {"NAK", '\025'}, + {"SYN", '\026'}, + {"ETB", '\027'}, + {"CAN", '\030'}, + {"EM", '\031'}, + {"SUB", '\032'}, + {"ESC", '\033'}, + {"IS4", '\034'}, + {"FS", '\034'}, + {"IS3", '\035'}, + {"GS", '\035'}, + {"IS2", '\036'}, + {"RS", '\036'}, + {"IS1", '\037'}, + {"US", '\037'}, + {"space", ' '}, + {"exclamation-mark", '!'}, + {"quotation-mark", '"'}, + {"number-sign", '#'}, + {"dollar-sign", '$'}, + {"percent-sign", '%'}, + {"ampersand", '&'}, + {"apostrophe", '\''}, + {"left-parenthesis", '('}, + {"right-parenthesis", ')'}, + {"asterisk", '*'}, + {"plus-sign", '+'}, + {"comma", ','}, + {"hyphen", '-'}, + {"hyphen-minus", '-'}, + {"period", '.'}, + {"full-stop", '.'}, + {"slash", '/'}, + {"solidus", '/'}, + {"zero", '0'}, + {"one", '1'}, + {"two", '2'}, + {"three", '3'}, + {"four", '4'}, + {"five", '5'}, + {"six", '6'}, + {"seven", '7'}, + {"eight", '8'}, + {"nine", '9'}, + {"colon", ':'}, + {"semicolon", ';'}, + {"less-than-sign", '<'}, + {"equals-sign", '='}, + {"greater-than-sign", '>'}, + {"question-mark", '?'}, + {"commercial-at", '@'}, + {"left-square-bracket", '['}, + {"backslash", '\\'}, + {"reverse-solidus", '\\'}, + {"right-square-bracket", ']'}, + {"circumflex", '^'}, + {"circumflex-accent", '^'}, + {"underscore", '_'}, + {"low-line", '_'}, + {"grave-accent", '`'}, + {"left-brace", '{'}, + {"left-curly-bracket", '{'}, + {"vertical-line", '|'}, + {"right-brace", '}'}, + {"right-curly-bracket", '}'}, + {"tilde", '~'}, + {"DEL", '\177'}, + {NULL, 0} +}; diff --git a/regex/regex/debug.c b/regex/regex/debug.c new file mode 100644 index 00000000..99ce7da6 --- /dev/null +++ b/regex/regex/debug.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "regex2.h" +#include "debug.ih" + +/* + - regprint - print a regexp for debugging + == void regprint(regex_t *r, FILE *d); + */ +void +regprint(r, d) +regex_t *r; +FILE *d; +{ + register struct re_guts *g = r->re_g; + register int i; + register int c; + register int last; + int nincat[NC]; + + fprintf(d, "%ld states, %d categories", (long)g->nstates, + g->ncategories); + fprintf(d, ", first %ld last %ld", (long)g->firststate, + (long)g->laststate); + if (g->iflags&USEBOL) + fprintf(d, ", USEBOL"); + if (g->iflags&USEEOL) + fprintf(d, ", USEEOL"); + if (g->iflags&BAD) + fprintf(d, ", BAD"); + if (g->nsub > 0) + fprintf(d, ", nsub=%ld", (long)g->nsub); + if (g->must != NULL) + fprintf(d, ", must(%ld) `%*s'", (long)g->mlen, (int)g->mlen, + g->must); + if (g->backrefs) + fprintf(d, ", backrefs"); + if (g->nplus > 0) + fprintf(d, ", nplus %ld", (long)g->nplus); + fprintf(d, "\n"); + s_print(g, d); + for (i = 0; i < g->ncategories; i++) { + nincat[i] = 0; + for (c = CHAR_MIN; c <= CHAR_MAX; c++) + if (g->categories[c] == i) + nincat[i]++; + } + fprintf(d, "cc0#%d", nincat[0]); + for (i = 1; i < g->ncategories; i++) + if (nincat[i] == 1) { + for (c = CHAR_MIN; c <= CHAR_MAX; c++) + if (g->categories[c] == i) + break; + fprintf(d, ", %d=%s", i, regchar(c)); + } + fprintf(d, "\n"); + for (i = 1; i < g->ncategories; i++) + if (nincat[i] != 1) { + fprintf(d, "cc%d\t", i); + last = -1; + for (c = CHAR_MIN; c <= CHAR_MAX+1; c++) /* +1 does flush */ + if (c <= CHAR_MAX && g->categories[c] == i) { + if (last < 0) { + fprintf(d, "%s", regchar(c)); + last = c; + } + } else { + if (last >= 0) { + if (last != c-1) + fprintf(d, "-%s", + regchar(c-1)); + last = -1; + } + } + fprintf(d, "\n"); + } +} + +/* + - s_print - print the strip for debugging + == static void s_print(register struct re_guts *g, FILE *d); + */ +static void +s_print(g, d) +register struct re_guts *g; +FILE *d; +{ + register sop *s; + register cset *cs; + register int i; + register int done = 0; + register sop opnd; + register int col = 0; + register int last; + register sopno offset = 2; +# define GAP() { if (offset % 5 == 0) { \ + if (col > 40) { \ + fprintf(d, "\n\t"); \ + col = 0; \ + } else { \ + fprintf(d, " "); \ + col++; \ + } \ + } else \ + col++; \ + offset++; \ + } + + if (OP(g->strip[0]) != OEND) + fprintf(d, "missing initial OEND!\n"); + for (s = &g->strip[1]; !done; s++) { + opnd = OPND(*s); + switch (OP(*s)) { + case OEND: + fprintf(d, "\n"); + done = 1; + break; + case OCHAR: + if (strchr("\\|()^$.[+*?{}!<> ", (char)opnd) != NULL) + fprintf(d, "\\%c", (char)opnd); + else + fprintf(d, "%s", regchar((char)opnd)); + break; + case OBOL: + fprintf(d, "^"); + break; + case OEOL: + fprintf(d, "$"); + break; + case OBOW: + fprintf(d, "\\{"); + break; + case OEOW: + fprintf(d, "\\}"); + break; + case OANY: + fprintf(d, "."); + break; + case OANYOF: + fprintf(d, "[(%ld)", (long)opnd); + cs = &g->sets[opnd]; + last = -1; + for (i = 0; i < g->csetsize+1; i++) /* +1 flushes */ + if (CHIN(cs, i) && i < g->csetsize) { + if (last < 0) { + fprintf(d, "%s", regchar(i)); + last = i; + } + } else { + if (last >= 0) { + if (last != i-1) + fprintf(d, "-%s", + regchar(i-1)); + last = -1; + } + } + fprintf(d, "]"); + break; + case OBACK_: + fprintf(d, "(\\<%ld>", (long)opnd); + break; + case O_BACK: + fprintf(d, "<%ld>\\)", (long)opnd); + break; + case OPLUS_: + fprintf(d, "(+"); + if (OP(*(s+opnd)) != O_PLUS) + fprintf(d, "<%ld>", (long)opnd); + break; + case O_PLUS: + if (OP(*(s-opnd)) != OPLUS_) + fprintf(d, "<%ld>", (long)opnd); + fprintf(d, "+)"); + break; + case OQUEST_: + fprintf(d, "(?"); + if (OP(*(s+opnd)) != O_QUEST) + fprintf(d, "<%ld>", (long)opnd); + break; + case O_QUEST: + if (OP(*(s-opnd)) != OQUEST_) + fprintf(d, "<%ld>", (long)opnd); + fprintf(d, "?)"); + break; + case OLPAREN: + fprintf(d, "((<%ld>", (long)opnd); + break; + case ORPAREN: + fprintf(d, "<%ld>))", (long)opnd); + break; + case OCH_: + fprintf(d, "<"); + if (OP(*(s+opnd)) != OOR2) + fprintf(d, "<%ld>", (long)opnd); + break; + case OOR1: + if (OP(*(s-opnd)) != OOR1 && OP(*(s-opnd)) != OCH_) + fprintf(d, "<%ld>", (long)opnd); + fprintf(d, "|"); + break; + case OOR2: + fprintf(d, "|"); + if (OP(*(s+opnd)) != OOR2 && OP(*(s+opnd)) != O_CH) + fprintf(d, "<%ld>", (long)opnd); + break; + case O_CH: + if (OP(*(s-opnd)) != OOR1) + fprintf(d, "<%ld>", (long)opnd); + fprintf(d, ">"); + break; + default: + fprintf(d, "!%d(%d)!", OP(*s), opnd); + break; + } + if (!done) + GAP(); + } +} + +/* + - regchar - make a character printable + == static char *regchar(int ch); + */ +static char * /* -> representation */ +regchar(ch) +int ch; +{ + static char buf[10]; + + if (isprint(ch) || ch == ' ') + sprintf(buf, "%c", ch); + else + sprintf(buf, "\\%o", ch); + return(buf); +} diff --git a/regex/regex/debug.ih b/regex/regex/debug.ih new file mode 100644 index 00000000..5f40ff79 --- /dev/null +++ b/regex/regex/debug.ih @@ -0,0 +1,14 @@ +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === debug.c === */ +void regprint(regex_t *r, FILE *d); +static void s_print(register struct re_guts *g, FILE *d); +static char *regchar(int ch); + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ diff --git a/regex/regex/engine.c b/regex/regex/engine.c new file mode 100644 index 00000000..a0919fe8 --- /dev/null +++ b/regex/regex/engine.c @@ -0,0 +1,1019 @@ +/* + * The matching engine and friends. This file is #included by regexec.c + * after suitable #defines of a variety of macros used herein, so that + * different state representations can be used without duplicating masses + * of code. + */ + +#ifdef SNAMES +#define matcher smatcher +#define fast sfast +#define slow sslow +#define dissect sdissect +#define backref sbackref +#define step sstep +#define print sprint +#define at sat +#define match smat +#endif +#ifdef LNAMES +#define matcher lmatcher +#define fast lfast +#define slow lslow +#define dissect ldissect +#define backref lbackref +#define step lstep +#define print lprint +#define at lat +#define match lmat +#endif + +/* another structure passed up and down to avoid zillions of parameters */ +struct match { + struct re_guts *g; + int eflags; + regmatch_t *pmatch; /* [nsub+1] (0 element unused) */ + char *offp; /* offsets work from here */ + char *beginp; /* start of string -- virtual NUL precedes */ + char *endp; /* end of string -- virtual NUL here */ + char *coldp; /* can be no match starting before here */ + char **lastpos; /* [nplus+1] */ + STATEVARS; + states st; /* current states */ + states fresh; /* states for a fresh start */ + states tmp; /* temporary */ + states empty; /* empty set of states */ +}; + +#include "engine.ih" + +#ifdef REDEBUG +#define SP(t, s, c) print(m, t, s, c, stdout) +#define AT(t, p1, p2, s1, s2) at(m, t, p1, p2, s1, s2) +#define NOTE(str) { if (m->eflags®_TRACE) printf("=%s\n", (str)); } +#else +#define SP(t, s, c) /* nothing */ +#define AT(t, p1, p2, s1, s2) /* nothing */ +#define NOTE(s) /* nothing */ +#endif + +/* + - matcher - the actual matching engine + == static int matcher(register struct re_guts *g, char *string, \ + == size_t nmatch, regmatch_t pmatch[], int eflags); + */ +static int /* 0 success, REG_NOMATCH failure */ +matcher(g, string, nmatch, pmatch, eflags) +register struct re_guts *g; +char *string; +size_t nmatch; +regmatch_t pmatch[]; +int eflags; +{ + register char *endp; + unsigned int i; + struct match mv; + register struct match *m = &mv; + register char *dp; + const register sopno gf = g->firststate+1; /* +1 for OEND */ + const register sopno gl = g->laststate; + char *start; + char *stop; + + /* simplify the situation where possible */ + if (g->cflags®_NOSUB) + nmatch = 0; + if (eflags®_STARTEND) { + start = string + pmatch[0].rm_so; + stop = string + pmatch[0].rm_eo; + } else { + start = string; + stop = start + strlen(start); + } + if (stop < start) + return(REG_INVARG); + + /* prescreening; this does wonders for this rather slow code */ + if (g->must != NULL) { + for (dp = start; dp < stop; dp++) + if (*dp == g->must[0] && stop - dp >= g->mlen && + memcmp(dp, g->must, (size_t)g->mlen) == 0) + break; + if (dp == stop) /* we didn't find g->must */ + return(REG_NOMATCH); + } + + /* match struct setup */ + m->g = g; + m->eflags = eflags; + m->pmatch = NULL; + m->lastpos = NULL; + m->offp = string; + m->beginp = start; + m->endp = stop; + STATESETUP(m, 4); + SETUP(m->st); + SETUP(m->fresh); + SETUP(m->tmp); + SETUP(m->empty); + CLEAR(m->empty); + + /* this loop does only one repetition except for backrefs */ + for (;;) { + endp = fast(m, start, stop, gf, gl); + if (endp == NULL) { /* a miss */ + STATETEARDOWN(m); + return(REG_NOMATCH); + } + if (nmatch == 0 && !g->backrefs) + break; /* no further info needed */ + + /* where? */ + assert(m->coldp != NULL); + for (;;) { + NOTE("finding start"); + endp = slow(m, m->coldp, stop, gf, gl); + if (endp != NULL) + break; + assert(m->coldp < m->endp); + m->coldp++; + } + if (nmatch == 1 && !g->backrefs) + break; /* no further info needed */ + + /* oh my, he wants the subexpressions... */ + if (m->pmatch == NULL) + m->pmatch = (regmatch_t *)malloc((m->g->nsub + 1) * + sizeof(regmatch_t)); + if (m->pmatch == NULL) { + STATETEARDOWN(m); + return(REG_ESPACE); + } + for (i = 1; i <= m->g->nsub; i++) + m->pmatch[i].rm_so = m->pmatch[i].rm_eo = -1; + if (!g->backrefs && !(m->eflags®_BACKR)) { + NOTE("dissecting"); + dp = dissect(m, m->coldp, endp, gf, gl); + } else { + if (g->nplus > 0 && m->lastpos == NULL) + m->lastpos = (char **)malloc((g->nplus+1) * + sizeof(char *)); + if (g->nplus > 0 && m->lastpos == NULL) { + free(m->pmatch); + STATETEARDOWN(m); + return(REG_ESPACE); + } + NOTE("backref dissect"); + dp = backref(m, m->coldp, endp, gf, gl, (sopno)0); + } + if (dp != NULL) + break; + + /* uh-oh... we couldn't find a subexpression-level match */ + assert(g->backrefs); /* must be back references doing it */ + assert(g->nplus == 0 || m->lastpos != NULL); + for (;;) { + if (dp != NULL || endp <= m->coldp) + break; /* defeat */ + NOTE("backoff"); + endp = slow(m, m->coldp, endp-1, gf, gl); + if (endp == NULL) + break; /* defeat */ + /* try it on a shorter possibility */ +#ifndef NDEBUG + for (i = 1; i <= m->g->nsub; i++) { + assert(m->pmatch[i].rm_so == -1); + assert(m->pmatch[i].rm_eo == -1); + } +#endif + NOTE("backoff dissect"); + dp = backref(m, m->coldp, endp, gf, gl, (sopno)0); + } + assert(dp == NULL || dp == endp); + if (dp != NULL) /* found a shorter one */ + break; + + /* despite initial appearances, there is no match here */ + NOTE("false alarm"); + start = m->coldp + 1; /* recycle starting later */ + assert(start <= stop); + } + + /* fill in the details if requested */ + if (nmatch > 0) { + pmatch[0].rm_so = m->coldp - m->offp; + pmatch[0].rm_eo = endp - m->offp; + } + if (nmatch > 1) { + assert(m->pmatch != NULL); + for (i = 1; i < nmatch; i++) + if (i <= m->g->nsub) + pmatch[i] = m->pmatch[i]; + else { + pmatch[i].rm_so = -1; + pmatch[i].rm_eo = -1; + } + } + + if (m->pmatch != NULL) + free((char *)m->pmatch); + if (m->lastpos != NULL) + free((char *)m->lastpos); + STATETEARDOWN(m); + return(0); +} + +/* + - dissect - figure out what matched what, no back references + == static char *dissect(register struct match *m, char *start, \ + == char *stop, sopno startst, sopno stopst); + */ +static char * /* == stop (success) always */ +dissect(m, start, stop, startst, stopst) +register struct match *m; +char *start; +char *stop; +sopno startst; +sopno stopst; +{ + register int i; + register sopno ss; /* start sop of current subRE */ + register sopno es; /* end sop of current subRE */ + register char *sp; /* start of string matched by it */ + register char *stp; /* string matched by it cannot pass here */ + register char *rest; /* start of rest of string */ + register char *tail; /* string unmatched by rest of RE */ + register sopno ssub; /* start sop of subsubRE */ + register sopno esub; /* end sop of subsubRE */ + register char *ssp; /* start of string matched by subsubRE */ + register char *sep; /* end of string matched by subsubRE */ + register char *oldssp; /* previous ssp */ + register char *dp; + + AT("diss", start, stop, startst, stopst); + sp = start; + for (ss = startst; ss < stopst; ss = es) { + /* identify end of subRE */ + es = ss; + switch (OP(m->g->strip[es])) { + case OPLUS_: + case OQUEST_: + es += OPND(m->g->strip[es]); + break; + case OCH_: + while (OP(m->g->strip[es]) != O_CH) + es += OPND(m->g->strip[es]); + break; + } + es++; + + /* figure out what it matched */ + switch (OP(m->g->strip[ss])) { + case OEND: + assert(nope); + break; + case OCHAR: + sp++; + break; + case OBOL: + case OEOL: + case OBOW: + case OEOW: + break; + case OANY: + case OANYOF: + sp++; + break; + case OBACK_: + case O_BACK: + assert(nope); + break; + /* cases where length of match is hard to find */ + case OQUEST_: + stp = stop; + for (;;) { + /* how long could this one be? */ + rest = slow(m, sp, stp, ss, es); + assert(rest != NULL); /* it did match */ + /* could the rest match the rest? */ + tail = slow(m, rest, stop, es, stopst); + if (tail == stop) + break; /* yes! */ + /* no -- try a shorter match for this one */ + stp = rest - 1; + assert(stp >= sp); /* it did work */ + } + ssub = ss + 1; + esub = es - 1; + /* did innards match? */ + if (slow(m, sp, rest, ssub, esub) != NULL) { + dp = dissect(m, sp, rest, ssub, esub); + assert(dp == rest); + } else /* no */ + assert(sp == rest); + sp = rest; + break; + case OPLUS_: + stp = stop; + for (;;) { + /* how long could this one be? */ + rest = slow(m, sp, stp, ss, es); + assert(rest != NULL); /* it did match */ + /* could the rest match the rest? */ + tail = slow(m, rest, stop, es, stopst); + if (tail == stop) + break; /* yes! */ + /* no -- try a shorter match for this one */ + stp = rest - 1; + assert(stp >= sp); /* it did work */ + } + ssub = ss + 1; + esub = es - 1; + ssp = sp; + oldssp = ssp; + for (;;) { /* find last match of innards */ + sep = slow(m, ssp, rest, ssub, esub); + if (sep == NULL || sep == ssp) + break; /* failed or matched null */ + oldssp = ssp; /* on to next try */ + ssp = sep; + } + if (sep == NULL) { + /* last successful match */ + sep = ssp; + ssp = oldssp; + } + assert(sep == rest); /* must exhaust substring */ + assert(slow(m, ssp, sep, ssub, esub) == rest); + dp = dissect(m, ssp, sep, ssub, esub); + assert(dp == sep); + sp = rest; + break; + case OCH_: + stp = stop; + for (;;) { + /* how long could this one be? */ + rest = slow(m, sp, stp, ss, es); + assert(rest != NULL); /* it did match */ + /* could the rest match the rest? */ + tail = slow(m, rest, stop, es, stopst); + if (tail == stop) + break; /* yes! */ + /* no -- try a shorter match for this one */ + stp = rest - 1; + assert(stp >= sp); /* it did work */ + } + ssub = ss + 1; + esub = ss + OPND(m->g->strip[ss]) - 1; + assert(OP(m->g->strip[esub]) == OOR1); + for (;;) { /* find first matching branch */ + if (slow(m, sp, rest, ssub, esub) == rest) + break; /* it matched all of it */ + /* that one missed, try next one */ + assert(OP(m->g->strip[esub]) == OOR1); + esub++; + assert(OP(m->g->strip[esub]) == OOR2); + ssub = esub + 1; + esub += OPND(m->g->strip[esub]); + if (OP(m->g->strip[esub]) == OOR2) + esub--; + else + assert(OP(m->g->strip[esub]) == O_CH); + } + dp = dissect(m, sp, rest, ssub, esub); + assert(dp == rest); + sp = rest; + break; + case O_PLUS: + case O_QUEST: + case OOR1: + case OOR2: + case O_CH: + assert(nope); + break; + case OLPAREN: + i = OPND(m->g->strip[ss]); + assert(0 < i && i <= m->g->nsub); + m->pmatch[i].rm_so = sp - m->offp; + break; + case ORPAREN: + i = OPND(m->g->strip[ss]); + assert(0 < i && i <= m->g->nsub); + m->pmatch[i].rm_eo = sp - m->offp; + break; + default: /* uh oh */ + assert(nope); + break; + } + } + + assert(sp == stop); + return(sp); +} + +/* + - backref - figure out what matched what, figuring in back references + == static char *backref(register struct match *m, char *start, \ + == char *stop, sopno startst, sopno stopst, sopno lev); + */ +static char * /* == stop (success) or NULL (failure) */ +backref(m, start, stop, startst, stopst, lev) +register struct match *m; +char *start; +char *stop; +sopno startst; +sopno stopst; +sopno lev; /* PLUS nesting level */ +{ + register int i; + register sopno ss; /* start sop of current subRE */ + register char *sp; /* start of string matched by it */ + register sopno ssub; /* start sop of subsubRE */ + register sopno esub; /* end sop of subsubRE */ + register char *ssp; /* start of string matched by subsubRE */ + register char *dp; + register size_t len; + register int hard; + register sop s; + register regoff_t offsave; + register cset *cs; + + AT("back", start, stop, startst, stopst); + sp = start; + + /* get as far as we can with easy stuff */ + hard = 0; + for (ss = startst; !hard && ss < stopst; ss++) + switch (OP(s = m->g->strip[ss])) { + case OCHAR: + if (sp == stop || *sp++ != (char)OPND(s)) + return(NULL); + break; + case OANY: + if (sp == stop) + return(NULL); + sp++; + break; + case OANYOF: + cs = &m->g->sets[OPND(s)]; + if (sp == stop || !CHIN(cs, *sp++)) + return(NULL); + break; + case OBOL: + if ( (sp == m->beginp && !(m->eflags®_NOTBOL)) || + (sp < m->endp && *(sp-1) == '\n' && + (m->g->cflags®_NEWLINE)) ) + { /* yes */ } + else + return(NULL); + break; + case OEOL: + if ( (sp == m->endp && !(m->eflags®_NOTEOL)) || + (sp < m->endp && *sp == '\n' && + (m->g->cflags®_NEWLINE)) ) + { /* yes */ } + else + return(NULL); + break; + case OBOW: + if (( (sp == m->beginp && !(m->eflags®_NOTBOL)) || + (sp < m->endp && *(sp-1) == '\n' && + (m->g->cflags®_NEWLINE)) || + (sp > m->beginp && + !ISWORD(*(sp-1))) ) && + (sp < m->endp && ISWORD(*sp)) ) + { /* yes */ } + else + return(NULL); + break; + case OEOW: + if (( (sp == m->endp && !(m->eflags®_NOTEOL)) || + (sp < m->endp && *sp == '\n' && + (m->g->cflags®_NEWLINE)) || + (sp < m->endp && !ISWORD(*sp)) ) && + (sp > m->beginp && ISWORD(*(sp-1))) ) + { /* yes */ } + else + return(NULL); + break; + case O_QUEST: + break; + case OOR1: /* matches null but needs to skip */ + ss++; + s = m->g->strip[ss]; + do { + assert(OP(s) == OOR2); + ss += OPND(s); + } while (OP(s = m->g->strip[ss]) != O_CH); + /* note that the ss++ gets us past the O_CH */ + break; + default: /* have to make a choice */ + hard = 1; + break; + } + if (!hard) { /* that was it! */ + if (sp != stop) + return(NULL); + return(sp); + } + ss--; /* adjust for the for's final increment */ + + /* the hard stuff */ + AT("hard", sp, stop, ss, stopst); + s = m->g->strip[ss]; + switch (OP(s)) { + case OBACK_: /* the vilest depths */ + i = OPND(s); + assert(0 < i && i <= m->g->nsub); + if (m->pmatch[i].rm_eo == -1) + return(NULL); + assert(m->pmatch[i].rm_so != -1); + len = m->pmatch[i].rm_eo - m->pmatch[i].rm_so; + assert(stop - m->beginp >= len); + if (sp > stop - len) + return(NULL); /* not enough left to match */ + ssp = m->offp + m->pmatch[i].rm_so; + if (memcmp(sp, ssp, len) != 0) + return(NULL); + while (m->g->strip[ss] != SOP(O_BACK, i)) + ss++; + return(backref(m, sp+len, stop, ss+1, stopst, lev)); + break; + case OQUEST_: /* to null or not */ + dp = backref(m, sp, stop, ss+1, stopst, lev); + if (dp != NULL) + return(dp); /* not */ + return(backref(m, sp, stop, ss+OPND(s)+1, stopst, lev)); + break; + case OPLUS_: + assert(m->lastpos != NULL); + assert(lev+1 <= m->g->nplus); + m->lastpos[lev+1] = sp; + return(backref(m, sp, stop, ss+1, stopst, lev+1)); + break; + case O_PLUS: + if (sp == m->lastpos[lev]) /* last pass matched null */ + return(backref(m, sp, stop, ss+1, stopst, lev-1)); + /* try another pass */ + m->lastpos[lev] = sp; + dp = backref(m, sp, stop, ss-OPND(s)+1, stopst, lev); + if (dp == NULL) + return(backref(m, sp, stop, ss+1, stopst, lev-1)); + else + return(dp); + break; + case OCH_: /* find the right one, if any */ + ssub = ss + 1; + esub = ss + OPND(s) - 1; + assert(OP(m->g->strip[esub]) == OOR1); + for (;;) { /* find first matching branch */ + dp = backref(m, sp, stop, ssub, esub, lev); + if (dp != NULL) + return(dp); + /* that one missed, try next one */ + if (OP(m->g->strip[esub]) == O_CH) + return(NULL); /* there is none */ + esub++; + assert(OP(m->g->strip[esub]) == OOR2); + ssub = esub + 1; + esub += OPND(m->g->strip[esub]); + if (OP(m->g->strip[esub]) == OOR2) + esub--; + else + assert(OP(m->g->strip[esub]) == O_CH); + } + break; + case OLPAREN: /* must undo assignment if rest fails */ + i = OPND(s); + assert(0 < i && i <= m->g->nsub); + offsave = m->pmatch[i].rm_so; + m->pmatch[i].rm_so = sp - m->offp; + dp = backref(m, sp, stop, ss+1, stopst, lev); + if (dp != NULL) + return(dp); + m->pmatch[i].rm_so = offsave; + return(NULL); + break; + case ORPAREN: /* must undo assignment if rest fails */ + i = OPND(s); + assert(0 < i && i <= m->g->nsub); + offsave = m->pmatch[i].rm_eo; + m->pmatch[i].rm_eo = sp - m->offp; + dp = backref(m, sp, stop, ss+1, stopst, lev); + if (dp != NULL) + return(dp); + m->pmatch[i].rm_eo = offsave; + return(NULL); + break; + default: /* uh oh */ + assert(nope); + break; + } + + /* "can't happen" */ + assert(nope); + /* NOTREACHED */ + return((char *)NULL); /* dummy */ +} + +/* + - fast - step through the string at top speed + == static char *fast(register struct match *m, char *start, \ + == char *stop, sopno startst, sopno stopst); + */ +static char * /* where tentative match ended, or NULL */ +fast(m, start, stop, startst, stopst) +register struct match *m; +char *start; +char *stop; +sopno startst; +sopno stopst; +{ + register states st = m->st; + register states fresh = m->fresh; + register states tmp = m->tmp; + register char *p = start; + register int c = (start == m->beginp) ? OUT : *(start-1); + register int lastc; /* previous c */ + register int flagch; + register int i; + register char *coldp; /* last p after which no match was underway */ + + CLEAR(st); + SET1(st, startst); + st = step(m->g, startst, stopst, st, NOTHING, st); + ASSIGN(fresh, st); + SP("start", st, *p); + coldp = NULL; + for (;;) { + /* next character */ + lastc = c; + c = (p == m->endp) ? OUT : *p; + if (EQ(st, fresh)) + coldp = p; + + /* is there an EOL and/or BOL between lastc and c? */ + flagch = '\0'; + i = 0; + if ( (lastc == '\n' && m->g->cflags®_NEWLINE) || + (lastc == OUT && !(m->eflags®_NOTBOL)) ) { + flagch = BOL; + i = m->g->nbol; + } + if ( (c == '\n' && m->g->cflags®_NEWLINE) || + (c == OUT && !(m->eflags®_NOTEOL)) ) { + flagch = (flagch == BOL) ? BOLEOL : EOL; + i += m->g->neol; + } + if (i != 0) { + for (; i > 0; i--) + st = step(m->g, startst, stopst, st, flagch, st); + SP("boleol", st, c); + } + + /* how about a word boundary? */ + if ( (flagch == BOL || (lastc != OUT && !ISWORD(lastc))) && + (c != OUT && ISWORD(c)) ) { + flagch = BOW; + } + if ( (lastc != OUT && ISWORD(lastc)) && + (flagch == EOL || (c != OUT && !ISWORD(c))) ) { + flagch = EOW; + } + if (flagch == BOW || flagch == EOW) { + st = step(m->g, startst, stopst, st, flagch, st); + SP("boweow", st, c); + } + + /* are we done? */ + if (ISSET(st, stopst) || p == stop) + break; /* NOTE BREAK OUT */ + + /* no, we must deal with this character */ + ASSIGN(tmp, st); + ASSIGN(st, fresh); + assert(c != OUT); + st = step(m->g, startst, stopst, tmp, c, st); + SP("aft", st, c); + assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st)); + p++; + } + + assert(coldp != NULL); + m->coldp = coldp; + if (ISSET(st, stopst)) + return(p+1); + else + return(NULL); +} + +/* + - slow - step through the string more deliberately + == static char *slow(register struct match *m, char *start, \ + == char *stop, sopno startst, sopno stopst); + */ +static char * /* where it ended */ +slow(m, start, stop, startst, stopst) +register struct match *m; +char *start; +char *stop; +sopno startst; +sopno stopst; +{ + register states st = m->st; + register states empty = m->empty; + register states tmp = m->tmp; + register char *p = start; + register int c = (start == m->beginp) ? OUT : *(start-1); + register int lastc; /* previous c */ + register int flagch; + register int i; + register char *matchp; /* last p at which a match ended */ + + AT("slow", start, stop, startst, stopst); + CLEAR(st); + SET1(st, startst); + SP("sstart", st, *p); + st = step(m->g, startst, stopst, st, NOTHING, st); + matchp = NULL; + for (;;) { + /* next character */ + lastc = c; + c = (p == m->endp) ? OUT : *p; + + /* is there an EOL and/or BOL between lastc and c? */ + flagch = '\0'; + i = 0; + if ( (lastc == '\n' && m->g->cflags®_NEWLINE) || + (lastc == OUT && !(m->eflags®_NOTBOL)) ) { + flagch = BOL; + i = m->g->nbol; + } + if ( (c == '\n' && m->g->cflags®_NEWLINE) || + (c == OUT && !(m->eflags®_NOTEOL)) ) { + flagch = (flagch == BOL) ? BOLEOL : EOL; + i += m->g->neol; + } + if (i != 0) { + for (; i > 0; i--) + st = step(m->g, startst, stopst, st, flagch, st); + SP("sboleol", st, c); + } + + /* how about a word boundary? */ + if ( (flagch == BOL || (lastc != OUT && !ISWORD(lastc))) && + (c != OUT && ISWORD(c)) ) { + flagch = BOW; + } + if ( (lastc != OUT && ISWORD(lastc)) && + (flagch == EOL || (c != OUT && !ISWORD(c))) ) { + flagch = EOW; + } + if (flagch == BOW || flagch == EOW) { + st = step(m->g, startst, stopst, st, flagch, st); + SP("sboweow", st, c); + } + + /* are we done? */ + if (ISSET(st, stopst)) + matchp = p; + if (EQ(st, empty) || p == stop) + break; /* NOTE BREAK OUT */ + + /* no, we must deal with this character */ + ASSIGN(tmp, st); + ASSIGN(st, empty); + assert(c != OUT); + st = step(m->g, startst, stopst, tmp, c, st); + SP("saft", st, c); + assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st)); + p++; + } + + return(matchp); +} + + +/* + - step - map set of states reachable before char to set reachable after + == static states step(register struct re_guts *g, sopno start, sopno stop, \ + == register states bef, int ch, register states aft); + == #define BOL (OUT+1) + == #define EOL (BOL+1) + == #define BOLEOL (BOL+2) + == #define NOTHING (BOL+3) + == #define BOW (BOL+4) + == #define EOW (BOL+5) + == #define CODEMAX (BOL+5) // highest code used + == #define NONCHAR(c) ((c) > CHAR_MAX) + == #define NNONCHAR (CODEMAX-CHAR_MAX) + */ +static states +step(g, start, stop, bef, ch, aft) +register struct re_guts *g; +sopno start; /* start state within strip */ +sopno stop; /* state after stop state within strip */ +register states bef; /* states reachable before */ +int ch; /* character or NONCHAR code */ +register states aft; /* states already known reachable after */ +{ + register cset *cs; + register sop s; + register sopno pc; + register onestate here; /* note, macros know this name */ + register sopno look; + register long i; + + for (pc = start, INIT(here, pc); pc != stop; pc++, INC(here)) { + s = g->strip[pc]; + switch (OP(s)) { + case OEND: + assert(pc == stop-1); + break; + case OCHAR: + /* only characters can match */ + assert(!NONCHAR(ch) || ch != (char)OPND(s)); + if (ch == (char)OPND(s)) + FWD(aft, bef, 1); + break; + case OBOL: + if (ch == BOL || ch == BOLEOL) + FWD(aft, bef, 1); + break; + case OEOL: + if (ch == EOL || ch == BOLEOL) + FWD(aft, bef, 1); + break; + case OBOW: + if (ch == BOW) + FWD(aft, bef, 1); + break; + case OEOW: + if (ch == EOW) + FWD(aft, bef, 1); + break; + case OANY: + if (!NONCHAR(ch)) + FWD(aft, bef, 1); + break; + case OANYOF: + cs = &g->sets[OPND(s)]; + if (!NONCHAR(ch) && CHIN(cs, ch)) + FWD(aft, bef, 1); + break; + case OBACK_: /* ignored here */ + case O_BACK: + FWD(aft, aft, 1); + break; + case OPLUS_: /* forward, this is just an empty */ + FWD(aft, aft, 1); + break; + case O_PLUS: /* both forward and back */ + FWD(aft, aft, 1); + i = ISSETBACK(aft, OPND(s)); + BACK(aft, aft, OPND(s)); + if (!i && ISSETBACK(aft, OPND(s))) { + /* oho, must reconsider loop body */ + pc -= OPND(s) + 1; + INIT(here, pc); + } + break; + case OQUEST_: /* two branches, both forward */ + FWD(aft, aft, 1); + FWD(aft, aft, OPND(s)); + break; + case O_QUEST: /* just an empty */ + FWD(aft, aft, 1); + break; + case OLPAREN: /* not significant here */ + case ORPAREN: + FWD(aft, aft, 1); + break; + case OCH_: /* mark the first two branches */ + FWD(aft, aft, 1); + assert(OP(g->strip[pc+OPND(s)]) == OOR2); + FWD(aft, aft, OPND(s)); + break; + case OOR1: /* done a branch, find the O_CH */ + if (ISSTATEIN(aft, here)) { + for (look = 1; + OP(s = g->strip[pc+look]) != O_CH; + look += OPND(s)) + assert(OP(s) == OOR2); + FWD(aft, aft, look); + } + break; + case OOR2: /* propagate OCH_'s marking */ + FWD(aft, aft, 1); + if (OP(g->strip[pc+OPND(s)]) != O_CH) { + assert(OP(g->strip[pc+OPND(s)]) == OOR2); + FWD(aft, aft, OPND(s)); + } + break; + case O_CH: /* just empty */ + FWD(aft, aft, 1); + break; + default: /* ooooops... */ + assert(nope); + break; + } + } + + return(aft); +} + +#ifdef REDEBUG +/* + - print - print a set of states + == #ifdef REDEBUG + == static void print(struct match *m, char *caption, states st, \ + == int ch, FILE *d); + == #endif + */ +static void +print(m, caption, st, ch, d) +struct match *m; +char *caption; +states st; +int ch; +FILE *d; +{ + register struct re_guts *g = m->g; + register int i; + register int first = 1; + + if (!(m->eflags®_TRACE)) + return; + + fprintf(d, "%s", caption); + if (ch != '\0') + fprintf(d, " %s", pchar(ch)); + for (i = 0; i < g->nstates; i++) + if (ISSET(st, i)) { + fprintf(d, "%s%d", (first) ? "\t" : ", ", i); + first = 0; + } + fprintf(d, "\n"); +} + +/* + - at - print current situation + == #ifdef REDEBUG + == static void at(struct match *m, char *title, char *start, char *stop, \ + == sopno startst, sopno stopst); + == #endif + */ +static void +at(m, title, start, stop, startst, stopst) +struct match *m; +char *title; +char *start; +char *stop; +sopno startst; +sopno stopst; +{ + if (!(m->eflags®_TRACE)) + return; + + printf("%s %s-", title, pchar(*start)); + printf("%s ", pchar(*stop)); + printf("%ld-%ld\n", (long)startst, (long)stopst); +} + +#ifndef PCHARDONE +#define PCHARDONE /* never again */ +/* + - pchar - make a character printable + == #ifdef REDEBUG + == static char *pchar(int ch); + == #endif + * + * Is this identical to regchar() over in debug.c? Well, yes. But a + * duplicate here avoids having a debugging-capable regexec.o tied to + * a matching debug.o, and this is convenient. It all disappears in + * the non-debug compilation anyway, so it doesn't matter much. + */ +static char * /* -> representation */ +pchar(ch) +int ch; +{ + static char pbuf[10]; + + if (isprint(ch) || ch == ' ') + sprintf(pbuf, "%c", ch); + else + sprintf(pbuf, "\\%o", ch); + return(pbuf); +} +#endif +#endif + +#undef matcher +#undef fast +#undef slow +#undef dissect +#undef backref +#undef step +#undef print +#undef at +#undef match diff --git a/regex/regex/engine.ih b/regex/regex/engine.ih new file mode 100644 index 00000000..cc98334e --- /dev/null +++ b/regex/regex/engine.ih @@ -0,0 +1,35 @@ +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === engine.c === */ +static int matcher(register struct re_guts *g, char *string, size_t nmatch, regmatch_t pmatch[], int eflags); +static char *dissect(register struct match *m, char *start, char *stop, sopno startst, sopno stopst); +static char *backref(register struct match *m, char *start, char *stop, sopno startst, sopno stopst, sopno lev); +static char *fast(register struct match *m, char *start, char *stop, sopno startst, sopno stopst); +static char *slow(register struct match *m, char *start, char *stop, sopno startst, sopno stopst); +static states step(register struct re_guts *g, sopno start, sopno stop, register states bef, int ch, register states aft); +#define BOL (OUT+1) +#define EOL (BOL+1) +#define BOLEOL (BOL+2) +#define NOTHING (BOL+3) +#define BOW (BOL+4) +#define EOW (BOL+5) +#define CODEMAX (BOL+5) /* highest code used */ +#define NONCHAR(c) ((c) > CHAR_MAX) +#define NNONCHAR (CODEMAX-CHAR_MAX) +#ifdef REDEBUG +static void print(struct match *m, char *caption, states st, int ch, FILE *d); +#endif +#ifdef REDEBUG +static void at(struct match *m, char *title, char *start, char *stop, sopno startst, sopno stopst); +#endif +#ifdef REDEBUG +static char *pchar(int ch); +#endif + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ diff --git a/regex/regex/mkh b/regex/regex/mkh new file mode 100644 index 00000000..252b246c --- /dev/null +++ b/regex/regex/mkh @@ -0,0 +1,76 @@ +#! /bin/sh +# mkh - pull headers out of C source +PATH=/bin:/usr/bin ; export PATH + +# egrep pattern to pick out marked lines +egrep='^ =([ ]|$)' + +# Sed program to process marked lines into lines for the header file. +# The markers have already been removed. Two things are done here: removal +# of backslashed newlines, and some fudging of comments. The first is done +# because -o needs to have prototypes on one line to strip them down. +# Getting comments into the output is tricky; we turn C++-style // comments +# into /* */ comments, after altering any existing */'s to avoid trouble. +peel=' /\\$/N + /\\\n[ ]*/s///g + /\/\//s;\*/;* /;g + /\/\//s;//\(.*\);/*\1 */;' + +for a +do + case "$a" in + -o) # old (pre-function-prototype) compiler + # add code to comment out argument lists + peel="$peel + "'/^\([^#\/][^\/]*[a-zA-Z0-9_)]\)(\(.*\))/s;;\1(/*\2*/);' + shift + ;; + -b) # funny Berkeley __P macro + peel="$peel + "'/^\([^#\/][^\/]*[a-zA-Z0-9_)]\)(\(.*\))/s;;\1 __P((\2));' + shift + ;; + -s) # compiler doesn't like `static foo();' + # add code to get rid of the `static' + peel="$peel + "'/^static[ ][^\/]*[a-zA-Z0-9_)](.*)/s;static.;;' + shift + ;; + -p) # private declarations + egrep='^ ==([ ]|$)' + shift + ;; + -i) # wrap in #ifndef, argument is name + ifndef="$2" + shift ; shift + ;; + *) break + ;; + esac +done + +if test " $ifndef" != " " +then + echo "#ifndef $ifndef" + echo "#define $ifndef /* never again */" +fi +echo "/* ========= begin header generated by $0 ========= */" +echo '#ifdef __cplusplus' +echo 'extern "C" {' +echo '#endif' +for f +do + echo + echo "/* === $f === */" + egrep "$egrep" $f | sed 's/^ ==*[ ]//;s/^ ==*$//' | sed "$peel" + echo +done +echo '#ifdef __cplusplus' +echo '}' +echo '#endif' +echo "/* ========= end header generated by $0 ========= */" +if test " $ifndef" != " " +then + echo "#endif" +fi +exit 0 diff --git a/regex/regex/regcomp.c b/regex/regex/regcomp.c new file mode 100644 index 00000000..fe143924 --- /dev/null +++ b/regex/regex/regcomp.c @@ -0,0 +1,1606 @@ +#include +#include +#include +#include +#include +#include +#include "regex.h" + +#include "utils.h" +#include "regex2.h" + +#include "cclass.h" +#include "cname.h" + +/* + * parse structure, passed up and down to avoid global variables and + * other clumsinesses + */ +struct parse { + char *next; /* next character in RE */ + char *end; /* end of string (-> NUL normally) */ + int error; /* has an error been seen? */ + sop *strip; /* malloced strip */ + sopno ssize; /* malloced strip size (allocated) */ + sopno slen; /* malloced strip length (used) */ + int ncsalloc; /* number of csets allocated */ + struct re_guts *g; +# define NPAREN 10 /* we need to remember () 1-9 for back refs */ + sopno pbegin[NPAREN]; /* -> ( ([0] unused) */ + sopno pend[NPAREN]; /* -> ) ([0] unused) */ +}; + +#include "regcomp.ih" + +static char nuls[10]; /* place to point scanner in event of error */ + +/* + * macros for use with parse structure + * BEWARE: these know that the parse structure is named `p' !!! + */ +#define PEEK() (*p->next) +#define PEEK2() (*(p->next+1)) +#define MORE() (p->next < p->end) +#define MORE2() (p->next+1 < p->end) +#define SEE(c) (MORE() && PEEK() == (c)) +#define SEETWO(a, b) (MORE() && MORE2() && PEEK() == (a) && PEEK2() == (b)) +#define EAT(c) ((SEE(c)) ? (NEXT(), 1) : 0) +#define EATTWO(a, b) ((SEETWO(a, b)) ? (NEXT2(), 1) : 0) +#define NEXT() (p->next++) +#define NEXT2() (p->next += 2) +#define NEXTn(n) (p->next += (n)) +#define GETNEXT() (*p->next++) +#define SETERROR(e) seterr(p, (e)) +#define REQUIRE(co, e) ((co) || SETERROR(e)) +#define MUSTSEE(c, e) (REQUIRE(MORE() && PEEK() == (c), e)) +#define MUSTEAT(c, e) (REQUIRE(MORE() && GETNEXT() == (c), e)) +#define MUSTNOTSEE(c, e) (REQUIRE(!MORE() || PEEK() != (c), e)) +#define EMIT(op, sopnd) doemit(p, (sop)(op), (size_t)(sopnd)) +#define INSERT(op, pos) doinsert(p, (sop)(op), HERE()-(pos)+1, pos) +#define AHEAD(pos) dofwd(p, pos, HERE()-(pos)) +#define ASTERN(sop, pos) EMIT(sop, HERE()-pos) +#define HERE() (p->slen) +#define THERE() (p->slen - 1) +#define THERETHERE() (p->slen - 2) +#define DROP(n) (p->slen -= (n)) + +#ifndef NDEBUG +static int never = 0; /* for use in asserts; shuts lint up */ +#else +#define never 0 /* some s have bugs too */ +#endif + +/* + - regcomp - interface for parser and compilation + = extern int regcomp(regex_t *, const char *, int); + = #define REG_BASIC 0000 + = #define REG_EXTENDED 0001 + = #define REG_ICASE 0002 + = #define REG_NOSUB 0004 + = #define REG_NEWLINE 0010 + = #define REG_NOSPEC 0020 + = #define REG_PEND 0040 + = #define REG_DUMP 0200 + */ +REXTERN +int /* 0 success, otherwise REG_something */ +regcomp(preg, pattern, cflags) +regex_t *preg; +const char *pattern; +int cflags; +{ + struct parse pa; + register struct re_guts *g; + register struct parse *p = &pa; + register int i; + register size_t len; +#ifdef REDEBUG +# define GOODFLAGS(f) (f) +#else +# define GOODFLAGS(f) ((f)&~REG_DUMP) +#endif + + cflags = GOODFLAGS(cflags); + if ((cflags®_EXTENDED) && (cflags®_NOSPEC)) + return(REG_INVARG); + + if (cflags®_PEND) { + if (preg->re_endp < pattern) + return(REG_INVARG); + len = preg->re_endp - pattern; + } else + len = strlen((char *)pattern); + + /* do the mallocs early so failure handling is easy */ + g = (struct re_guts *)malloc(sizeof(struct re_guts) + + (NC-1)*sizeof(cat_t)); + if (g == NULL) + return(REG_ESPACE); + p->ssize = len/(size_t)2*(size_t)3 + (size_t)1; /* ugh */ + p->strip = (sop *)malloc(p->ssize * sizeof(sop)); + p->slen = 0; + if (p->strip == NULL) { + free((char *)g); + return(REG_ESPACE); + } + + /* set things up */ + p->g = g; + p->next = (char *)pattern; /* convenience; we do not modify it */ + p->end = p->next + len; + p->error = 0; + p->ncsalloc = 0; + for (i = 0; i < NPAREN; i++) { + p->pbegin[i] = 0; + p->pend[i] = 0; + } + g->csetsize = NC; + g->sets = NULL; + g->setbits = NULL; + g->ncsets = 0; + g->cflags = cflags; + g->iflags = 0; + g->nbol = 0; + g->neol = 0; + g->must = NULL; + g->mlen = 0; + g->nsub = 0; + g->ncategories = 1; /* category 0 is "everything else" */ + g->categories = &g->catspace[-(CHAR_MIN)]; + (void) memset((char *)g->catspace, 0, NC*sizeof(cat_t)); + g->backrefs = 0; + + /* do it */ + EMIT(OEND, 0); + g->firststate = THERE(); + if (cflags®_EXTENDED) + p_ere(p, OUT); + else if (cflags®_NOSPEC) + p_str(p); + else + p_bre(p, OUT, OUT); + EMIT(OEND, 0); + g->laststate = THERE(); + + /* tidy up loose ends and fill things in */ + categorize(p, g); + stripsnug(p, g); + findmust(p, g); + g->nplus = pluscount(p, g); + g->magic = MAGIC2; + preg->re_nsub = g->nsub; + preg->re_g = g; + preg->re_magic = MAGIC1; +#ifndef REDEBUG + /* not debugging, so can't rely on the assert() in regexec() */ + if (g->iflags&BAD) + SETERROR(REG_ASSERT); +#endif + + /* win or lose, we're done */ + if (p->error != 0) /* lose */ + regfree(preg); + return(p->error); +} + +/* + - p_ere - ERE parser top level, concatenation and alternation + == static void p_ere(register struct parse *p, int stop); + */ +static void +p_ere(p, stop) +register struct parse *p; +int stop; /* character this ERE should end at */ +{ + register char c; + register sopno prevback; + register sopno prevfwd; + register sopno conc; + register int first = 1; /* is this the first alternative? */ + + for (;;) { + /* do a bunch of concatenated expressions */ + conc = HERE(); + while (MORE() && (c = PEEK()) != '|' && c != stop) + p_ere_exp(p); + REQUIRE(HERE() != conc, REG_EMPTY); /* require nonempty */ + + if (!EAT('|')) + break; /* NOTE BREAK OUT */ + + if (first) { + INSERT(OCH_, conc); /* offset is wrong */ + prevfwd = conc; + prevback = conc; + first = 0; + } + ASTERN(OOR1, prevback); + prevback = THERE(); + AHEAD(prevfwd); /* fix previous offset */ + prevfwd = HERE(); + EMIT(OOR2, 0); /* offset is very wrong */ + } + + if (!first) { /* tail-end fixups */ + AHEAD(prevfwd); + ASTERN(O_CH, prevback); + } + + assert(!MORE() || SEE(stop)); +} + +/* + - p_ere_exp - parse one subERE, an atom possibly followed by a repetition op + == static void p_ere_exp(register struct parse *p); + */ +static void +p_ere_exp(p) +register struct parse *p; +{ + register char c; + register sopno pos; + register int count; + register int count2; + register sopno subno; + int wascaret = 0; + + assert(MORE()); /* caller should have ensured this */ + c = GETNEXT(); + + pos = HERE(); + switch (c) { + case '(': + REQUIRE(MORE(), REG_EPAREN); + p->g->nsub++; + subno = p->g->nsub; + if (subno < NPAREN) + p->pbegin[subno] = HERE(); + EMIT(OLPAREN, subno); + if (!SEE(')')) + p_ere(p, ')'); + if (subno < NPAREN) { + p->pend[subno] = HERE(); + assert(p->pend[subno] != 0); + } + EMIT(ORPAREN, subno); + MUSTEAT(')', REG_EPAREN); + break; +#ifndef POSIX_MISTAKE + case ')': /* happens only if no current unmatched ( */ + /* + * You may ask, why the ifndef? Because I didn't notice + * this until slightly too late for 1003.2, and none of the + * other 1003.2 regular-expression reviewers noticed it at + * all. So an unmatched ) is legal POSIX, at least until + * we can get it fixed. + */ + SETERROR(REG_EPAREN); + break; +#endif + case '^': + EMIT(OBOL, 0); + p->g->iflags |= USEBOL; + p->g->nbol++; + wascaret = 1; + break; + case '$': + EMIT(OEOL, 0); + p->g->iflags |= USEEOL; + p->g->neol++; + break; + case '|': + SETERROR(REG_EMPTY); + break; + case '*': + case '+': + case '?': + SETERROR(REG_BADRPT); + break; + case '.': + if (p->g->cflags®_NEWLINE) + nonnewline(p); + else + EMIT(OANY, 0); + break; + case '[': + p_bracket(p); + break; + case '\\': + REQUIRE(MORE(), REG_EESCAPE); + c = GETNEXT(); + ordinary(p, c); + break; + case '{': /* okay as ordinary except if digit follows */ + REQUIRE(!MORE() || !isdigit(PEEK()), REG_BADRPT); + /* FALLTHROUGH */ + default: + ordinary(p, c); + break; + } + + if (!MORE()) + return; + c = PEEK(); + /* we call { a repetition if followed by a digit */ + if (!( c == '*' || c == '+' || c == '?' || + (c == '{' && MORE2() && isdigit(PEEK2())) )) + return; /* no repetition, we're done */ + NEXT(); + + REQUIRE(!wascaret, REG_BADRPT); + switch (c) { + case '*': /* implemented as +? */ + /* this case does not require the (y|) trick, noKLUDGE */ + INSERT(OPLUS_, pos); + ASTERN(O_PLUS, pos); + INSERT(OQUEST_, pos); + ASTERN(O_QUEST, pos); + break; + case '+': + INSERT(OPLUS_, pos); + ASTERN(O_PLUS, pos); + break; + case '?': + /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ + INSERT(OCH_, pos); /* offset slightly wrong */ + ASTERN(OOR1, pos); /* this one's right */ + AHEAD(pos); /* fix the OCH_ */ + EMIT(OOR2, 0); /* offset very wrong... */ + AHEAD(THERE()); /* ...so fix it */ + ASTERN(O_CH, THERETHERE()); + break; + case '{': + count = p_count(p); + if (EAT(',')) { + if (isdigit(PEEK())) { + count2 = p_count(p); + REQUIRE(count <= count2, REG_BADBR); + } else /* single number with comma */ + count2 = INFINITY; + } else /* just a single number */ + count2 = count; + repeat(p, pos, count, count2); + if (!EAT('}')) { /* error heuristics */ + while (MORE() && PEEK() != '}') + NEXT(); + REQUIRE(MORE(), REG_EBRACE); + SETERROR(REG_BADBR); + } + break; + } + + if (!MORE()) + return; + c = PEEK(); + if (!( c == '*' || c == '+' || c == '?' || + (c == '{' && MORE2() && isdigit(PEEK2())) ) ) + return; + SETERROR(REG_BADRPT); +} + +/* + - p_str - string (no metacharacters) "parser" + == static void p_str(register struct parse *p); + */ +static void +p_str(p) +register struct parse *p; +{ + REQUIRE(MORE(), REG_EMPTY); + while (MORE()) + ordinary(p, GETNEXT()); +} + +/* + - p_bre - BRE parser top level, anchoring and concatenation + == static void p_bre(register struct parse *p, register int end1, \ + == register int end2); + * Giving end1 as OUT essentially eliminates the end1/end2 check. + * + * This implementation is a bit of a kludge, in that a trailing $ is first + * taken as an ordinary character and then revised to be an anchor. The + * only undesirable side effect is that '$' gets included as a character + * category in such cases. This is fairly harmless; not worth fixing. + * The amount of lookahead needed to avoid this kludge is excessive. + */ +static void +p_bre(p, end1, end2) +register struct parse *p; +register int end1; /* first terminating character */ +register int end2; /* second terminating character */ +{ + register sopno start = HERE(); + register int first = 1; /* first subexpression? */ + register int wasdollar = 0; + + if (EAT('^')) { + EMIT(OBOL, 0); + p->g->iflags |= USEBOL; + p->g->nbol++; + } + while (MORE() && !SEETWO(end1, end2)) { + wasdollar = p_simp_re(p, first); + first = 0; + } + if (wasdollar) { /* oops, that was a trailing anchor */ + DROP(1); + EMIT(OEOL, 0); + p->g->iflags |= USEEOL; + p->g->neol++; + } + + REQUIRE(HERE() != start, REG_EMPTY); /* require nonempty */ +} + +/* + - p_simp_re - parse a simple RE, an atom possibly followed by a repetition + == static int p_simp_re(register struct parse *p, int starordinary); + */ +static int /* was the simple RE an unbackslashed $? */ +p_simp_re(p, starordinary) +register struct parse *p; +int starordinary; /* is a leading * an ordinary character? */ +{ + register int c; + register int count; + register int count2; + register sopno pos; + register int i; + register sopno subno; +# define BACKSL (1<g->cflags®_NEWLINE) + nonnewline(p); + else + EMIT(OANY, 0); + break; + case '[': + p_bracket(p); + break; + case BACKSL|'{': + SETERROR(REG_BADRPT); + break; + case BACKSL|'(': + p->g->nsub++; + subno = p->g->nsub; + if (subno < NPAREN) + p->pbegin[subno] = HERE(); + EMIT(OLPAREN, subno); + /* the MORE here is an error heuristic */ + if (MORE() && !SEETWO('\\', ')')) + p_bre(p, '\\', ')'); + if (subno < NPAREN) { + p->pend[subno] = HERE(); + assert(p->pend[subno] != 0); + } + EMIT(ORPAREN, subno); + REQUIRE(EATTWO('\\', ')'), REG_EPAREN); + break; + case BACKSL|')': /* should not get here -- must be user */ + case BACKSL|'}': + SETERROR(REG_EPAREN); + break; + case BACKSL|'1': + case BACKSL|'2': + case BACKSL|'3': + case BACKSL|'4': + case BACKSL|'5': + case BACKSL|'6': + case BACKSL|'7': + case BACKSL|'8': + case BACKSL|'9': + i = (c&~BACKSL) - '0'; + assert(i < NPAREN); + if (p->pend[i] != 0) { + assert(i <= p->g->nsub); + EMIT(OBACK_, i); + assert(p->pbegin[i] != 0); + assert(OP(p->strip[p->pbegin[i]]) == OLPAREN); + assert(OP(p->strip[p->pend[i]]) == ORPAREN); + (void) dupl(p, p->pbegin[i]+1, p->pend[i]); + EMIT(O_BACK, i); + } else + SETERROR(REG_ESUBREG); + p->g->backrefs = 1; + break; + case '*': + REQUIRE(starordinary, REG_BADRPT); + /* FALLTHROUGH */ + default: + ordinary(p, (char)c); /* takes off BACKSL, if any */ + break; + } + + if (EAT('*')) { /* implemented as +? */ + /* this case does not require the (y|) trick, noKLUDGE */ + INSERT(OPLUS_, pos); + ASTERN(O_PLUS, pos); + INSERT(OQUEST_, pos); + ASTERN(O_QUEST, pos); + } else if (EATTWO('\\', '{')) { + count = p_count(p); + if (EAT(',')) { + if (MORE() && isdigit(PEEK())) { + count2 = p_count(p); + REQUIRE(count <= count2, REG_BADBR); + } else /* single number with comma */ + count2 = INFINITY; + } else /* just a single number */ + count2 = count; + repeat(p, pos, count, count2); + if (!EATTWO('\\', '}')) { /* error heuristics */ + while (MORE() && !SEETWO('\\', '}')) + NEXT(); + REQUIRE(MORE(), REG_EBRACE); + SETERROR(REG_BADBR); + } + } else if (c == (unsigned char)'$') /* $ (but not \$) ends it */ + return(1); + + return(0); +} + +/* + - p_count - parse a repetition count + == static int p_count(register struct parse *p); + */ +static int /* the value */ +p_count(p) +register struct parse *p; +{ + register int count = 0; + register int ndigits = 0; + + while (MORE() && isdigit(PEEK()) && count <= DUPMAX) { + count = count*10 + (GETNEXT() - '0'); + ndigits++; + } + + REQUIRE(ndigits > 0 && count <= DUPMAX, REG_BADBR); + return(count); +} + +/* + - p_bracket - parse a bracketed character list + == static void p_bracket(register struct parse *p); + * + * Note a significant property of this code: if the allocset() did SETERROR, + * no set operations are done. + */ +static void +p_bracket(p) +register struct parse *p; +{ + register cset *cs = allocset(p); + register int invert = 0; + + /* Dept of Truly Sickening Special-Case Kludges */ + if (p->next + 5 < p->end && strncmp(p->next, "[:<:]]", 6) == 0) { + EMIT(OBOW, 0); + NEXTn(6); + return; + } + if (p->next + 5 < p->end && strncmp(p->next, "[:>:]]", 6) == 0) { + EMIT(OEOW, 0); + NEXTn(6); + return; + } + + if (EAT('^')) + invert++; /* make note to invert set at end */ + if (EAT(']')) + CHadd(cs, ']'); + else if (EAT('-')) + CHadd(cs, '-'); + while (MORE() && PEEK() != ']' && !SEETWO('-', ']')) + p_b_term(p, cs); + if (EAT('-')) + CHadd(cs, '-'); + MUSTEAT(']', REG_EBRACK); + + if (p->error != 0) /* don't mess things up further */ + return; + + if (p->g->cflags®_ICASE) { + register int i; + register int ci; + + for (i = p->g->csetsize - 1; i >= 0; i--) + if (CHIN(cs, i) && isalpha(i)) { + ci = othercase(i); + if (ci != i) + CHadd(cs, ci); + } + if (cs->multis != NULL) + mccase(p, cs); + } + if (invert) { + register int i; + + for (i = p->g->csetsize - 1; i >= 0; i--) + if (CHIN(cs, i)) + CHsub(cs, i); + else + CHadd(cs, i); + if (p->g->cflags®_NEWLINE) + CHsub(cs, '\n'); + if (cs->multis != NULL) + mcinvert(p, cs); + } + + assert(cs->multis == NULL); /* xxx */ + + if (nch(p, cs) == 1) { /* optimize singleton sets */ + ordinary(p, firstch(p, cs)); + freeset(p, cs); + } else + EMIT(OANYOF, freezeset(p, cs)); +} + +/* + - p_b_term - parse one term of a bracketed character list + == static void p_b_term(register struct parse *p, register cset *cs); + */ +static void +p_b_term(p, cs) +register struct parse *p; +register cset *cs; +{ + register char c; + register char start, finish; + register int i; + + /* classify what we've got */ + switch ((MORE()) ? PEEK() : '\0') { + case '[': + c = (MORE2()) ? PEEK2() : '\0'; + break; + case '-': + SETERROR(REG_ERANGE); + return; /* NOTE RETURN */ + break; + default: + c = '\0'; + break; + } + + switch (c) { + case ':': /* character class */ + NEXT2(); + REQUIRE(MORE(), REG_EBRACK); + c = PEEK(); + REQUIRE(c != '-' && c != ']', REG_ECTYPE); + p_b_cclass(p, cs); + REQUIRE(MORE(), REG_EBRACK); + REQUIRE(EATTWO(':', ']'), REG_ECTYPE); + break; + case '=': /* equivalence class */ + NEXT2(); + REQUIRE(MORE(), REG_EBRACK); + c = PEEK(); + REQUIRE(c != '-' && c != ']', REG_ECOLLATE); + p_b_eclass(p, cs); + REQUIRE(MORE(), REG_EBRACK); + REQUIRE(EATTWO('=', ']'), REG_ECOLLATE); + break; + default: /* symbol, ordinary character, or range */ +/* xxx revision needed for multichar stuff */ + start = p_b_symbol(p); + if (SEE('-') && MORE2() && PEEK2() != ']') { + /* range */ + NEXT(); + if (EAT('-')) + finish = '-'; + else + finish = p_b_symbol(p); + } else + finish = start; +/* xxx what about signed chars here... */ + REQUIRE(start <= finish, REG_ERANGE); + for (i = start; i <= finish; i++) + CHadd(cs, i); + break; + } +} + +/* + - p_b_cclass - parse a character-class name and deal with it + == static void p_b_cclass(register struct parse *p, register cset *cs); + */ +static void +p_b_cclass(p, cs) +register struct parse *p; +register cset *cs; +{ + register char *sp = p->next; + register struct cclass *cp; + register size_t len; + register char *u; + register char c; + + while (MORE() && isalpha(PEEK())) + NEXT(); + len = p->next - sp; + for (cp = cclasses; cp->name != NULL; cp++) + if (strncmp(cp->name, sp, len) == 0 && cp->name[len] == '\0') + break; + if (cp->name == NULL) { + /* oops, didn't find it */ + SETERROR(REG_ECTYPE); + return; + } + + u = cp->chars; + while ((c = *u++) != '\0') + CHadd(cs, c); + for (u = cp->multis; *u != '\0'; u += strlen(u) + 1) + MCadd(p, cs, u); +} + +/* + - p_b_eclass - parse an equivalence-class name and deal with it + == static void p_b_eclass(register struct parse *p, register cset *cs); + * + * This implementation is incomplete. xxx + */ +static void +p_b_eclass(p, cs) +register struct parse *p; +register cset *cs; +{ + register char c; + + c = p_b_coll_elem(p, '='); + CHadd(cs, c); +} + +/* + - p_b_symbol - parse a character or [..]ed multicharacter collating symbol + == static char p_b_symbol(register struct parse *p); + */ +static char /* value of symbol */ +p_b_symbol(p) +register struct parse *p; +{ + register char value; + + REQUIRE(MORE(), REG_EBRACK); + if (!EATTWO('[', '.')) + return(GETNEXT()); + + /* collating symbol */ + value = p_b_coll_elem(p, '.'); + REQUIRE(EATTWO('.', ']'), REG_ECOLLATE); + return(value); +} + +/* + - p_b_coll_elem - parse a collating-element name and look it up + == static char p_b_coll_elem(register struct parse *p, int endc); + */ +static char /* value of collating element */ +p_b_coll_elem(p, endc) +register struct parse *p; +int endc; /* name ended by endc,']' */ +{ + register char *sp = p->next; + register struct cname *cp; + register int len; + + while (MORE() && !SEETWO(endc, ']')) + NEXT(); + if (!MORE()) { + SETERROR(REG_EBRACK); + return(0); + } + len = p->next - sp; + for (cp = cnames; cp->name != NULL; cp++) + if (strncmp(cp->name, sp, len) == 0 && cp->name[len] == '\0') + return(cp->code); /* known name */ + if (len == 1) + return(*sp); /* single character */ + SETERROR(REG_ECOLLATE); /* neither */ + return(0); +} + +/* + - othercase - return the case counterpart of an alphabetic + == static char othercase(int ch); + */ +static char /* if no counterpart, return ch */ +othercase(ch) +int ch; +{ + assert(isalpha(ch)); + if (isupper(ch)) + return(tolower(ch)); + else if (islower(ch)) + return(toupper(ch)); + else /* peculiar, but could happen */ + return(ch); +} + +/* + - bothcases - emit a dualcase version of a two-case character + == static void bothcases(register struct parse *p, int ch); + * + * Boy, is this implementation ever a kludge... + */ +static void +bothcases(p, ch) +register struct parse *p; +int ch; +{ + register char *oldnext = p->next; + register char *oldend = p->end; + char bracket[3]; + + assert(othercase(ch) != ch); /* p_bracket() would recurse */ + p->next = bracket; + p->end = bracket+2; + bracket[0] = ch; + bracket[1] = ']'; + bracket[2] = '\0'; + p_bracket(p); + assert(p->next == bracket+2); + p->next = oldnext; + p->end = oldend; +} + +/* + - ordinary - emit an ordinary character + == static void ordinary(register struct parse *p, register int ch); + */ +static void +ordinary(p, ch) +register struct parse *p; +register int ch; +{ + register cat_t *cap = p->g->categories; + + if ((p->g->cflags®_ICASE) && isalpha(ch) && othercase(ch) != ch) + bothcases(p, ch); + else { + EMIT(OCHAR, (unsigned char)ch); + if (cap[ch] == 0) + cap[ch] = p->g->ncategories++; + } +} + +/* + - nonnewline - emit REG_NEWLINE version of OANY + == static void nonnewline(register struct parse *p); + * + * Boy, is this implementation ever a kludge... + */ +static void +nonnewline(p) +register struct parse *p; +{ + register char *oldnext = p->next; + register char *oldend = p->end; + char bracket[4]; + + p->next = bracket; + p->end = bracket+3; + bracket[0] = '^'; + bracket[1] = '\n'; + bracket[2] = ']'; + bracket[3] = '\0'; + p_bracket(p); + assert(p->next == bracket+3); + p->next = oldnext; + p->end = oldend; +} + +/* + - repeat - generate code for a bounded repetition, recursively if needed + == static void repeat(register struct parse *p, sopno start, int from, int to); + */ +static void +repeat(p, start, from, to) +register struct parse *p; +sopno start; /* operand from here to end of strip */ +int from; /* repeated from this number */ +int to; /* to this number of times (maybe INFINITY) */ +{ + register sopno finish = HERE(); +# define N 2 +# define INF 3 +# define REP(f, t) ((f)*8 + (t)) +# define MAP(n) (((n) <= 1) ? (n) : ((n) == INFINITY) ? INF : N) + register sopno copy; + + if (p->error != 0) /* head off possible runaway recursion */ + return; + + assert(from <= to); + + switch (REP(MAP(from), MAP(to))) { + case REP(0, 0): /* must be user doing this */ + DROP(finish-start); /* drop the operand */ + break; + case REP(0, 1): /* as x{1,1}? */ + case REP(0, N): /* as x{1,n}? */ + case REP(0, INF): /* as x{1,}? */ + /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ + INSERT(OCH_, start); /* offset is wrong... */ + repeat(p, start+1, 1, to); + ASTERN(OOR1, start); + AHEAD(start); /* ... fix it */ + EMIT(OOR2, 0); + AHEAD(THERE()); + ASTERN(O_CH, THERETHERE()); + break; + case REP(1, 1): /* trivial case */ + /* done */ + break; + case REP(1, N): /* as x?x{1,n-1} */ + /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ + INSERT(OCH_, start); + ASTERN(OOR1, start); + AHEAD(start); + EMIT(OOR2, 0); /* offset very wrong... */ + AHEAD(THERE()); /* ...so fix it */ + ASTERN(O_CH, THERETHERE()); + copy = dupl(p, start+1, finish+1); + assert(copy == finish+4); + repeat(p, copy, 1, to-1); + break; + case REP(1, INF): /* as x+ */ + INSERT(OPLUS_, start); + ASTERN(O_PLUS, start); + break; + case REP(N, N): /* as xx{m-1,n-1} */ + copy = dupl(p, start, finish); + repeat(p, copy, from-1, to-1); + break; + case REP(N, INF): /* as xx{n-1,INF} */ + copy = dupl(p, start, finish); + repeat(p, copy, from-1, to); + break; + default: /* "can't happen" */ + SETERROR(REG_ASSERT); /* just in case */ + break; + } +} + +/* + - seterr - set an error condition + == static int seterr(register struct parse *p, int e); + */ +static int /* useless but makes type checking happy */ +seterr(p, e) +register struct parse *p; +int e; +{ + if (p->error == 0) /* keep earliest error condition */ + p->error = e; + p->next = nuls; /* try to bring things to a halt */ + p->end = nuls; + return(0); /* make the return value well-defined */ +} + +/* + - allocset - allocate a set of characters for [] + == static cset *allocset(register struct parse *p); + */ +static cset * +allocset(p) +register struct parse *p; +{ + register int no = p->g->ncsets++; + register size_t nc; + register size_t nbytes; + register cset *cs; + register size_t css = (size_t)p->g->csetsize; + register int i; + + if (no >= p->ncsalloc) { /* need another column of space */ + p->ncsalloc += CHAR_BIT; + nc = p->ncsalloc; + assert(nc % CHAR_BIT == 0); + nbytes = nc / CHAR_BIT * css; + if (p->g->sets == NULL) + p->g->sets = (cset *)malloc(nc * sizeof(cset)); + else + p->g->sets = (cset *)realloc((char *)p->g->sets, + nc * sizeof(cset)); + if (p->g->setbits == NULL) + p->g->setbits = (uch *)malloc(nbytes); + else { + p->g->setbits = (uch *)realloc((char *)p->g->setbits, + nbytes); + /* xxx this isn't right if setbits is now NULL */ + for (i = 0; i < no; i++) + p->g->sets[i].ptr = p->g->setbits + css*(i/CHAR_BIT); + } + if (p->g->sets != NULL && p->g->setbits != NULL) + (void) memset((char *)p->g->setbits + (nbytes - css), + 0, css); + else { + no = 0; + SETERROR(REG_ESPACE); + /* caller's responsibility not to do set ops */ + } + } + + assert(p->g->sets != NULL); /* xxx */ + cs = &p->g->sets[no]; + cs->ptr = p->g->setbits + css*((no)/CHAR_BIT); + cs->mask = 1 << ((no) % CHAR_BIT); + cs->hash = 0; + cs->smultis = 0; + cs->multis = NULL; + + return(cs); +} + +/* + - freeset - free a now-unused set + == static void freeset(register struct parse *p, register cset *cs); + */ +static void +freeset(p, cs) +register struct parse *p; +register cset *cs; +{ + unsigned int i; + register cset *top = &p->g->sets[p->g->ncsets]; + register size_t css = (size_t)p->g->csetsize; + + for (i = 0; i < css; i++) + CHsub(cs, i); + if (cs == top-1) /* recover only the easy case */ + p->g->ncsets--; +} + +/* + - freezeset - final processing on a set of characters + == static int freezeset(register struct parse *p, register cset *cs); + * + * The main task here is merging identical sets. This is usually a waste + * of time (although the hash code minimizes the overhead), but can win + * big if REG_ICASE is being used. REG_ICASE, by the way, is why the hash + * is done using addition rather than xor -- all ASCII [aA] sets xor to + * the same value! + */ +static int /* set number */ +freezeset(p, cs) +register struct parse *p; +register cset *cs; +{ + register uch h = cs->hash; + unsigned int i; + register cset *top = &p->g->sets[p->g->ncsets]; + register cset *cs2; + register size_t css = (size_t)p->g->csetsize; + + /* look for an earlier one which is the same */ + for (cs2 = &p->g->sets[0]; cs2 < top; cs2++) + if (cs2->hash == h && cs2 != cs) { + /* maybe */ + for (i = 0; i < css; i++) + if (!!CHIN(cs2, i) != !!CHIN(cs, i)) + break; /* no */ + if (i == css) + break; /* yes */ + } + + if (cs2 < top) { /* found one */ + freeset(p, cs); + cs = cs2; + } + + return((int)(cs - p->g->sets)); +} + +/* + - firstch - return first character in a set (which must have at least one) + == static int firstch(register struct parse *p, register cset *cs); + */ +static int /* character; there is no "none" value */ +firstch(p, cs) +register struct parse *p; +register cset *cs; +{ + unsigned int i; + register size_t css = (size_t)p->g->csetsize; + + for (i = 0; i < css; i++) + if (CHIN(cs, i)) + return((char)i); + assert(never); + return(0); /* arbitrary */ +} + +/* + - nch - number of characters in a set + == static int nch(register struct parse *p, register cset *cs); + */ +static int +nch(p, cs) +register struct parse *p; +register cset *cs; +{ + unsigned int i; + register size_t css = (size_t)p->g->csetsize; + register int n = 0; + + for (i = 0; i < css; i++) + if (CHIN(cs, i)) + n++; + return(n); +} + +/* + - mcadd - add a collating element to a cset + == static void mcadd(register struct parse *p, register cset *cs, \ + == register char *cp); + */ +static void +mcadd(p, cs, cp) +register struct parse *p; +register cset *cs; +register char *cp; +{ + register size_t oldend = cs->smultis; + + cs->smultis += strlen(cp) + 1; + if (cs->multis == NULL) + cs->multis = malloc(cs->smultis); + else + cs->multis = realloc(cs->multis, cs->smultis); + if (cs->multis == NULL) { + SETERROR(REG_ESPACE); + return; + } + + (void) strcpy(cs->multis + oldend - 1, cp); + cs->multis[cs->smultis - 1] = '\0'; +} + +/* + - mcsub - subtract a collating element from a cset + == static void mcsub(register cset *cs, register char *cp); + */ +static void +mcsub(cs, cp) +register cset *cs; +register char *cp; +{ + register char *fp = mcfind(cs, cp); + register size_t len = strlen(fp); + + assert(fp != NULL); + (void) memmove(fp, fp + len + 1, + cs->smultis - (fp + len + 1 - cs->multis)); + cs->smultis -= len; + + if (cs->smultis == 0) { + free(cs->multis); + cs->multis = NULL; + return; + } + + cs->multis = realloc(cs->multis, cs->smultis); + assert(cs->multis != NULL); +} + +/* + - mcin - is a collating element in a cset? + == static int mcin(register cset *cs, register char *cp); + */ +static int +mcin(cs, cp) +register cset *cs; +register char *cp; +{ + return(mcfind(cs, cp) != NULL); +} + +/* + - mcfind - find a collating element in a cset + == static char *mcfind(register cset *cs, register char *cp); + */ +static char * +mcfind(cs, cp) +register cset *cs; +register char *cp; +{ + register char *p; + + if (cs->multis == NULL) + return(NULL); + for (p = cs->multis; *p != '\0'; p += strlen(p) + 1) + if (strcmp(cp, p) == 0) + return(p); + return(NULL); +} + +/* + - mcinvert - invert the list of collating elements in a cset + == static void mcinvert(register struct parse *p, register cset *cs); + * + * This would have to know the set of possibilities. Implementation + * is deferred. + */ +static void +mcinvert(p, cs) +register struct parse *p; +register cset *cs; +{ + (void) p; /* avoid compiler warnings */ + (void) cs; /* avoid compiler warnings */ + assert(cs->multis == NULL); /* xxx */ +} + +/* + - mccase - add case counterparts of the list of collating elements in a cset + == static void mccase(register struct parse *p, register cset *cs); + * + * This would have to know the set of possibilities. Implementation + * is deferred. + */ +static void +mccase(p, cs) +register struct parse *p; +register cset *cs; +{ + assert(cs->multis == NULL); /* xxx */ +} + +/* + - isinsets - is this character in any sets? + == static int isinsets(register struct re_guts *g, int c); + */ +static int /* predicate */ +isinsets(g, c) +register struct re_guts *g; +int c; +{ + register uch *col; + register int i; + register int ncols = (g->ncsets+(CHAR_BIT-1)) / CHAR_BIT; + register unsigned uc = (unsigned char)c; + + for (i = 0, col = g->setbits; i < ncols; i++, col += g->csetsize) + if (col[uc] != 0) + return(1); + return(0); +} + +/* + - samesets - are these two characters in exactly the same sets? + == static int samesets(register struct re_guts *g, int c1, int c2); + */ +static int /* predicate */ +samesets(g, c1, c2) +register struct re_guts *g; +int c1; +int c2; +{ + register uch *col; + register int i; + register int ncols = (g->ncsets+(CHAR_BIT-1)) / CHAR_BIT; + register unsigned uc1 = (unsigned char)c1; + register unsigned uc2 = (unsigned char)c2; + + for (i = 0, col = g->setbits; i < ncols; i++, col += g->csetsize) + if (col[uc1] != col[uc2]) + return(0); + return(1); +} + +/* + - categorize - sort out character categories + == static void categorize(struct parse *p, register struct re_guts *g); + */ +static void +categorize(p, g) +struct parse *p; +register struct re_guts *g; +{ + register cat_t *cats = g->categories; + register int c; + register int c2; + register cat_t cat; + + /* avoid making error situations worse */ + if (p->error != 0) + return; + + for (c = CHAR_MIN; c <= CHAR_MAX; c++) + if (cats[c] == 0 && isinsets(g, c)) { + cat = g->ncategories++; + cats[c] = cat; + for (c2 = c+1; c2 <= CHAR_MAX; c2++) + if (cats[c2] == 0 && samesets(g, c, c2)) + cats[c2] = cat; + } +} + +/* + - dupl - emit a duplicate of a bunch of sops + == static sopno dupl(register struct parse *p, sopno start, sopno finish); + */ +static sopno /* start of duplicate */ +dupl(p, start, finish) +register struct parse *p; +sopno start; /* from here */ +sopno finish; /* to this less one */ +{ + register sopno ret = HERE(); + register sopno len = finish - start; + + assert(finish >= start); + if (len == 0) + return(ret); + enlarge(p, p->ssize + len); /* this many unexpected additions */ + assert(p->ssize >= p->slen + len); + (void) memcpy((char *)(p->strip + p->slen), + (char *)(p->strip + start), (size_t)len*sizeof(sop)); + p->slen += len; + return(ret); +} + +/* + - doemit - emit a strip operator + == static void doemit(register struct parse *p, sop op, size_t opnd); + * + * It might seem better to implement this as a macro with a function as + * hard-case backup, but it's just too big and messy unless there are + * some changes to the data structures. Maybe later. + */ +static void +doemit(p, op, opnd) +register struct parse *p; +sop op; +size_t opnd; +{ + /* avoid making error situations worse */ + if (p->error != 0) + return; + + /* deal with oversize operands ("can't happen", more or less) */ + assert(opnd < 1<slen >= p->ssize) + enlarge(p, (p->ssize+1) / 2 * 3); /* +50% */ + assert(p->slen < p->ssize); + + /* finally, it's all reduced to the easy case */ + p->strip[p->slen++] = SOP(op, opnd); +} + +/* + - doinsert - insert a sop into the strip + == static void doinsert(register struct parse *p, sop op, size_t opnd, sopno pos); + */ +static void +doinsert(p, op, opnd, pos) +register struct parse *p; +sop op; +size_t opnd; +sopno pos; +{ + register sopno sn; + register sop s; + register int i; + + /* avoid making error situations worse */ + if (p->error != 0) + return; + + sn = HERE(); + EMIT(op, opnd); /* do checks, ensure space */ + assert(HERE() == sn+1); + s = p->strip[sn]; + + /* adjust paren pointers */ + assert(pos > 0); + for (i = 1; i < NPAREN; i++) { + if (p->pbegin[i] >= pos) { + p->pbegin[i]++; + } + if (p->pend[i] >= pos) { + p->pend[i]++; + } + } + + memmove((char *)&p->strip[pos+1], (char *)&p->strip[pos], + (HERE()-pos-1)*sizeof(sop)); + p->strip[pos] = s; +} + +/* + - dofwd - complete a forward reference + == static void dofwd(register struct parse *p, sopno pos, sop value); + */ +static void +dofwd(p, pos, value) +register struct parse *p; +register sopno pos; +sop value; +{ + /* avoid making error situations worse */ + if (p->error != 0) + return; + + assert(value < 1<strip[pos] = OP(p->strip[pos]) | value; +} + +/* + - enlarge - enlarge the strip + == static void enlarge(register struct parse *p, sopno size); + */ +static void +enlarge(p, size) +register struct parse *p; +register sopno size; +{ + register sop *sp; + + if (p->ssize >= size) + return; + + sp = (sop *)realloc(p->strip, size*sizeof(sop)); + if (sp == NULL) { + SETERROR(REG_ESPACE); + return; + } + p->strip = sp; + p->ssize = size; +} + +/* + - stripsnug - compact the strip + == static void stripsnug(register struct parse *p, register struct re_guts *g); + */ +static void +stripsnug(p, g) +register struct parse *p; +register struct re_guts *g; +{ + g->nstates = p->slen; + g->strip = (sop *)realloc((char *)p->strip, p->slen * sizeof(sop)); + if (g->strip == NULL) { + SETERROR(REG_ESPACE); + g->strip = p->strip; + } +} + +/* + - findmust - fill in must and mlen with longest mandatory literal string + == static void findmust(register struct parse *p, register struct re_guts *g); + * + * This algorithm could do fancy things like analyzing the operands of | + * for common subsequences. Someday. This code is simple and finds most + * of the interesting cases. + * + * Note that must and mlen got initialized during setup. + */ +static void +findmust(p, g) +struct parse *p; +register struct re_guts *g; +{ + register sop *scan; + sop *start; + register sop *newstart; + register sopno newlen; + register sop s; + register char *cp; + register sopno i; + + /* avoid making error situations worse */ + if (p->error != 0) + return; + + /* find the longest OCHAR sequence in strip */ + newlen = 0; + scan = g->strip + 1; + do { + s = *scan++; + switch (OP(s)) { + case OCHAR: /* sequence member */ + if (newlen == 0) /* new sequence */ + newstart = scan - 1; + newlen++; + break; + case OPLUS_: /* things that don't break one */ + case OLPAREN: + case ORPAREN: + break; + case OQUEST_: /* things that must be skipped */ + case OCH_: + scan--; + do { + scan += OPND(s); + s = *scan; + /* assert() interferes w debug printouts */ + if (OP(s) != O_QUEST && OP(s) != O_CH && + OP(s) != OOR2) { + g->iflags |= BAD; + return; + } + } while (OP(s) != O_QUEST && OP(s) != O_CH); + /* fallthrough */ + default: /* things that break a sequence */ + if (newlen > g->mlen) { /* ends one */ + start = newstart; + g->mlen = newlen; + } + newlen = 0; + break; + } + } while (OP(s) != OEND); + + if (g->mlen == 0) /* there isn't one */ + return; + + /* turn it into a character string */ + g->must = malloc((size_t)g->mlen + 1); + if (g->must == NULL) { /* argh; just forget it */ + g->mlen = 0; + return; + } + cp = g->must; + scan = start; + for (i = g->mlen; i > 0; i--) { + while (OP(s = *scan++) != OCHAR) + continue; + assert(cp < g->must + g->mlen); + *cp++ = (char)OPND(s); + } + assert(cp == g->must + g->mlen); + *cp++ = '\0'; /* just on general principles */ +} + +/* + - pluscount - count + nesting + == static sopno pluscount(register struct parse *p, register struct re_guts *g); + */ +static sopno /* nesting depth */ +pluscount(p, g) +struct parse *p; +register struct re_guts *g; +{ + register sop *scan; + register sop s; + register sopno plusnest = 0; + register sopno maxnest = 0; + + if (p->error != 0) + return(0); /* there may not be an OEND */ + + scan = g->strip + 1; + do { + s = *scan++; + switch (OP(s)) { + case OPLUS_: + plusnest++; + break; + case O_PLUS: + if (plusnest > maxnest) + maxnest = plusnest; + plusnest--; + break; + } + } while (OP(s) != OEND); + if (plusnest != 0) + g->iflags |= BAD; + return(maxnest); +} diff --git a/regex/regex/regcomp.ih b/regex/regex/regcomp.ih new file mode 100644 index 00000000..0776e718 --- /dev/null +++ b/regex/regex/regcomp.ih @@ -0,0 +1,51 @@ +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === regcomp.c === */ +static void p_ere(register struct parse *p, int stop); +static void p_ere_exp(register struct parse *p); +static void p_str(register struct parse *p); +static void p_bre(register struct parse *p, register int end1, register int end2); +static int p_simp_re(register struct parse *p, int starordinary); +static int p_count(register struct parse *p); +static void p_bracket(register struct parse *p); +static void p_b_term(register struct parse *p, register cset *cs); +static void p_b_cclass(register struct parse *p, register cset *cs); +static void p_b_eclass(register struct parse *p, register cset *cs); +static char p_b_symbol(register struct parse *p); +static char p_b_coll_elem(register struct parse *p, int endc); +static char othercase(int ch); +static void bothcases(register struct parse *p, int ch); +static void ordinary(register struct parse *p, register int ch); +static void nonnewline(register struct parse *p); +static void repeat(register struct parse *p, sopno start, int from, int to); +static int seterr(register struct parse *p, int e); +static cset *allocset(register struct parse *p); +static void freeset(register struct parse *p, register cset *cs); +static int freezeset(register struct parse *p, register cset *cs); +static int firstch(register struct parse *p, register cset *cs); +static int nch(register struct parse *p, register cset *cs); +static void mcadd(register struct parse *p, register cset *cs, register char *cp); +static void mcsub(register cset *cs, register char *cp); +static int mcin(register cset *cs, register char *cp); +static char *mcfind(register cset *cs, register char *cp); +static void mcinvert(register struct parse *p, register cset *cs); +static void mccase(register struct parse *p, register cset *cs); +static int isinsets(register struct re_guts *g, int c); +static int samesets(register struct re_guts *g, int c1, int c2); +static void categorize(struct parse *p, register struct re_guts *g); +static sopno dupl(register struct parse *p, sopno start, sopno finish); +static void doemit(register struct parse *p, sop op, size_t opnd); +static void doinsert(register struct parse *p, sop op, size_t opnd, sopno pos); +static void dofwd(register struct parse *p, sopno pos, sop value); +static void enlarge(register struct parse *p, sopno size); +static void stripsnug(register struct parse *p, register struct re_guts *g); +static void findmust(register struct parse *p, register struct re_guts *g); +static sopno pluscount(register struct parse *p, register struct re_guts *g); + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ diff --git a/regex/regex/regerror.c b/regex/regex/regerror.c new file mode 100644 index 00000000..9c530ec5 --- /dev/null +++ b/regex/regex/regerror.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include "regex.h" + +#include "utils.h" +#include "regerror.ih" + +/* + = #define REG_OKAY 0 + = #define REG_NOMATCH 1 + = #define REG_BADPAT 2 + = #define REG_ECOLLATE 3 + = #define REG_ECTYPE 4 + = #define REG_EESCAPE 5 + = #define REG_ESUBREG 6 + = #define REG_EBRACK 7 + = #define REG_EPAREN 8 + = #define REG_EBRACE 9 + = #define REG_BADBR 10 + = #define REG_ERANGE 11 + = #define REG_ESPACE 12 + = #define REG_BADRPT 13 + = #define REG_EMPTY 14 + = #define REG_ASSERT 15 + = #define REG_INVARG 16 + = #define REG_ATOI 255 // convert name to number (!) + = #define REG_ITOA 0400 // convert number to name (!) + */ +static struct rerr { + int code; + char *name; + char *explain; +} rerrs[] = { + REG_OKAY, "REG_OKAY", "no errors detected", + REG_NOMATCH, "REG_NOMATCH", "regexec() failed to match", + REG_BADPAT, "REG_BADPAT", "invalid regular expression", + REG_ECOLLATE, "REG_ECOLLATE", "invalid collating element", + REG_ECTYPE, "REG_ECTYPE", "invalid character class", + REG_EESCAPE, "REG_EESCAPE", "trailing backslash (\\)", + REG_ESUBREG, "REG_ESUBREG", "invalid backreference number", + REG_EBRACK, "REG_EBRACK", "brackets ([ ]) not balanced", + REG_EPAREN, "REG_EPAREN", "parentheses not balanced", + REG_EBRACE, "REG_EBRACE", "braces not balanced", + REG_BADBR, "REG_BADBR", "invalid repetition count(s)", + REG_ERANGE, "REG_ERANGE", "invalid character range", + REG_ESPACE, "REG_ESPACE", "out of memory", + REG_BADRPT, "REG_BADRPT", "repetition-operator operand invalid", + REG_EMPTY, "REG_EMPTY", "empty (sub)expression", + REG_ASSERT, "REG_ASSERT", "\"can't happen\" -- you found a bug", + REG_INVARG, "REG_INVARG", "invalid argument to regex routine", + -1, "", "*** unknown regexp error code ***", +}; + +/* + - regerror - the interface to error numbers + = extern size_t regerror(int, const regex_t *, char *, size_t); + */ +/* ARGSUSED */ +size_t +regerror(regerrcode, preg, errbuf, errbuf_size) +int regerrcode; +const regex_t *preg; +char *errbuf; +size_t errbuf_size; +{ + register struct rerr *r; + register size_t len; + register int target = regerrcode &~ REG_ITOA; + register char *s; + char convbuf[50]; + + if (regerrcode == REG_ATOI) + s = regatoi(preg, convbuf); + else { + for (r = rerrs; r->code >= 0; r++) + if (r->code == target) + break; + + if (regerrcode®_ITOA) { + if (r->code >= 0) + (void) strcpy(convbuf, r->name); + else + sprintf(convbuf, "REG_0x%x", target); + assert(strlen(convbuf) < sizeof(convbuf)); + s = convbuf; + } else + s = r->explain; + } + + len = strlen(s) + 1; + if (errbuf_size > 0) { + if (errbuf_size > len) + (void) strcpy(errbuf, s); + else { + (void) strncpy(errbuf, s, errbuf_size-1); + errbuf[errbuf_size-1] = '\0'; + } + } + + return(len); +} + +/* + - regatoi - internal routine to implement REG_ATOI + == static char *regatoi(const regex_t *preg, char *localbuf); + */ +static char * +regatoi(preg, localbuf) +const regex_t *preg; +char *localbuf; +{ + register struct rerr *r; + + for (r = rerrs; r->code >= 0; r++) + if (strcmp(r->name, preg->re_endp) == 0) + break; + if (r->code < 0) + return("0"); + + sprintf(localbuf, "%d", r->code); + return(localbuf); +} diff --git a/regex/regex/regerror.ih b/regex/regex/regerror.ih new file mode 100644 index 00000000..2cb668c2 --- /dev/null +++ b/regex/regex/regerror.ih @@ -0,0 +1,12 @@ +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === regerror.c === */ +static char *regatoi(const regex_t *preg, char *localbuf); + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ diff --git a/regex/regex/regex.3 b/regex/regex/regex.3 new file mode 100644 index 00000000..bc747096 --- /dev/null +++ b/regex/regex/regex.3 @@ -0,0 +1,509 @@ +.TH REGEX 3 "25 Sept 1997" +.BY "Henry Spencer" +.de ZR +.\" one other place knows this name: the SEE ALSO section +.IR regex (7) \\$1 +.. +.SH NAME +regcomp, regexec, regerror, regfree \- regular-expression library +.SH SYNOPSIS +.ft B +.\".na +#include +.br +#include +.HP 10 +int regcomp(regex_t\ *preg, const\ char\ *pattern, int\ cflags); +.HP +int\ regexec(const\ regex_t\ *preg, const\ char\ *string, +size_t\ nmatch, regmatch_t\ pmatch[], int\ eflags); +.HP +size_t\ regerror(int\ errcode, const\ regex_t\ *preg, +char\ *errbuf, size_t\ errbuf_size); +.HP +void\ regfree(regex_t\ *preg); +.\".ad +.ft +.SH DESCRIPTION +These routines implement POSIX 1003.2 regular expressions (``RE''s); +see +.ZR . +.I Regcomp +compiles an RE written as a string into an internal form, +.I regexec +matches that internal form against a string and reports results, +.I regerror +transforms error codes from either into human-readable messages, +and +.I regfree +frees any dynamically-allocated storage used by the internal form +of an RE. +.PP +The header +.I +declares two structure types, +.I regex_t +and +.IR regmatch_t , +the former for compiled internal forms and the latter for match reporting. +It also declares the four functions, +a type +.IR regoff_t , +and a number of constants with names starting with ``REG_''. +.PP +.I Regcomp +compiles the regular expression contained in the +.I pattern +string, +subject to the flags in +.IR cflags , +and places the results in the +.I regex_t +structure pointed to by +.IR preg . +.I Cflags +is the bitwise OR of zero or more of the following flags: +.IP REG_EXTENDED \w'REG_EXTENDED'u+2n +Compile modern (``extended'') REs, +rather than the obsolete (``basic'') REs that +are the default. +.IP REG_BASIC +This is a synonym for 0, +provided as a counterpart to REG_EXTENDED to improve readability. +This is an extension, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +.IP REG_NOSPEC +Compile with recognition of all special characters turned off. +All characters are thus considered ordinary, +so the ``RE'' is a literal string. +This is an extension, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +REG_EXTENDED and REG_NOSPEC may not be used +in the same call to +.IR regcomp . +.IP REG_ICASE +Compile for matching that ignores upper/lower case distinctions. +See +.ZR . +.IP REG_NOSUB +Compile for matching that need only report success or failure, +not what was matched. +.IP REG_NEWLINE +Compile for newline-sensitive matching. +By default, newline is a completely ordinary character with no special +meaning in either REs or strings. +With this flag, +`[^' bracket expressions and `.' never match newline, +a `^' anchor matches the null string after any newline in the string +in addition to its normal function, +and the `$' anchor matches the null string before any newline in the +string in addition to its normal function. +.IP REG_PEND +The regular expression ends, +not at the first NUL, +but just before the character pointed to by the +.I re_endp +member of the structure pointed to by +.IR preg . +The +.I re_endp +member is of type +.IR const\ char\ * . +This flag permits inclusion of NULs in the RE; +they are considered ordinary characters. +This is an extension, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +.PP +When successful, +.I regcomp +returns 0 and fills in the structure pointed to by +.IR preg . +One member of that structure +(other than +.IR re_endp ) +is publicized: +.IR re_nsub , +of type +.IR size_t , +contains the number of parenthesized subexpressions within the RE +(except that the value of this member is undefined if the +REG_NOSUB flag was used). +If +.I regcomp +fails, it returns a non-zero error code; +see DIAGNOSTICS. +.PP +.I Regexec +matches the compiled RE pointed to by +.I preg +against the +.IR string , +subject to the flags in +.IR eflags , +and reports results using +.IR nmatch , +.IR pmatch , +and the returned value. +The RE must have been compiled by a previous invocation of +.IR regcomp . +The compiled form is not altered during execution of +.IR regexec , +so a single compiled RE can be used simultaneously by multiple threads. +.PP +By default, +the NUL-terminated string pointed to by +.I string +is considered to be the text of an entire line, +with the NUL indicating the end of the line. +(That is, +any other end-of-line marker is considered to have been removed +and replaced by the NUL.) +The +.I eflags +argument is the bitwise OR of zero or more of the following flags: +.IP REG_NOTBOL \w'REG_STARTEND'u+2n +The first character of +the string +is not the beginning of a line, so the `^' anchor should not match before it. +This does not affect the behavior of newlines under REG_NEWLINE. +.IP REG_NOTEOL +The NUL terminating +the string +does not end a line, so the `$' anchor should not match before it. +This does not affect the behavior of newlines under REG_NEWLINE. +.IP REG_STARTEND +The string is considered to start at +\fIstring\fR\ + \fIpmatch\fR[0].\fIrm_so\fR +and to have a terminating NUL located at +\fIstring\fR\ + \fIpmatch\fR[0].\fIrm_eo\fR +(there need not actually be a NUL at that location), +regardless of the value of +.IR nmatch . +See below for the definition of +.IR pmatch +and +.IR nmatch . +This is an extension, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +Note that a non-zero \fIrm_so\fR does not imply REG_NOTBOL; +REG_STARTEND affects only the location of the string, +not how it is matched. +.PP +See +.ZR +for a discussion of what is matched in situations where an RE or a +portion thereof could match any of several substrings of +.IR string . +.PP +Normally, +.I regexec +returns 0 for success and the non-zero code REG_NOMATCH for failure. +Other non-zero error codes may be returned in exceptional situations; +see DIAGNOSTICS. +.PP +If REG_NOSUB was specified in the compilation of the RE, +or if +.I nmatch +is 0, +.I regexec +ignores the +.I pmatch +argument (but see below for the case where REG_STARTEND is specified). +Otherwise, +.I pmatch +points to an array of +.I nmatch +structures of type +.IR regmatch_t . +Such a structure has at least the members +.I rm_so +and +.IR rm_eo , +both of type +.I regoff_t +(a signed arithmetic type at least as large as an +.I off_t +and a +.IR ssize_t ), +containing respectively the offset of the first character of a substring +and the offset of the first character after the end of the substring. +Offsets are measured from the beginning of the +.I string +argument given to +.IR regexec . +An empty substring is denoted by equal offsets, +both indicating the character following the empty substring. +.PP +The 0th member of the +.I pmatch +array is filled in to indicate what substring of +.I string +was matched by the entire RE. +Remaining members report what substring was matched by parenthesized +subexpressions within the RE; +member +.I i +reports subexpression +.IR i , +with subexpressions counted (starting at 1) by the order of their opening +parentheses in the RE, left to right. +Unused entries in the array\(emcorresponding either to subexpressions that +did not participate in the match at all, or to subexpressions that do not +exist in the RE (that is, \fIi\fR\ > \fIpreg\fR\->\fIre_nsub\fR)\(emhave both +.I rm_so +and +.I rm_eo +set to \-1. +If a subexpression participated in the match several times, +the reported substring is the last one it matched. +(Note, as an example in particular, that when the RE `(b*)+' matches `bbb', +the parenthesized subexpression matches the three `b's and then +an infinite number of empty strings following the last `b', +so the reported substring is one of the empties.) +.PP +If REG_STARTEND is specified, +.I pmatch +must point to at least one +.I regmatch_t +(even if +.I nmatch +is 0 or REG_NOSUB was specified), +to hold the input offsets for REG_STARTEND. +Use for output is still entirely controlled by +.IR nmatch ; +if +.I nmatch +is 0 or REG_NOSUB was specified, +the value of +.IR pmatch [0] +will not be changed by a successful +.IR regexec . +.PP +.I Regerror +maps a non-zero +.I errcode +from either +.I regcomp +or +.I regexec +to a human-readable, printable message. +If +.I preg +is non-NULL, +the error code should have arisen from use of +the +.I regex_t +pointed to by +.IR preg , +and if the error code came from +.IR regcomp , +it should have been the result from the most recent +.I regcomp +using that +.IR regex_t . +.RI ( Regerror +may be able to supply a more detailed message using information +from the +.IR regex_t .) +.I Regerror +places the NUL-terminated message into the buffer pointed to by +.IR errbuf , +limiting the length (including the NUL) to at most +.I errbuf_size +bytes. +If the whole message won't fit, +as much of it as will fit before the terminating NUL is supplied. +In any case, +the returned value is the size of buffer needed to hold the whole +message (including terminating NUL). +If +.I errbuf_size +is 0, +.I errbuf +is ignored but the return value is still correct. +.PP +If the +.I errcode +given to +.I regerror +is first ORed with REG_ITOA, +the ``message'' that results is the printable name of the error code, +e.g. ``REG_NOMATCH'', +rather than an explanation thereof. +If +.I errcode +is REG_ATOI, +then +.I preg +shall be non-NULL and the +.I re_endp +member of the structure it points to +must point to the printable name of an error code; +in this case, the result in +.I errbuf +is the decimal digits of +the numeric value of the error code +(0 if the name is not recognized). +REG_ITOA and REG_ATOI are intended primarily as debugging facilities; +they are extensions, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +Be warned also that they are considered experimental and changes are possible. +.PP +.I Regfree +frees any dynamically-allocated storage associated with the compiled RE +pointed to by +.IR preg . +The remaining +.I regex_t +is no longer a valid compiled RE +and the effect of supplying it to +.I regexec +or +.I regerror +is undefined. +.PP +None of these functions references global variables except for tables +of constants; +all are safe for use from multiple threads if the arguments are safe. +.SH IMPLEMENTATION CHOICES +There are a number of decisions that 1003.2 leaves up to the implementor, +either by explicitly saying ``undefined'' or by virtue of them being +forbidden by the RE grammar. +This implementation treats them as follows. +.PP +See +.ZR +for a discussion of the definition of case-independent matching. +.PP +There is no particular limit on the length of REs, +except insofar as memory is limited. +Memory usage is approximately linear in RE size, and largely insensitive +to RE complexity, except for bounded repetitions. +See BUGS for one short RE using them +that will run almost any system out of memory. +.PP +A backslashed character other than one specifically given a magic meaning +by 1003.2 (such magic meanings occur only in obsolete [``basic''] REs) +is taken as an ordinary character. +.PP +Any unmatched [ is a REG_EBRACK error. +.PP +Equivalence classes cannot begin or end bracket-expression ranges. +The endpoint of one range cannot begin another. +.PP +RE_DUP_MAX, the limit on repetition counts in bounded repetitions, is 255. +.PP +A repetition operator (?, *, +, or bounds) cannot follow another +repetition operator. +A repetition operator cannot begin an expression or subexpression +or follow `^' or `|'. +.PP +`|' cannot appear first or last in a (sub)expression or after another `|', +i.e. an operand of `|' cannot be an empty subexpression. +An empty parenthesized subexpression, `()', is legal and matches an +empty (sub)string. +An empty string is not a legal RE. +.PP +A `{' followed by a digit is considered the beginning of bounds for a +bounded repetition, which must then follow the syntax for bounds. +A `{' \fInot\fR followed by a digit is considered an ordinary character. +.PP +`^' and `$' beginning and ending subexpressions in obsolete (``basic'') +REs are anchors, not ordinary characters. +.SH SEE ALSO +grep(1), regex(7) +.PP +POSIX 1003.2, sections 2.8 (Regular Expression Notation) +and +B.5 (C Binding for Regular Expression Matching). +.SH DIAGNOSTICS +Non-zero error codes from +.I regcomp +and +.I regexec +include the following: +.PP +.nf +.ta \w'REG_ECOLLATE'u+3n +REG_NOMATCH regexec() failed to match +REG_BADPAT invalid regular expression +REG_ECOLLATE invalid collating element +REG_ECTYPE invalid character class +REG_EESCAPE \e applied to unescapable character +REG_ESUBREG invalid backreference number +REG_EBRACK brackets [ ] not balanced +REG_EPAREN parentheses ( ) not balanced +REG_EBRACE braces { } not balanced +REG_BADBR invalid repetition count(s) in { } +REG_ERANGE invalid character range in [ ] +REG_ESPACE ran out of memory +REG_BADRPT ?, *, or + operand invalid +REG_EMPTY empty (sub)expression +REG_ASSERT ``can't happen''\(emyou found a bug +REG_INVARG invalid argument, e.g. negative-length string +.fi +.SH HISTORY +Written by Henry Spencer, +henry@zoo.toronto.edu. +.SH BUGS +This is an alpha release with known defects. +Please report problems. +.PP +There is one known functionality bug. +The implementation of internationalization is incomplete: +the locale is always assumed to be the default one of 1003.2, +and only the collating elements etc. of that locale are available. +.PP +The back-reference code is subtle and doubts linger about its correctness +in complex cases. +.PP +.I Regexec +performance is poor. +This will improve with later releases. +.I Nmatch +exceeding 0 is expensive; +.I nmatch +exceeding 1 is worse. +.I Regexec +is largely insensitive to RE complexity \fIexcept\fR that back +references are massively expensive. +RE length does matter; in particular, there is a strong speed bonus +for keeping RE length under about 30 characters, +with most special characters counting roughly double. +.PP +.I Regcomp +implements bounded repetitions by macro expansion, +which is costly in time and space if counts are large +or bounded repetitions are nested. +An RE like, say, +`((((a{1,100}){1,100}){1,100}){1,100}){1,100}' +will (eventually) run almost any existing machine out of swap space. +.PP +There are suspected problems with response to obscure error conditions. +Notably, +certain kinds of internal overflow, +produced only by truly enormous REs or by multiply nested bounded repetitions, +are probably not handled well. +.PP +Due to a mistake in 1003.2, things like `a)b' are legal REs because `)' is +a special character only in the presence of a previous unmatched `('. +This can't be fixed until the spec is fixed. +.PP +The standard's definition of back references is vague. +For example, does +`a\e(\e(b\e)*\e2\e)*d' match `abbbd'? +Until the standard is clarified, +behavior in such cases should not be relied on. +.PP +The implementation of word-boundary matching is a bit of a kludge, +and bugs may lurk in combinations of word-boundary matching and anchoring. diff --git a/regex/regex/regex.7 b/regex/regex/regex.7 new file mode 100644 index 00000000..0fa18026 --- /dev/null +++ b/regex/regex/regex.7 @@ -0,0 +1,235 @@ +.TH REGEX 7 "25 Oct 1995" +.BY "Henry Spencer" +.SH NAME +regex \- POSIX 1003.2 regular expressions +.SH DESCRIPTION +Regular expressions (``RE''s), +as defined in POSIX 1003.2, come in two forms: +modern REs (roughly those of +.IR egrep ; +1003.2 calls these ``extended'' REs) +and obsolete REs (roughly those of +.IR ed ; +1003.2 ``basic'' REs). +Obsolete REs mostly exist for backward compatibility in some old programs; +they will be discussed at the end. +1003.2 leaves some aspects of RE syntax and semantics open; +`\(dg' marks decisions on these aspects that +may not be fully portable to other 1003.2 implementations. +.PP +A (modern) RE is one\(dg or more non-empty\(dg \fIbranches\fR, +separated by `|'. +It matches anything that matches one of the branches. +.PP +A branch is one\(dg or more \fIpieces\fR, concatenated. +It matches a match for the first, followed by a match for the second, etc. +.PP +A piece is an \fIatom\fR possibly followed +by a single\(dg `*', `+', `?', or \fIbound\fR. +An atom followed by `*' matches a sequence of 0 or more matches of the atom. +An atom followed by `+' matches a sequence of 1 or more matches of the atom. +An atom followed by `?' matches a sequence of 0 or 1 matches of the atom. +.PP +A \fIbound\fR is `{' followed by an unsigned decimal integer, +possibly followed by `,' +possibly followed by another unsigned decimal integer, +always followed by `}'. +The integers must lie between 0 and RE_DUP_MAX (255\(dg) inclusive, +and if there are two of them, the first may not exceed the second. +An atom followed by a bound containing one integer \fIi\fR +and no comma matches +a sequence of exactly \fIi\fR matches of the atom. +An atom followed by a bound +containing one integer \fIi\fR and a comma matches +a sequence of \fIi\fR or more matches of the atom. +An atom followed by a bound +containing two integers \fIi\fR and \fIj\fR matches +a sequence of \fIi\fR through \fIj\fR (inclusive) matches of the atom. +.PP +An atom is a regular expression enclosed in `()' (matching a match for the +regular expression), +an empty set of `()' (matching the null string)\(dg, +a \fIbracket expression\fR (see below), `.' +(matching any single character), `^' (matching the null string at the +beginning of a line), `$' (matching the null string at the +end of a line), a `\e' followed by one of the characters +`^.[$()|*+?{\e' +(matching that character taken as an ordinary character), +a `\e' followed by any other character\(dg +(matching that character taken as an ordinary character, +as if the `\e' had not been present\(dg), +or a single character with no other significance (matching that character). +A `{' followed by a character other than a digit is an ordinary +character, not the beginning of a bound\(dg. +It is illegal to end an RE with `\e'. +.PP +A \fIbracket expression\fR is a list of characters enclosed in `[]'. +It normally matches any single character from the list (but see below). +If the list begins with `^', +it matches any single character +(but see below) \fInot\fR from the rest of the list. +If two characters in the list are separated by `\-', this is shorthand +for the full \fIrange\fR of characters between those two (inclusive) in the +collating sequence, +e.g. `[0\-9]' in ASCII matches any decimal digit. +It is illegal\(dg for two ranges to share an +endpoint, e.g. `a\-c\-e'. +Ranges are very collating-sequence-dependent, +and portable programs should avoid relying on them. +.PP +To include a literal `]' in the list, make it the first character +(following a possible `^'). +To include a literal `\-', make it the first or last character, +or the second endpoint of a range. +To use a literal `\-' as the first endpoint of a range, +enclose it in `[.' and `.]' to make it a collating element (see below). +With the exception of these and some combinations using `[' (see next +paragraphs), all other special characters, including `\e', lose their +special significance within a bracket expression. +.PP +Within a bracket expression, a collating element (a character, +a multi-character sequence that collates as if it were a single character, +or a collating-sequence name for either) +enclosed in `[.' and `.]' stands for the +sequence of characters of that collating element. +The sequence is a single element of the bracket expression's list. +A bracket expression containing a multi-character collating element +can thus match more than one character, +e.g. if the collating sequence includes a `ch' collating element, +then the RE `[[.ch.]]*c' matches the first five characters +of `chchcc'. +.PP +Within a bracket expression, a collating element enclosed in `[=' and +`=]' is an equivalence class, standing for the sequences of characters +of all collating elements equivalent to that one, including itself. +(If there are no other equivalent collating elements, +the treatment is as if the enclosing delimiters were `[.' and `.]'.) +For example, if o and \o'o^' are the members of an equivalence class, +then `[[=o=]]', `[[=\o'o^'=]]', and `[o\o'o^']' are all synonymous. +An equivalence class may not\(dg be an endpoint +of a range. +.PP +Within a bracket expression, the name of a \fIcharacter class\fR enclosed +in `[:' and `:]' stands for the list of all characters belonging to that +class. +Standard character class names are: +.PP +.RS +.nf +.ta 3c 6c 9c +alnum digit punct +alpha graph space +blank lower upper +cntrl print xdigit +.fi +.RE +.PP +These stand for the character classes defined in +.IR ctype (3). +A locale may provide others. +A character class may not be used as an endpoint of a range. +.PP +There are two special cases\(dg of bracket expressions: +the bracket expressions `[[:<:]]' and `[[:>:]]' match the null string at +the beginning and end of a word respectively. +A word is defined as a sequence of +word characters +which is neither preceded nor followed by +word characters. +A word character is an +.I alnum +character (as defined by +.IR ctype (3)) +or an underscore. +This is an extension, +compatible with but not specified by POSIX 1003.2, +and should be used with +caution in software intended to be portable to other systems. +.PP +In the event that an RE could match more than one substring of a given +string, +the RE matches the one starting earliest in the string. +If the RE could match more than one substring starting at that point, +it matches the longest. +Subexpressions also match the longest possible substrings, subject to +the constraint that the whole match be as long as possible, +with subexpressions starting earlier in the RE taking priority over +ones starting later. +Note that higher-level subexpressions thus take priority over +their lower-level component subexpressions. +.PP +Match lengths are measured in characters, not collating elements. +A null string is considered longer than no match at all. +For example, +`bb*' matches the three middle characters of `abbbc', +`(wee|week)(knights|nights)' matches all ten characters of `weeknights', +when `(.*).*' is matched against `abc' the parenthesized subexpression +matches all three characters, and +when `(a*)*' is matched against `bc' both the whole RE and the parenthesized +subexpression match the null string. +.PP +If case-independent matching is specified, +the effect is much as if all case distinctions had vanished from the +alphabet. +When an alphabetic that exists in multiple cases appears as an +ordinary character outside a bracket expression, it is effectively +transformed into a bracket expression containing both cases, +e.g. `x' becomes `[xX]'. +When it appears inside a bracket expression, all case counterparts +of it are added to the bracket expression, so that (e.g.) `[x]' +becomes `[xX]' and `[^x]' becomes `[^xX]'. +.PP +No particular limit is imposed on the length of REs\(dg. +Programs intended to be portable should not employ REs longer +than 256 bytes, +as an implementation can refuse to accept such REs and remain +POSIX-compliant. +.PP +Obsolete (``basic'') regular expressions differ in several respects. +`|', `+', and `?' are ordinary characters and there is no equivalent +for their functionality. +The delimiters for bounds are `\e{' and `\e}', +with `{' and `}' by themselves ordinary characters. +The parentheses for nested subexpressions are `\e(' and `\e)', +with `(' and `)' by themselves ordinary characters. +`^' is an ordinary character except at the beginning of the +RE or\(dg the beginning of a parenthesized subexpression, +`$' is an ordinary character except at the end of the +RE or\(dg the end of a parenthesized subexpression, +and `*' is an ordinary character if it appears at the beginning of the +RE or the beginning of a parenthesized subexpression +(after a possible leading `^'). +Finally, there is one new type of atom, a \fIback reference\fR: +`\e' followed by a non-zero decimal digit \fId\fR +matches the same sequence of characters +matched by the \fId\fRth parenthesized subexpression +(numbering subexpressions by the positions of their opening parentheses, +left to right), +so that (e.g.) `\e([bc]\e)\e1' matches `bb' or `cc' but not `bc'. +.SH SEE ALSO +regex(3) +.PP +POSIX 1003.2, section 2.8 (Regular Expression Notation). +.SH HISTORY +Written by Henry Spencer, based on the 1003.2 spec. +.SH BUGS +Having two kinds of REs is a botch. +.PP +The current 1003.2 spec says that `)' is an ordinary character in +the absence of an unmatched `('; +this was an unintentional result of a wording error, +and change is likely. +Avoid relying on it. +.PP +Back references are a dreadful botch, +posing major problems for efficient implementations. +They are also somewhat vaguely defined +(does +`a\e(\e(b\e)*\e2\e)*d' match `abbbd'?). +Avoid using them. +.PP +1003.2's specification of case-independent matching is vague. +The ``one case implies all cases'' definition given above +is current consensus among implementors as to the right interpretation. +.PP +The syntax for word boundaries is incredibly ugly. diff --git a/regex/regex/regex.h b/regex/regex/regex.h new file mode 100644 index 00000000..c98b955c --- /dev/null +++ b/regex/regex/regex.h @@ -0,0 +1,97 @@ +#ifndef _REGEX_H_ +#define _REGEX_H_ /* never again */ + +// This block added by Monni to improve windows compatibility +#if defined(_WINDOWS) || defined(WINDOWS) || defined(_WIN32) || defined(WIN32) +# include +# if defined(_MSC_VER) +# if defined(REGEX_DLL) +# define REXTERN __declspec(dllexport) +# define REXTERNF __declspec(dllexport) +# elif defined(REGEX_USEDLL) +# define REXTERN __declspec(dllimport) +# define REXTERNF __declspec(dllimport) +# endif +# endif +#endif + +#if !defined(REXTERN) +# define REXTERN +#endif + +#if !defined(REXTERNF) +# define REXTERNF extern +#endif + +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === regex2.h === */ +typedef off_t regoff_t; +typedef struct { + int re_magic; + size_t re_nsub; /* number of parenthesized subexpressions */ + const char *re_endp; /* end pointer for REG_PEND */ + struct re_guts *re_g; /* none of your business :-) */ +} regex_t; +typedef struct { + regoff_t rm_so; /* start of match */ + regoff_t rm_eo; /* end of match */ +} regmatch_t; + + +/* === regcomp.c === */ +REXTERNF int regcomp(regex_t *, const char *, int); +#define REG_BASIC 0000 +#define REG_EXTENDED 0001 +#define REG_ICASE 0002 +#define REG_NOSUB 0004 +#define REG_NEWLINE 0010 +#define REG_NOSPEC 0020 +#define REG_PEND 0040 +#define REG_DUMP 0200 + + +/* === regerror.c === */ +#define REG_OKAY 0 +#define REG_NOMATCH 1 +#define REG_BADPAT 2 +#define REG_ECOLLATE 3 +#define REG_ECTYPE 4 +#define REG_EESCAPE 5 +#define REG_ESUBREG 6 +#define REG_EBRACK 7 +#define REG_EPAREN 8 +#define REG_EBRACE 9 +#define REG_BADBR 10 +#define REG_ERANGE 11 +#define REG_ESPACE 12 +#define REG_BADRPT 13 +#define REG_EMPTY 14 +#define REG_ASSERT 15 +#define REG_INVARG 16 +#define REG_ATOI 255 /* convert name to number (!) */ +#define REG_ITOA 0400 /* convert number to name (!) */ +extern size_t regerror(int, const regex_t *, char *, size_t); + + +/* === regexec.c === */ +REXTERNF int regexec(const regex_t *, const char *, size_t, regmatch_t [], int); +#define REG_NOTBOL 00001 +#define REG_NOTEOL 00002 +#define REG_STARTEND 00004 +#define REG_TRACE 00400 /* tracing of execution */ +#define REG_LARGE 01000 /* force large representation */ +#define REG_BACKR 02000 /* force use of backref code */ + + +/* === regfree.c === */ +REXTERNF void regfree(regex_t *); + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ +#endif diff --git a/regex/regex/regex2.h b/regex/regex/regex2.h new file mode 100644 index 00000000..79353f06 --- /dev/null +++ b/regex/regex/regex2.h @@ -0,0 +1,137 @@ +/* + * First, the stuff that ends up in the outside-world include file + = typedef off_t regoff_t; + = typedef struct { + = int re_magic; + = size_t re_nsub; // number of parenthesized subexpressions + = const char *re_endp; // end pointer for REG_PEND + = struct re_guts *re_g; // none of your business :-) + = } regex_t; + = typedef struct { + = regoff_t rm_so; // start of match + = regoff_t rm_eo; // end of match + = } regmatch_t; + */ +/* + * internals of regex_t + */ +#define MAGIC1 ((('r'^0200)<<8) | 'e') + +/* + * The internal representation is a *strip*, a sequence of + * operators ending with an endmarker. (Some terminology etc. is a + * historical relic of earlier versions which used multiple strips.) + * Certain oddities in the representation are there to permit running + * the machinery backwards; in particular, any deviation from sequential + * flow must be marked at both its source and its destination. Some + * fine points: + * + * - OPLUS_ and O_PLUS are *inside* the loop they create. + * - OQUEST_ and O_QUEST are *outside* the bypass they create. + * - OCH_ and O_CH are *outside* the multi-way branch they create, while + * OOR1 and OOR2 are respectively the end and the beginning of one of + * the branches. Note that there is an implicit OOR2 following OCH_ + * and an implicit OOR1 preceding O_CH. + * + * In state representations, an operator's bit is on to signify a state + * immediately *preceding* "execution" of that operator. + */ +typedef long sop; /* strip operator */ +typedef long sopno; +#define OPRMASK 0x7c000000 +#define OPDMASK 0x03ffffff +#define OPSHIFT (26) +#define OP(n) ((n)&OPRMASK) +#define OPND(n) ((n)&OPDMASK) +#define SOP(op, opnd) ((op)|(opnd)) +/* operators meaning operand */ +/* (back, fwd are offsets) */ +#define OEND (1< uch [csetsize] */ + uch mask; /* bit within array */ + uch hash; /* hash code */ + size_t smultis; + char *multis; /* -> char[smulti] ab\0cd\0ef\0\0 */ +} cset; +/* note that CHadd and CHsub are unsafe, and CHIN doesn't yield 0/1 */ +#define CHadd(cs, c) ((cs)->ptr[(uch)(c)] |= (cs)->mask, (cs)->hash += (c)) +#define CHsub(cs, c) ((cs)->ptr[(uch)(c)] &= ~(cs)->mask, (cs)->hash -= (c)) +#define CHIN(cs, c) ((cs)->ptr[(uch)(c)] & (cs)->mask) +#define MCadd(p, cs, cp) mcadd(p, cs, cp) /* regcomp() internal fns */ +#define MCsub(p, cs, cp) mcsub(p, cs, cp) +#define MCin(p, cs, cp) mcin(p, cs, cp) + +/* stuff for character categories */ +typedef unsigned char cat_t; + +/* + * main compiled-expression structure + */ +struct re_guts { + int magic; +# define MAGIC2 ((('R'^0200)<<8)|'E') + sop *strip; /* malloced area for strip */ + int csetsize; /* number of bits in a cset vector */ + int ncsets; /* number of csets in use */ + cset *sets; /* -> cset [ncsets] */ + uch *setbits; /* -> uch[csetsize][ncsets/CHAR_BIT] */ + int cflags; /* copy of regcomp() cflags argument */ + sopno nstates; /* = number of sops */ + sopno firststate; /* the initial OEND (normally 0) */ + sopno laststate; /* the final OEND */ + int iflags; /* internal flags */ +# define USEBOL 01 /* used ^ */ +# define USEEOL 02 /* used $ */ +# define BAD 04 /* something wrong */ + int nbol; /* number of ^ used */ + int neol; /* number of $ used */ + int ncategories; /* how many character categories */ + cat_t *categories; /* ->catspace[-CHAR_MIN] */ + char *must; /* match must contain this string */ + int mlen; /* length of must */ + size_t nsub; /* copy of re_nsub */ + int backrefs; /* does it use back references? */ + sopno nplus; /* how deep does it nest +s? */ + /* catspace must be last */ + cat_t catspace[1]; /* actually [NC] */ +}; + +/* misc utilities */ +#if defined(OUT) +# undef OUT +#endif +#define OUT (CHAR_MAX+1) /* a non-character value */ +#define ISWORD(c) (isalnum(c) || (c) == '_') diff --git a/regex/regex/regexec.c b/regex/regex/regexec.c new file mode 100644 index 00000000..5557a1bf --- /dev/null +++ b/regex/regex/regexec.c @@ -0,0 +1,139 @@ +/* + * the outer shell of regexec() + * + * This file includes engine.c *twice*, after muchos fiddling with the + * macros that code uses. This lets the same code operate on two different + * representations for state sets. + */ +#include +#include +#include +#include +#include +#include +#include "regex.h" + +#include "utils.h" +#include "regex2.h" + +static int nope = 0; /* for use in asserts; shuts lint up */ + +/* macros for manipulating states, small version */ +#define states unsigned +#define states1 unsigned /* for later use in regexec() decision */ +#define CLEAR(v) ((v) = 0) +#define SET0(v, n) ((v) &= ~((unsigned)1 << (n))) +#define SET1(v, n) ((v) |= (unsigned)1 << (n)) +#define ISSET(v, n) ((v) & ((unsigned)1 << (n))) +#define ASSIGN(d, s) ((d) = (s)) +#define EQ(a, b) ((a) == (b)) +#define STATEVARS int dummy /* dummy version */ +#define STATESETUP(m, n) /* nothing */ +#define STATETEARDOWN(m) /* nothing */ +#define SETUP(v) ((v) = 0) +#define onestate unsigned +#define INIT(o, n) ((o) = (unsigned)1 << (n)) +#define INC(o) ((o) <<= 1) +#define ISSTATEIN(v, o) ((v) & (o)) +/* some abbreviations; note that some of these know variable names! */ +/* do "if I'm here, I can also be there" etc without branches */ +#define FWD(dst, src, n) ((dst) |= ((unsigned)(src)&(here)) << (n)) +#define BACK(dst, src, n) ((dst) |= ((unsigned)(src)&(here)) >> (n)) +#define ISSETBACK(v, n) ((v) & ((unsigned)here >> (n))) +/* function names */ +#define SNAMES /* engine.c looks after details */ + +#include "engine.c" + +/* now undo things */ +#undef states +#undef CLEAR +#undef SET0 +#undef SET1 +#undef ISSET +#undef ASSIGN +#undef EQ +#undef STATEVARS +#undef STATESETUP +#undef STATETEARDOWN +#undef SETUP +#undef onestate +#undef INIT +#undef INC +#undef ISSTATEIN +#undef FWD +#undef BACK +#undef ISSETBACK +#undef SNAMES + +/* macros for manipulating states, large version */ +#define states char * +#define CLEAR(v) memset(v, 0, m->g->nstates) +#define SET0(v, n) ((v)[n] = 0) +#define SET1(v, n) ((v)[n] = 1) +#define ISSET(v, n) ((v)[n]) +#define ASSIGN(d, s) memcpy(d, s, m->g->nstates) +#define EQ(a, b) (memcmp(a, b, m->g->nstates) == 0) +#define STATEVARS int vn; char *space +#define STATESETUP(m, nv) { (m)->space = malloc((nv)*(m)->g->nstates); \ + if ((m)->space == NULL) return(REG_ESPACE); \ + (m)->vn = 0; } +#define STATETEARDOWN(m) { free((m)->space); } +#define SETUP(v) ((v) = &m->space[m->vn++ * m->g->nstates]) +#define onestate int +#define INIT(o, n) ((o) = (n)) +#define INC(o) ((o)++) +#define ISSTATEIN(v, o) ((v)[o]) +/* some abbreviations; note that some of these know variable names! */ +/* do "if I'm here, I can also be there" etc without branches */ +#define FWD(dst, src, n) ((dst)[here+(n)] |= (src)[here]) +#define BACK(dst, src, n) ((dst)[here-(n)] |= (src)[here]) +#define ISSETBACK(v, n) ((v)[here - (n)]) +/* function names */ +#define LNAMES /* flag */ + +#include "engine.c" + +/* + - regexec - interface for matching + = extern int regexec(const regex_t *, const char *, size_t, \ + = regmatch_t [], int); + = #define REG_NOTBOL 00001 + = #define REG_NOTEOL 00002 + = #define REG_STARTEND 00004 + = #define REG_TRACE 00400 // tracing of execution + = #define REG_LARGE 01000 // force large representation + = #define REG_BACKR 02000 // force use of backref code + * + * We put this here so we can exploit knowledge of the state representation + * when choosing which matcher to call. Also, by this point the matchers + * have been prototyped. + */ +REXTERN +int /* 0 success, REG_NOMATCH failure */ +regexec(preg, string, nmatch, pmatch, eflags) +const regex_t *preg; +const char *string; +size_t nmatch; +regmatch_t pmatch[]; +int eflags; +{ + register struct re_guts *g = preg->re_g; +#ifdef REDEBUG +# define GOODFLAGS(f) (f) +#else +# define GOODFLAGS(f) ((f)&(REG_NOTBOL|REG_NOTEOL|REG_STARTEND)) +#endif + + if (preg->re_magic != MAGIC1 || g->magic != MAGIC2) + return(REG_BADPAT); + assert(!(g->iflags&BAD)); + if (g->iflags&BAD) /* backstop for no-debug case */ + return(REG_BADPAT); + eflags = GOODFLAGS(eflags); + + if (g->nstates <= CHAR_BIT*sizeof(states1) && !(eflags®_LARGE)) + return(smatcher(g, (char *)string, nmatch, pmatch, eflags)); + else + return(lmatcher(g, (char *)string, nmatch, pmatch, eflags)); +} diff --git a/regex/regex/regexmain.c b/regex/regex/regexmain.c new file mode 100644 index 00000000..13a079ce --- /dev/null +++ b/regex/regex/regexmain.c @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include "regex.h" +#include + +#include "regexmain.ih" + +char *progname; +int debug = 0; +int line = 0; +int status = 0; + +int copts = REG_EXTENDED; +int eopts = 0; +regoff_t startoff = 0; +regoff_t endoff = 0; + + +extern int split(); +extern void regprint(); + +/* + - Microsoft Visual C++ 6.0 Standard Edition has no include file that defines getopt(); + */ +#ifdef _MSC_VER +# ifndef getopt + extern int getopt(int argc, char * const argv[], const char *optstring); +# endif +#endif + +/* + - main - do the simple case, hand off to regress() for regression + */ +main(argc, argv) +int argc; +char *argv[]; +{ + regex_t re; +# define NS 10 + regmatch_t subs[NS]; + char erbuf[100]; + int err; + size_t len; + int c; + int errflg = 0; + register int i; + extern int optind; + extern char *optarg; + + progname = argv[0]; + + while ((c = getopt(argc, argv, "c:e:S:E:x")) != EOF) + switch (c) { + case 'c': /* compile options */ + copts = options('c', optarg); + break; + case 'e': /* execute options */ + eopts = options('e', optarg); + break; + case 'S': /* start offset */ + startoff = (regoff_t)atoi(optarg); + break; + case 'E': /* end offset */ + endoff = (regoff_t)atoi(optarg); + break; + case 'x': /* Debugging. */ + debug++; + break; + case '?': + default: + errflg++; + break; + } + if (errflg) { + fprintf(stderr, "usage: %s ", progname); + fprintf(stderr, "[-c copt][-C][-d] [re]\n"); + exit(2); + } + + if (optind >= argc) { + regress(stdin); + exit(status); + } + + err = regcomp(&re, argv[optind++], copts); + if (err) { + len = regerror(err, &re, erbuf, sizeof(erbuf)); + fprintf(stderr, "error %s, %d/%d `%s'\n", + eprint(err), len, sizeof(erbuf), erbuf); + exit(status); + } + regprint(&re, stdout); + + if (optind >= argc) { + regfree(&re); + exit(status); + } + + if (eopts®_STARTEND) { + subs[0].rm_so = startoff; + subs[0].rm_eo = strlen(argv[optind]) - endoff; + } + err = regexec(&re, argv[optind], (size_t)NS, subs, eopts); + if (err) { + len = regerror(err, &re, erbuf, sizeof(erbuf)); + fprintf(stderr, "error %s, %d/%d `%s'\n", + eprint(err), len, sizeof(erbuf), erbuf); + exit(status); + } + if (!(copts®_NOSUB)) { + len = (int)(subs[0].rm_eo - subs[0].rm_so); + if (subs[0].rm_so != -1) { + if (len != 0) + printf("match `%.*s'\n", len, + argv[optind] + subs[0].rm_so); + else + printf("match `'@%.1s\n", + argv[optind] + subs[0].rm_so); + } + for (i = 1; i < NS; i++) + if (subs[i].rm_so != -1) + printf("(%d) `%.*s'\n", i, + (int)(subs[i].rm_eo - subs[i].rm_so), + argv[optind] + subs[i].rm_so); + } + exit(status); +} + +/* + - regress - main loop of regression test + == void regress(FILE *in); + */ +void +regress(in) +FILE *in; +{ + char inbuf[1000]; +# define MAXF 10 + char *f[MAXF]; + int nf; + int i; + char erbuf[100]; + size_t ne; + char *badpat = "invalid regular expression"; +# define SHORT 10 + char *bpname = "REG_BADPAT"; + regex_t re; + + while (fgets(inbuf, sizeof(inbuf), in) != NULL) { + line++; + if (inbuf[0] == '#' || inbuf[0] == '\n') + continue; /* NOTE CONTINUE */ + inbuf[strlen(inbuf)-1] = '\0'; /* get rid of stupid \n */ + if (debug) + fprintf(stdout, "%d:\n", line); + nf = split(inbuf, f, MAXF, "\t\t"); + if (nf < 3) { + fprintf(stderr, "bad input, line %d\n", line); + exit(1); + } + for (i = 0; i < nf; i++) + if (strcmp(f[i], "\"\"") == 0) + f[i] = ""; + if (nf <= 3) + f[3] = NULL; + if (nf <= 4) + f[4] = NULL; + try(f[0], f[1], f[2], f[3], f[4], options('c', f[1])); + if (opt('&', f[1])) /* try with either type of RE */ + try(f[0], f[1], f[2], f[3], f[4], + options('c', f[1]) &~ REG_EXTENDED); + } + + ne = regerror(REG_BADPAT, (regex_t *)NULL, erbuf, sizeof(erbuf)); + if (strcmp(erbuf, badpat) != 0 || ne != strlen(badpat)+1) { + fprintf(stderr, "end: regerror() test gave `%s' not `%s'\n", + erbuf, badpat); + status = 1; + } + ne = regerror(REG_BADPAT, (regex_t *)NULL, erbuf, (size_t)SHORT); + if (strncmp(erbuf, badpat, SHORT-1) != 0 || erbuf[SHORT-1] != '\0' || + ne != strlen(badpat)+1) { + fprintf(stderr, "end: regerror() short test gave `%s' not `%.*s'\n", + erbuf, SHORT-1, badpat); + status = 1; + } + ne = regerror(REG_ITOA|REG_BADPAT, (regex_t *)NULL, erbuf, sizeof(erbuf)); + if (strcmp(erbuf, bpname) != 0 || ne != strlen(bpname)+1) { + fprintf(stderr, "end: regerror() ITOA test gave `%s' not `%s'\n", + erbuf, bpname); + status = 1; + } + re.re_endp = bpname; + ne = regerror(REG_ATOI, &re, erbuf, sizeof(erbuf)); + if (atoi(erbuf) != (int)REG_BADPAT) { + fprintf(stderr, "end: regerror() ATOI test gave `%s' not `%ld'\n", + erbuf, (long)REG_BADPAT); + status = 1; + } else if (ne != strlen(erbuf)+1) { + fprintf(stderr, "end: regerror() ATOI test len(`%s') = %ld\n", + erbuf, (long)REG_BADPAT); + status = 1; + } +} + +/* + - try - try it, and report on problems + == void try(char *f0, char *f1, char *f2, char *f3, char *f4, int opts); + */ +void +try(f0, f1, f2, f3, f4, opts) +char *f0; +char *f1; +char *f2; +char *f3; +char *f4; +int opts; /* may not match f1 */ +{ + regex_t re; +# define NSUBS 10 + regmatch_t subs[NSUBS]; +# define NSHOULD 15 + char *should[NSHOULD]; + int nshould; + char erbuf[100]; + int err; + int len; + char *type = (opts & REG_EXTENDED) ? "ERE" : "BRE"; + register int i; + char *grump; + char f0copy[1000]; + char f2copy[1000]; + + strcpy(f0copy, f0); + re.re_endp = (opts®_PEND) ? f0copy + strlen(f0copy) : NULL; + fixstr(f0copy); + err = regcomp(&re, f0copy, opts); + if (err != 0 && (!opt('C', f1) || err != efind(f2))) { + /* unexpected error or wrong error */ + len = regerror(err, &re, erbuf, sizeof(erbuf)); + fprintf(stderr, "%d: %s error %s, %d/%d `%s'\n", + line, type, eprint(err), len, + sizeof(erbuf), erbuf); + status = 1; + } else if (err == 0 && opt('C', f1)) { + /* unexpected success */ + fprintf(stderr, "%d: %s should have given REG_%s\n", + line, type, f2); + status = 1; + err = 1; /* so we won't try regexec */ + } + + if (err != 0) { + regfree(&re); + return; + } + + strcpy(f2copy, f2); + fixstr(f2copy); + + if (options('e', f1)®_STARTEND) { + if (strchr(f2, '(') == NULL || strchr(f2, ')') == NULL) + fprintf(stderr, "%d: bad STARTEND syntax\n", line); + subs[0].rm_so = strchr(f2, '(') - f2 + 1; + subs[0].rm_eo = strchr(f2, ')') - f2; + } + err = regexec(&re, f2copy, NSUBS, subs, options('e', f1)); + + if (err != 0 && (f3 != NULL || err != REG_NOMATCH)) { + /* unexpected error or wrong error */ + len = regerror(err, &re, erbuf, sizeof(erbuf)); + fprintf(stderr, "%d: %s exec error %s, %d/%d `%s'\n", + line, type, eprint(err), len, + sizeof(erbuf), erbuf); + status = 1; + } else if (err != 0) { + /* nothing more to check */ + } else if (f3 == NULL) { + /* unexpected success */ + fprintf(stderr, "%d: %s exec should have failed\n", + line, type); + status = 1; + err = 1; /* just on principle */ + } else if (opts®_NOSUB) { + /* nothing more to check */ + } else if ((grump = check(f2, subs[0], f3)) != NULL) { + fprintf(stderr, "%d: %s %s\n", line, type, grump); + status = 1; + err = 1; + } + + if (err != 0 || f4 == NULL) { + regfree(&re); + return; + } + + for (i = 1; i < NSHOULD; i++) + should[i] = NULL; + nshould = split(f4, should+1, NSHOULD-1, ","); + if (nshould == 0) { + nshould = 1; + should[1] = ""; + } + for (i = 1; i < NSUBS; i++) { + grump = check(f2, subs[i], should[i]); + if (grump != NULL) { + fprintf(stderr, "%d: %s $%d %s\n", line, + type, i, grump); + status = 1; + err = 1; + } + } + + regfree(&re); +} + +/* + - options - pick options out of a regression-test string + == int options(int type, char *s); + */ +int +options(type, s) +int type; /* 'c' compile, 'e' exec */ +char *s; +{ + register char *p; + register int o = (type == 'c') ? copts : eopts; + register char *legal = (type == 'c') ? "bisnmp" : "^$#tl"; + + for (p = s; *p != '\0'; p++) + if (strchr(legal, *p) != NULL) + switch (*p) { + case 'b': + o &= ~REG_EXTENDED; + break; + case 'i': + o |= REG_ICASE; + break; + case 's': + o |= REG_NOSUB; + break; + case 'n': + o |= REG_NEWLINE; + break; + case 'm': + o &= ~REG_EXTENDED; + o |= REG_NOSPEC; + break; + case 'p': + o |= REG_PEND; + break; + case '^': + o |= REG_NOTBOL; + break; + case '$': + o |= REG_NOTEOL; + break; + case '#': + o |= REG_STARTEND; + break; + case 't': /* trace */ + o |= REG_TRACE; + break; + case 'l': /* force long representation */ + o |= REG_LARGE; + break; + case 'r': /* force backref use */ + o |= REG_BACKR; + break; + } + return(o); +} + +/* + - opt - is a particular option in a regression string? + == int opt(int c, char *s); + */ +int /* predicate */ +opt(c, s) +int c; +char *s; +{ + return(strchr(s, c) != NULL); +} + +/* + - fixstr - transform magic characters in strings + == void fixstr(register char *p); + */ +void +fixstr(p) +register char *p; +{ + if (p == NULL) + return; + + for (; *p != '\0'; p++) + if (*p == 'N') + *p = '\n'; + else if (*p == 'T') + *p = '\t'; + else if (*p == 'S') + *p = ' '; + else if (*p == 'Z') + *p = '\0'; +} + +/* + - check - check a substring match + == char *check(char *str, regmatch_t sub, char *should); + */ +char * /* NULL or complaint */ +check(str, sub, should) +char *str; +regmatch_t sub; +char *should; +{ + register int len; + register int shlen; + register char *p; + static char grump[500]; + register char *at = NULL; + + if (should != NULL && strcmp(should, "-") == 0) + should = NULL; + if (should != NULL && should[0] == '@') { + at = should + 1; + should = ""; + } + + /* check rm_so and rm_eo for consistency */ + if (sub.rm_so > sub.rm_eo || (sub.rm_so == -1 && sub.rm_eo != -1) || + (sub.rm_so != -1 && sub.rm_eo == -1) || + (sub.rm_so != -1 && sub.rm_so < 0) || + (sub.rm_eo != -1 && sub.rm_eo < 0) ) { + sprintf(grump, "start %ld end %ld", (long)sub.rm_so, + (long)sub.rm_eo); + return(grump); + } + + /* check for no match */ + if (sub.rm_so == -1 && should == NULL) + return(NULL); + if (sub.rm_so == -1) + return("did not match"); + + /* check for in range */ + if (sub.rm_eo > (long) strlen(str)) { + sprintf(grump, "start %ld end %ld, past end of string", + (long)sub.rm_so, (long)sub.rm_eo); + return(grump); + } + + len = (int)(sub.rm_eo - sub.rm_so); + shlen = (int)strlen(should); + p = str + sub.rm_so; + + /* check for not supposed to match */ + if (should == NULL) { + sprintf(grump, "matched `%.*s'", len, p); + return(grump); + } + + /* check for wrong match */ + if (len != shlen || strncmp(p, should, (size_t)shlen) != 0) { + sprintf(grump, "matched `%.*s' instead", len, p); + return(grump); + } + if (shlen > 0) + return(NULL); + + /* check null match in right place */ + if (at == NULL) + return(NULL); + shlen = strlen(at); + if (shlen == 0) + shlen = 1; /* force check for end-of-string */ + if (strncmp(p, at, shlen) != 0) { + sprintf(grump, "matched null at `%.20s'", p); + return(grump); + } + return(NULL); +} + +/* + - eprint - convert error number to name + == static char *eprint(int err); + */ +static char * +eprint(err) +int err; +{ + static char epbuf[100]; + size_t len = regerror(REG_ITOA|err, (regex_t *)NULL, epbuf, sizeof(epbuf)); + assert(len <= sizeof(epbuf)); + return(epbuf); +} + +/* + - efind - convert error name to number + == static int efind(char *name); + */ +static int +efind(name) +char *name; +{ + static char efbuf[100]; + regex_t re; + + sprintf(efbuf, "REG_%s", name); + assert(strlen(efbuf) < sizeof(efbuf)); + re.re_endp = efbuf; + (void) regerror(REG_ATOI, &re, efbuf, sizeof(efbuf)); + return(atoi(efbuf)); +} diff --git a/regex/regex/regexmain.ih b/regex/regex/regexmain.ih new file mode 100644 index 00000000..1ec56a25 --- /dev/null +++ b/regex/regex/regexmain.ih @@ -0,0 +1,19 @@ +/* ========= begin header generated by ./mkh ========= */ +#ifdef __cplusplus +extern "C" { +#endif + +/* === regexmain.c === */ +void regress(FILE *in); +void try(char *f0, char *f1, char *f2, char *f3, char *f4, int opts); +int options(int type, char *s); +int opt(int c, char *s); +void fixstr(register char *p); +char *check(char *str, regmatch_t sub, char *should); +static char *eprint(int err); +static int efind(char *name); + +#ifdef __cplusplus +} +#endif +/* ========= end header generated by ./mkh ========= */ diff --git a/regex/regex/regfree.c b/regex/regex/regfree.c new file mode 100644 index 00000000..4c4b91bf --- /dev/null +++ b/regex/regex/regfree.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include "regex.h" + +#include "utils.h" +#include "regex2.h" + +/* + - regfree - free everything + = extern void regfree(regex_t *); + */ +REXTERN +void +regfree(preg) +regex_t *preg; +{ + register struct re_guts *g; + + if (preg->re_magic != MAGIC1) /* oops */ + return; /* nice to complain, but hard */ + + g = preg->re_g; + if (g == NULL || g->magic != MAGIC2) /* oops again */ + return; + preg->re_magic = 0; /* mark it invalid */ + g->magic = 0; /* mark it invalid */ + + if (g->strip != NULL) + free((char *)g->strip); + if (g->sets != NULL) + free((char *)g->sets); + if (g->setbits != NULL) + free((char *)g->setbits); + if (g->must != NULL) + free(g->must); + free((char *)g); +} diff --git a/regex/regex/split.c b/regex/regex/split.c new file mode 100644 index 00000000..188bdb77 --- /dev/null +++ b/regex/regex/split.c @@ -0,0 +1,316 @@ +#include +#include + +/* + - split - divide a string into fields, like awk split() + = int split(char *string, char *fields[], int nfields, char *sep); + */ +int /* number of fields, including overflow */ +split(string, fields, nfields, sep) +char *string; +char *fields[]; /* list is not NULL-terminated */ +int nfields; /* number of entries available in fields[] */ +char *sep; /* "" white, "c" single char, "ab" [ab]+ */ +{ + register char *p = string; + register char c; /* latest character */ + register char sepc = sep[0]; + register char sepc2; + register int fn; + register char **fp = fields; + register char *sepp; + register int trimtrail; + + /* white space */ + if (sepc == '\0') { + while ((c = *p++) == ' ' || c == '\t') + continue; + p--; + trimtrail = 1; + sep = " \t"; /* note, code below knows this is 2 long */ + sepc = ' '; + } else + trimtrail = 0; + sepc2 = sep[1]; /* now we can safely pick this up */ + + /* catch empties */ + if (*p == '\0') + return(0); + + /* single separator */ + if (sepc2 == '\0') { + fn = nfields; + for (;;) { + *fp++ = p; + fn--; + if (fn == 0) + break; + while ((c = *p++) != sepc) + if (c == '\0') + return(nfields - fn); + *(p-1) = '\0'; + } + /* we have overflowed the fields vector -- just count them */ + fn = nfields; + for (;;) { + while ((c = *p++) != sepc) + if (c == '\0') + return(fn); + fn++; + } + /* not reached */ + } + + /* two separators */ + if (sep[2] == '\0') { + fn = nfields; + for (;;) { + *fp++ = p; + fn--; + while ((c = *p++) != sepc && c != sepc2) + if (c == '\0') { + if (trimtrail && **(fp-1) == '\0') + fn++; + return(nfields - fn); + } + if (fn == 0) + break; + *(p-1) = '\0'; + while ((c = *p++) == sepc || c == sepc2) + continue; + p--; + } + /* we have overflowed the fields vector -- just count them */ + fn = nfields; + while (c != '\0') { + while ((c = *p++) == sepc || c == sepc2) + continue; + p--; + fn++; + while ((c = *p++) != '\0' && c != sepc && c != sepc2) + continue; + } + /* might have to trim trailing white space */ + if (trimtrail) { + p--; + while ((c = *--p) == sepc || c == sepc2) + continue; + p++; + if (*p != '\0') { + if (fn == nfields+1) + *p = '\0'; + fn--; + } + } + return(fn); + } + + /* n separators */ + fn = 0; + for (;;) { + if (fn < nfields) + *fp++ = p; + fn++; + for (;;) { + c = *p++; + if (c == '\0') + return(fn); + sepp = sep; + while ((sepc = *sepp++) != '\0' && sepc != c) + continue; + if (sepc != '\0') /* it was a separator */ + break; + } + if (fn < nfields) + *(p-1) = '\0'; + for (;;) { + c = *p++; + sepp = sep; + while ((sepc = *sepp++) != '\0' && sepc != c) + continue; + if (sepc == '\0') /* it wasn't a separator */ + break; + } + p--; + } + + /* not reached */ +} + +#ifdef TEST_SPLIT + + +/* + * test program + * pgm runs regression + * pgm sep splits stdin lines by sep + * pgm str sep splits str by sep + * pgm str sep n splits str by sep n times + */ +int +main(argc, argv) +int argc; +char *argv[]; +{ + char buf[512]; + register int n; +# define MNF 10 + char *fields[MNF]; + + if (argc > 4) + for (n = atoi(argv[3]); n > 0; n--) { + (void) strcpy(buf, argv[1]); + } + else if (argc > 3) + for (n = atoi(argv[3]); n > 0; n--) { + (void) strcpy(buf, argv[1]); + (void) split(buf, fields, MNF, argv[2]); + } + else if (argc > 2) + dosplit(argv[1], argv[2]); + else if (argc > 1) + while (fgets(buf, sizeof(buf), stdin) != NULL) { + buf[strlen(buf)-1] = '\0'; /* stomp newline */ + dosplit(buf, argv[1]); + } + else + regress(); + + exit(0); +} + +dosplit(string, seps) +char *string; +char *seps; +{ +# define NF 5 + char *fields[NF]; + register int nf; + + nf = split(string, fields, NF, seps); + print(nf, NF, fields); +} + +print(nf, nfp, fields) +int nf; +int nfp; +char *fields[]; +{ + register int fn; + register int bound; + + bound = (nf > nfp) ? nfp : nf; + printf("%d:\t", nf); + for (fn = 0; fn < bound; fn++) + printf("\"%s\"%s", fields[fn], (fn+1 < nf) ? ", " : "\n"); +} + +#define RNF 5 /* some table entries know this */ +struct { + char *str; + char *seps; + int nf; + char *fi[RNF]; +} tests[] = { + "", " ", 0, { "" }, + " ", " ", 2, { "", "" }, + "x", " ", 1, { "x" }, + "xy", " ", 1, { "xy" }, + "x y", " ", 2, { "x", "y" }, + "abc def g ", " ", 5, { "abc", "def", "", "g", "" }, + " a bcd", " ", 4, { "", "", "a", "bcd" }, + "a b c d e f", " ", 6, { "a", "b", "c", "d", "e f" }, + " a b c d ", " ", 6, { "", "a", "b", "c", "d " }, + + "", " _", 0, { "" }, + " ", " _", 2, { "", "" }, + "x", " _", 1, { "x" }, + "x y", " _", 2, { "x", "y" }, + "ab _ cd", " _", 2, { "ab", "cd" }, + " a_b c ", " _", 5, { "", "a", "b", "c", "" }, + "a b c_d e f", " _", 6, { "a", "b", "c", "d", "e f" }, + " a b c d ", " _", 6, { "", "a", "b", "c", "d " }, + + "", " _~", 0, { "" }, + " ", " _~", 2, { "", "" }, + "x", " _~", 1, { "x" }, + "x y", " _~", 2, { "x", "y" }, + "ab _~ cd", " _~", 2, { "ab", "cd" }, + " a_b c~", " _~", 5, { "", "a", "b", "c", "" }, + "a b_c d~e f", " _~", 6, { "a", "b", "c", "d", "e f" }, + "~a b c d ", " _~", 6, { "", "a", "b", "c", "d " }, + + "", " _~-", 0, { "" }, + " ", " _~-", 2, { "", "" }, + "x", " _~-", 1, { "x" }, + "x y", " _~-", 2, { "x", "y" }, + "ab _~- cd", " _~-", 2, { "ab", "cd" }, + " a_b c~", " _~-", 5, { "", "a", "b", "c", "" }, + "a b_c-d~e f", " _~-", 6, { "a", "b", "c", "d", "e f" }, + "~a-b c d ", " _~-", 6, { "", "a", "b", "c", "d " }, + + "", " ", 0, { "" }, + " ", " ", 2, { "", "" }, + "x", " ", 1, { "x" }, + "xy", " ", 1, { "xy" }, + "x y", " ", 2, { "x", "y" }, + "abc def g ", " ", 4, { "abc", "def", "g", "" }, + " a bcd", " ", 3, { "", "a", "bcd" }, + "a b c d e f", " ", 6, { "a", "b", "c", "d", "e f" }, + " a b c d ", " ", 6, { "", "a", "b", "c", "d " }, + + "", "", 0, { "" }, + " ", "", 0, { "" }, + "x", "", 1, { "x" }, + "xy", "", 1, { "xy" }, + "x y", "", 2, { "x", "y" }, + "abc def g ", "", 3, { "abc", "def", "g" }, + "\t a bcd", "", 2, { "a", "bcd" }, + " a \tb\t c ", "", 3, { "a", "b", "c" }, + "a b c d e ", "", 5, { "a", "b", "c", "d", "e" }, + "a b\tc d e f", "", 6, { "a", "b", "c", "d", "e f" }, + " a b c d e f ", "", 6, { "a", "b", "c", "d", "e f " }, + + NULL, NULL, 0, { NULL }, +}; + +regress() +{ + char buf[512]; + register int n; + char *fields[RNF+1]; + register int nf; + register int i; + register int printit; + register char *f; + + for (n = 0; tests[n].str != NULL; n++) { + (void) strcpy(buf, tests[n].str); + fields[RNF] = NULL; + nf = split(buf, fields, RNF, tests[n].seps); + printit = 0; + if (nf != tests[n].nf) { + printf("split `%s' by `%s' gave %d fields, not %d\n", + tests[n].str, tests[n].seps, nf, tests[n].nf); + printit = 1; + } else if (fields[RNF] != NULL) { + printf("split() went beyond array end\n"); + printit = 1; + } else { + for (i = 0; i < nf && i < RNF; i++) { + f = fields[i]; + if (f == NULL) + f = "(NULL)"; + if (strcmp(f, tests[n].fi[i]) != 0) { + printf("split `%s' by `%s', field %d is `%s', not `%s'\n", + tests[n].str, tests[n].seps, + i, fields[i], tests[n].fi[i]); + printit = 1; + } + } + } + if (printit) + print(nf, RNF, fields); + } +} +#endif diff --git a/regex/regex/tests b/regex/regex/tests new file mode 100644 index 00000000..e4d928da --- /dev/null +++ b/regex/regex/tests @@ -0,0 +1,477 @@ +# regular expression test set +# Lines are at least three fields, separated by one or more tabs. "" stands +# for an empty field. First field is an RE. Second field is flags. If +# C flag given, regcomp() is expected to fail, and the third field is the +# error name (minus the leading REG_). +# +# Otherwise it is expected to succeed, and the third field is the string to +# try matching it against. If there is no fourth field, the match is +# expected to fail. If there is a fourth field, it is the substring that +# the RE is expected to match. If there is a fifth field, it is a comma- +# separated list of what the subexpressions should match, with - indicating +# no match for that one. In both the fourth and fifth fields, a (sub)field +# starting with @ indicates that the (sub)expression is expected to match +# a null string followed by the stuff after the @; this provides a way to +# test where null strings match. The character `N' in REs and strings +# is newline, `S' is space, `T' is tab, `Z' is NUL. +# +# The full list of flags: +# - placeholder, does nothing +# b RE is a BRE, not an ERE +# & try it as both an ERE and a BRE +# C regcomp() error expected, third field is error name +# i REG_ICASE +# m ("mundane") REG_NOSPEC +# s REG_NOSUB (not really testable) +# n REG_NEWLINE +# ^ REG_NOTBOL +# $ REG_NOTEOL +# # REG_STARTEND (see below) +# p REG_PEND +# +# For REG_STARTEND, the start/end offsets are those of the substring +# enclosed in (). + +# basics +a & a a +abc & abc abc +abc|de - abc abc +a|b|c - abc a + +# parentheses and perversions thereof +a(b)c - abc abc +a\(b\)c b abc abc +a( C EPAREN +a( b a( a( +a\( - a( a( +a\( bC EPAREN +a\(b bC EPAREN +a(b C EPAREN +a(b b a(b a(b +# gag me with a right parenthesis -- 1003.2 goofed here (my fault, partly) +a) - a) a) +) - ) ) +# end gagging (in a just world, those *should* give EPAREN) +a) b a) a) +a\) bC EPAREN +\) bC EPAREN +a()b - ab ab +a\(\)b b ab ab + +# anchoring and REG_NEWLINE +^abc$ & abc abc +a^b - a^b +a^b b a^b a^b +a$b - a$b +a$b b a$b a$b +^ & abc @abc +$ & abc @ +^$ & "" @ +$^ - "" @ +\($\)\(^\) b "" @ +# stop retching, those are legitimate (although disgusting) +^^ - "" @ +$$ - "" @ +b$ & abNc +b$ &n abNc b +^b$ & aNbNc +^b$ &n aNbNc b +^$ &n aNNb @Nb +^$ n abc +^$ n abcN @ +$^ n aNNb @Nb +\($\)\(^\) bn aNNb @Nb +^^ n^ aNNb @Nb +$$ n aNNb @NN +^a ^ a +a$ $ a +^a ^n aNb +^b ^n aNb b +a$ $n bNa +b$ $n bNa b +a*(^b$)c* - b b +a*\(^b$\)c* b b b + +# certain syntax errors and non-errors +| C EMPTY +| b | | +* C BADRPT +* b * * ++ C BADRPT +? C BADRPT +"" &C EMPTY +() - abc @abc +\(\) b abc @abc +a||b C EMPTY +|ab C EMPTY +ab| C EMPTY +(|a)b C EMPTY +(a|)b C EMPTY +(*a) C BADRPT +(+a) C BADRPT +(?a) C BADRPT +({1}a) C BADRPT +\(\{1\}a\) bC BADRPT +(a|*b) C BADRPT +(a|+b) C BADRPT +(a|?b) C BADRPT +(a|{1}b) C BADRPT +^* C BADRPT +^* b * * +^+ C BADRPT +^? C BADRPT +^{1} C BADRPT +^\{1\} bC BADRPT + +# metacharacters, backslashes +a.c & abc abc +a[bc]d & abd abd +a\*c & a*c a*c +a\\b & a\b a\b +a\\\*b & a\*b a\*b +a\bc & abc abc +a\ &C EESCAPE +a\\bc & a\bc a\bc +\{ bC BADRPT +a\[b & a[b a[b +a[b &C EBRACK +# trailing $ is a peculiar special case for the BRE code +a$ & a a +a$ & a$ +a\$ & a +a\$ & a$ a$ +a\\$ & a +a\\$ & a$ +a\\$ & a\$ +a\\$ & a\ a\ + +# back references, ugh +a\(b\)\2c bC ESUBREG +a\(b\1\)c bC ESUBREG +a\(b*\)c\1d b abbcbbd abbcbbd bb +a\(b*\)c\1d b abbcbd +a\(b*\)c\1d b abbcbbbd +^\(.\)\1 b abc +a\([bc]\)\1d b abcdabbd abbd b +a\(\([bc]\)\2\)*d b abbccd abbccd +a\(\([bc]\)\2\)*d b abbcbd +# actually, this next one probably ought to fail, but the spec is unclear +a\(\(b\)*\2\)*d b abbbd abbbd +# here is a case that no NFA implementation does right +\(ab*\)[ab]*\1 b ababaaa ababaaa a +# check out normal matching in the presence of back refs +\(a\)\1bcd b aabcd aabcd +\(a\)\1bc*d b aabcd aabcd +\(a\)\1bc*d b aabd aabd +\(a\)\1bc*d b aabcccd aabcccd +\(a\)\1bc*[ce]d b aabcccd aabcccd +^\(a\)\1b\(c\)*cd$ b aabcccd aabcccd + +# ordinary repetitions +ab*c & abc abc +ab+c - abc abc +ab?c - abc abc +a\(*\)b b a*b a*b +a\(**\)b b ab ab +a\(***\)b bC BADRPT +*a b *a *a +**a b a a +***a bC BADRPT + +# the dreaded bounded repetitions +{ & { { +{abc & {abc {abc +{1 C BADRPT +{1} C BADRPT +a{b & a{b a{b +a{1}b - ab ab +a\{1\}b b ab ab +a{1,}b - ab ab +a\{1,\}b b ab ab +a{1,2}b - aab aab +a\{1,2\}b b aab aab +a{1 C EBRACE +a\{1 bC EBRACE +a{1a C EBRACE +a\{1a bC EBRACE +a{1a} C BADBR +a\{1a\} bC BADBR +a{,2} - a{,2} a{,2} +a\{,2\} bC BADBR +a{,} - a{,} a{,} +a\{,\} bC BADBR +a{1,x} C BADBR +a\{1,x\} bC BADBR +a{1,x C EBRACE +a\{1,x bC EBRACE +a{300} C BADBR +a\{300\} bC BADBR +a{1,0} C BADBR +a\{1,0\} bC BADBR +ab{0,0}c - abcac ac +ab\{0,0\}c b abcac ac +ab{0,1}c - abcac abc +ab\{0,1\}c b abcac abc +ab{0,3}c - abbcac abbc +ab\{0,3\}c b abbcac abbc +ab{1,1}c - acabc abc +ab\{1,1\}c b acabc abc +ab{1,3}c - acabc abc +ab\{1,3\}c b acabc abc +ab{2,2}c - abcabbc abbc +ab\{2,2\}c b abcabbc abbc +ab{2,4}c - abcabbc abbc +ab\{2,4\}c b abcabbc abbc +((a{1,10}){1,10}){1,10} - a a a,a + +# multiple repetitions +a** &C BADRPT +a++ C BADRPT +a?? C BADRPT +a*+ C BADRPT +a*? C BADRPT +a+* C BADRPT +a+? C BADRPT +a?* C BADRPT +a?+ C BADRPT +a{1}{1} C BADRPT +a*{1} C BADRPT +a+{1} C BADRPT +a?{1} C BADRPT +a{1}* C BADRPT +a{1}+ C BADRPT +a{1}? C BADRPT +a*{b} - a{b} a{b} +a\{1\}\{1\} bC BADRPT +a*\{1\} bC BADRPT +a\{1\}* bC BADRPT + +# brackets, and numerous perversions thereof +a[b]c & abc abc +a[ab]c & abc abc +a[^ab]c & adc adc +a[]b]c & a]c a]c +a[[b]c & a[c a[c +a[-b]c & a-c a-c +a[^]b]c & adc adc +a[^-b]c & adc adc +a[b-]c & a-c a-c +a[b &C EBRACK +a[] &C EBRACK +a[1-3]c & a2c a2c +a[3-1]c &C ERANGE +a[1-3-5]c &C ERANGE +a[[.-.]--]c & a-c a-c +a[1- &C ERANGE +a[[. &C EBRACK +a[[.x &C EBRACK +a[[.x. &C EBRACK +a[[.x.] &C EBRACK +a[[.x.]] & ax ax +a[[.x,.]] &C ECOLLATE +a[[.one.]]b & a1b a1b +a[[.notdef.]]b &C ECOLLATE +a[[.].]]b & a]b a]b +a[[:alpha:]]c & abc abc +a[[:notdef:]]c &C ECTYPE +a[[: &C EBRACK +a[[:alpha &C EBRACK +a[[:alpha:] &C EBRACK +a[[:alpha,:] &C ECTYPE +a[[:]:]]b &C ECTYPE +a[[:-:]]b &C ECTYPE +a[[:alph:]] &C ECTYPE +a[[:alphabet:]] &C ECTYPE +[[:alnum:]]+ - -%@a0X- a0X +[[:alpha:]]+ - -%@aX0- aX +[[:blank:]]+ - aSSTb SST +[[:cntrl:]]+ - aNTb NT +[[:digit:]]+ - a019b 019 +[[:graph:]]+ - Sa%bS a%b +[[:lower:]]+ - AabC ab +[[:print:]]+ - NaSbN aSb +[[:punct:]]+ - S%-&T %-& +[[:space:]]+ - aSNTb SNT +[[:upper:]]+ - aBCd BC +[[:xdigit:]]+ - p0f3Cq 0f3C +a[[=b=]]c & abc abc +a[[= &C EBRACK +a[[=b &C EBRACK +a[[=b= &C EBRACK +a[[=b=] &C EBRACK +a[[=b,=]] &C ECOLLATE +a[[=one=]]b & a1b a1b + +# complexities +a(((b)))c - abc abc +a(b|(c))d - abd abd +a(b*|c)d - abbd abbd +# just gotta have one DFA-buster, of course +a[ab]{20} - aaaaabaaaabaaaabaaaab aaaaabaaaabaaaabaaaab +# and an inline expansion in case somebody gets tricky +a[ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab] - aaaaabaaaabaaaabaaaab aaaaabaaaabaaaabaaaab +# and in case somebody just slips in an NFA... +a[ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab][ab](wee|week)(knights|night) - aaaaabaaaabaaaabaaaabweeknights aaaaabaaaabaaaabaaaabweeknights +# fish for anomalies as the number of states passes 32 +12345678901234567890123456789 - a12345678901234567890123456789b 12345678901234567890123456789 +123456789012345678901234567890 - a123456789012345678901234567890b 123456789012345678901234567890 +1234567890123456789012345678901 - a1234567890123456789012345678901b 1234567890123456789012345678901 +12345678901234567890123456789012 - a12345678901234567890123456789012b 12345678901234567890123456789012 +123456789012345678901234567890123 - a123456789012345678901234567890123b 123456789012345678901234567890123 +# and one really big one, beyond any plausible word width +1234567890123456789012345678901234567890123456789012345678901234567890 - a1234567890123456789012345678901234567890123456789012345678901234567890b 1234567890123456789012345678901234567890123456789012345678901234567890 +# fish for problems as brackets go past 8 +[ab][cd][ef][gh][ij][kl][mn] - xacegikmoq acegikm +[ab][cd][ef][gh][ij][kl][mn][op] - xacegikmoq acegikmo +[ab][cd][ef][gh][ij][kl][mn][op][qr] - xacegikmoqy acegikmoq +[ab][cd][ef][gh][ij][kl][mn][op][q] - xacegikmoqy acegikmoq + +# subtleties of matching +abc & xabcy abc +a\(b\)?c\1d b acd +aBc i Abc Abc +a[Bc]*d i abBCcd abBCcd +0[[:upper:]]1 &i 0a1 0a1 +0[[:lower:]]1 &i 0A1 0A1 +a[^b]c &i abc +a[^b]c &i aBc +a[^b]c &i adc adc +[a]b[c] - abc abc +[a]b[a] - aba aba +[abc]b[abc] - abc abc +[abc]b[abd] - abd abd +a(b?c)+d - accd accd +(wee|week)(knights|night) - weeknights weeknights +(we|wee|week|frob)(knights|night|day) - weeknights weeknights +a[bc]d - xyzaaabcaababdacd abd +a[ab]c - aaabc abc +abc s abc abc +a* & b @b + +# Let's have some fun -- try to match a C comment. +# first the obvious, which looks okay at first glance... +/\*.*\*/ - /*x*/ /*x*/ +# but... +/\*.*\*/ - /*x*/y/*z*/ /*x*/y/*z*/ +# okay, we must not match */ inside; try to do that... +/\*([^*]|\*[^/])*\*/ - /*x*/ /*x*/ +/\*([^*]|\*[^/])*\*/ - /*x*/y/*z*/ /*x*/ +# but... +/\*([^*]|\*[^/])*\*/ - /*x**/y/*z*/ /*x**/y/*z*/ +# and a still fancier version, which does it right (I think)... +/\*([^*]|\*+[^*/])*\*+/ - /*x*/ /*x*/ +/\*([^*]|\*+[^*/])*\*+/ - /*x*/y/*z*/ /*x*/ +/\*([^*]|\*+[^*/])*\*+/ - /*x**/y/*z*/ /*x**/ +/\*([^*]|\*+[^*/])*\*+/ - /*x****/y/*z*/ /*x****/ +/\*([^*]|\*+[^*/])*\*+/ - /*x**x*/y/*z*/ /*x**x*/ +/\*([^*]|\*+[^*/])*\*+/ - /*x***x/y/*z*/ /*x***x/y/*z*/ + +# subexpressions +.* - abc abc - +a(b)(c)d - abcd abcd b,c +a(((b)))c - abc abc b,b,b +a(b|(c))d - abd abd b,- +a(b*|c|e)d - abbd abbd bb +a(b*|c|e)d - acd acd c +a(b*|c|e)d - ad ad @d +a(b?)c - abc abc b +a(b?)c - ac ac @c +a(b+)c - abc abc b +a(b+)c - abbbc abbbc bbb +a(b*)c - ac ac @c +(a|ab)(bc([de]+)f|cde) - abcdef abcdef a,bcdef,de +# the regression tester only asks for 9 subexpressions +a(b)(c)(d)(e)(f)(g)(h)(i)(j)k - abcdefghijk abcdefghijk b,c,d,e,f,g,h,i,j +a(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)l - abcdefghijkl abcdefghijkl b,c,d,e,f,g,h,i,j,k +a([bc]?)c - abc abc b +a([bc]?)c - ac ac @c +a([bc]+)c - abc abc b +a([bc]+)c - abcc abcc bc +a([bc]+)bc - abcbc abcbc bc +a(bb+|b)b - abb abb b +a(bbb+|bb+|b)b - abb abb b +a(bbb+|bb+|b)b - abbb abbb bb +a(bbb+|bb+|b)bb - abbb abbb b +(.*).* - abcdef abcdef abcdef +(a*)* - bc @b @b + +# do we get the right subexpression when it is used more than once? +a(b|c)*d - ad ad - +a(b|c)*d - abcd abcd c +a(b|c)+d - abd abd b +a(b|c)+d - abcd abcd c +a(b|c?)+d - ad ad @d +a(b|c?)+d - abcd abcd @d +a(b|c){0,0}d - ad ad - +a(b|c){0,1}d - ad ad - +a(b|c){0,1}d - abd abd b +a(b|c){0,2}d - ad ad - +a(b|c){0,2}d - abcd abcd c +a(b|c){0,}d - ad ad - +a(b|c){0,}d - abcd abcd c +a(b|c){1,1}d - abd abd b +a(b|c){1,1}d - acd acd c +a(b|c){1,2}d - abd abd b +a(b|c){1,2}d - abcd abcd c +a(b|c){1,}d - abd abd b +a(b|c){1,}d - abcd abcd c +a(b|c){2,2}d - acbd acbd b +a(b|c){2,2}d - abcd abcd c +a(b|c){2,4}d - abcd abcd c +a(b|c){2,4}d - abcbd abcbd b +a(b|c){2,4}d - abcbcd abcbcd c +a(b|c){2,}d - abcd abcd c +a(b|c){2,}d - abcbd abcbd b +a(b+|((c)*))+d - abd abd @d,@d,- +a(b+|((c)*))+d - abcd abcd @d,@d,- + +# check out the STARTEND option +[abc] &# a(b)c b +[abc] &# a(d)c +[abc] &# a(bc)d b +[abc] &# a(dc)d c +. &# a()c +b.*c &# b(bc)c bc +b.* &# b(bc)c bc +.*c &# b(bc)c bc + +# plain strings, with the NOSPEC flag +abc m abc abc +abc m xabcy abc +abc m xyz +a*b m aba*b a*b +a*b m ab +"" mC EMPTY + +# cases involving NULs +aZb & a a +aZb &p a +aZb &p# (aZb) aZb +aZ*b &p# (ab) ab +a.b &# (aZb) aZb +a.* &# (aZb)c aZb + +# word boundaries (ick) +[[:<:]]a & a a +[[:<:]]a & ba +[[:<:]]a & -a a +a[[:>:]] & a a +a[[:>:]] & ab +a[[:>:]] & a- a +[[:<:]]a.c[[:>:]] & axcd-dayc-dazce-abc abc +[[:<:]]a.c[[:>:]] & axcd-dayc-dazce-abc-q abc +[[:<:]]a.c[[:>:]] & axc-dayc-dazce-abc axc +[[:<:]]b.c[[:>:]] & a_bxc-byc_d-bzc-q bzc +[[:<:]].x..[[:>:]] & y_xa_-_xb_y-_xc_-axdc _xc_ +[[:<:]]a_b[[:>:]] & x_a_b + +# past problems, and suspected problems +(A[1])|(A[2])|(A[3])|(A[4])|(A[5])|(A[6])|(A[7])|(A[8])|(A[9])|(A[A]) - A1 A1 +abcdefghijklmnop i abcdefghijklmnop abcdefghijklmnop +abcdefghijklmnopqrstuv i abcdefghijklmnopqrstuv abcdefghijklmnopqrstuv +(ALAK)|(ALT[AB])|(CC[123]1)|(CM[123]1)|(GAMC)|(LC[23][EO ])|(SEM[1234])|(SL[ES][12])|(SLWW)|(SLF )|(SLDT)|(VWH[12])|(WH[34][EW])|(WP1[ESN]) - CC11 CC11 +CC[13]1|a{21}[23][EO][123][Es][12]a{15}aa[34][EW]aaaaaaa[X]a - CC11 CC11 +Char \([a-z0-9_]*\)\[.* b Char xyz[k Char xyz[k xyz +a?b - ab ab +-\{0,1\}[0-9]*$ b -5 -5 +a*a*a*a*a*a*a* & aaaaaa aaaaaa diff --git a/regex/regex/utils.h b/regex/regex/utils.h new file mode 100644 index 00000000..1a997ac8 --- /dev/null +++ b/regex/regex/utils.h @@ -0,0 +1,22 @@ +/* utility definitions */ +#ifdef _POSIX2_RE_DUP_MAX +#define DUPMAX _POSIX2_RE_DUP_MAX +#else +#define DUPMAX 255 +#endif +#define INFINITY (DUPMAX + 1) +#define NC (CHAR_MAX - CHAR_MIN + 1) +typedef unsigned char uch; + +/* switch off assertions (if not already off) if no REDEBUG */ +#ifndef REDEBUG +#ifndef NDEBUG +#define NDEBUG /* no assertions please */ +#endif +#endif +#include + +/* for old systems with bcopy() but no memmove() */ +#ifdef USEBCOPY +#define memmove(d, s, c) bcopy(s, d, c) +#endif diff --git a/sdlsupport/SDLMessageTransceiverThread.cpp b/sdlsupport/SDLMessageTransceiverThread.cpp new file mode 100644 index 00000000..6685f7f0 --- /dev/null +++ b/sdlsupport/SDLMessageTransceiverThread.cpp @@ -0,0 +1,22 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "sdlsupport/SDLMessageTransceiverThread.h" + +namespace muscle { + +SDLMessageTransceiverThread :: SDLMessageTransceiverThread() : MessageTransceiverThread() +{ + // empty +} + +void SDLMessageTransceiverThread :: SignalOwner() +{ + SDL_Event event; + event.type = SDL_MTT_EVENT; + event.user.code = SDL_MTT_EVENT; + event.user.data1 = NULL; + event.user.data2 = NULL; + SDL_PeepEvents(&event, 1, SDL_ADDEVENT, 0); +} + +}; // end namespace muscle diff --git a/sdlsupport/SDLMessageTransceiverThread.h b/sdlsupport/SDLMessageTransceiverThread.h new file mode 100644 index 00000000..8f442e6c --- /dev/null +++ b/sdlsupport/SDLMessageTransceiverThread.h @@ -0,0 +1,29 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSDLMessageTransceiverThread_h +#define MuscleSDLMessageTransceiverThread_h + +#include +#include "system/MessageTransceiverThread.h" + +namespace muscle { + +static const uint32 SDL_MTT_EVENT = SDL_NUMEVENTS-1; + +/** This class is useful for interfacing a MessageTransceiverThread to the SDL event loop. + * @author Shard + */ +class SDLMessageTransceiverThread : public MessageTransceiverThread +{ +public: + /** Constructor. */ + SDLMessageTransceiverThread(); + +protected: + /** Overridden to send a SDLEvent */ + virtual void SignalOwner(); +}; + +}; // end namespace muscle + +#endif diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 00000000..c1171e86 --- /dev/null +++ b/server/Makefile @@ -0,0 +1,133 @@ +# compilation flags used under any OS or compiler (may be appended to, below) +CXXFLAGS += -I.. -DMUSCLE_SINGLE_THREAD_ONLY +CXXFLAGS += -DMUSCLE_ENABLE_ZLIB_ENCODING +#CXXFLAGS += -DMUSCLE_AVOID_IPV6 +#CXXFLAGS += -DMUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME +#CXXFLAGS += -DMUSCLE_USE_POLL +#CXXFLAGS += -DMUSCLE_USE_KQUEUE +#CXXFLAGS += -DMUSCLE_USE_EPOLL +#CXXFLAGS += -DMUSCLE_ENABLE_SSL + +# Uncomment this if you want to compile with C++11 support enabled +#CXXFLAGS += -std=c++11 -stdlib=libc++ $(CFLAGS) $(DEFINES) -DMUSCLE_USE_CPLUSPLUS11 + +# Uncomment these to enable clang++'s static analyzer (for more thorough error checking) +#CXXFLAGS += --analyze -Xanalyzer -analyzer-output=text +#CFLAGS += --analyze -Xanalyzer -analyzer-output=text + +# compilation flags that are specific to the gcc compiler (hard-coded) +GCCFLAGS = -fno-exceptions -DMUSCLE_NO_EXCEPTIONS -W -Wall -Wno-multichar + +# flags to include when compiling the optimized version (with 'make') +CCOPTFLAGS = -O3 + +# flags to include when linking (set per operating system, below) +LFLAGS = + +# libraries to include when linking (set per operating system, below) +LIBS = + +# names of the executables to compile +EXECUTABLES = muscled admin + +# object files to include in all executables +OBJFILES = Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o AbstractReflectSession.o SignalMultiplexer.o SignalHandlerSession.o DumbReflectSession.o StorageReflectSession.o DataNode.o ReflectServer.o SocketMultiplexer.o StringMatcher.o muscled.o MiscUtilityFunctions.o NetworkUtilityFunctions.o SysLog.o PulseNode.o PathMatcher.o FilterSessionFactory.o RateLimitSessionIOPolicy.o MemoryAllocator.o GlobalMemoryAllocator.o SetupSystem.o ServerComponent.o ZLibCodec.o ByteBuffer.o QueryFilter.o Directory.o FilePathInfo.o +ZLIBOBJS = adler32.o deflate.o trees.o zutil.o inflate.o inftrees.o inffast.o crc32.o compress.o gzclose.o gzread.o gzwrite.o gzlib.o + +# Where to find .cpp files +VPATH = ../message ../besupport ../dataio ../iogateway ../reflector ../regex ../util ../syslog ../system ../zlib ../zlib/zlib + +# if muscled is crashing, try setting this to no; some older compilers can't handle memory tracking! +MEMORY_TRACKING_SUPPORTED = yes + +# if the OS type variable is unset, try to set it using the uname shell command +ifeq ($(OSTYPE),) + OSTYPE = $(strip $(shell uname)) +endif + +# IRIX may report itself as IRIX or as IRIX64. They are both the same to us. +ifeq ($(OSTYPE),IRIX64) + OSTYPE = IRIX +endif + +ifeq ($(OSTYPE),Haiku) + LIBS = -lbe -lnetwork -lroot + CXXFLAGS += -DMUSCLE_AVOID_IPV6 +endif + +ifeq ($(OSTYPE),beos) + ifeq ($(BE_HOST_CPU),ppc) + CXX = mwcc + OBJFILES += regcomp.o regerror.o regexec.o regfree.o + VPATH += ../regex/regex + CFLAGS += -I../regex/regex -I../zlib/zlib + MEMORY_TRACKING_SUPPORTED = no # mwcc can't handle it correctly :^( + else # not ppc + CXXFLAGS += $(GCCFLAGS) $(CCOPTFLAGS) + LIBS = -lbe -lnet -lroot + ifeq ($(shell ls 2>/dev/null -1 /boot/develop/headers/be/bone/bone_api.h), /boot/develop/headers/be/bone/bone_api.h) + CXXFLAGS += -I/boot/develop/headers/be/bone -DBONE + LIBS = -nodefaultlibs -lbind -lsocket -lbe -lroot -L/boot/beos/system/lib + endif + endif +else # not beos + ifneq (,$(findstring g++,$(CXX))) + CXXFLAGS += $(GCCFLAGS) $(CCOPTFLAGS) + else + CXXFLAGS += $(CCOPTFLAGS) + endif +endif + +ifeq ($(OSTYPE),freebsd4.0) + CXXFLAGS += -I/usr/include/machine +endif + +ifeq ($(OSTYPE),DragonFly) + # void +endif + +ifeq ($(OSTYPE),IRIX) + CXXFLAGS += -DSGI -DMIPS + ifneq (,$(findstring g++,$(CXX))) # if we are using SGI's CC compiler, we gotta change a few things + CXX = CC + CCFLAGS = -g2 -n32 -LANG:std -woff 1110,1174,1552,1460,3303 + LFLAGS = -g2 -n32 + CXXFLAGS += -DNEW_H_NOT_AVAILABLE + MEMORY_TRACKING_SUPPORTED = no # SGI CC can't handle it correctly :^( + endif +endif + +ifeq ($(OSTYPE), SunOS) + CXXFLAGS += -DSUN + ifneq ($(CXX),$(findstring g++,$(CXX))) # if we are using the SunPro compilers, we have to change some things + CXX = CC + endif + LIBS = -lsocket -lnsl +endif + +ifeq ($(strip $(MEMORY_TRACKING_SUPPORTED)),yes) + CXXFLAGS += -DMUSCLE_ENABLE_MEMORY_TRACKING +endif + +ifneq (,$(findstring MUSCLE_ENABLE_SSL,$(CXXFLAGS))) # Add OpenSSL-specific files only if OpenSSL is enabled + OBJFILES += SSLSocketDataIO.o SSLSocketAdapterGateway.o + LIBS += -lssl -lcrypto +endif + +all : $(EXECUTABLES) + +debug : CCOPTFLAGS = -g +debug : all + +muscled : $(OBJFILES) $(ZLIBOBJS) + $(CXX) $(LIBS) $(LFLAGS) -o $@ $^ + +admin : Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o NetworkUtilityFunctions.o SysLog.o admin.o PulseNode.o SetupSystem.o ZLibCodec.o ByteBuffer.o GlobalMemoryAllocator.o MiscUtilityFunctions.o Directory.o FilePathInfo.o StringMatcher.o SocketMultiplexer.o $(ZLIBOBJS) + $(CXX) $(LIBS) $(LFLAGS) -o $@ $^ + +libmuscle.a : $(OBJFILES) + $(AR) rcs $@ $^ + +clean : + rm -f *.o *.xSYM $(EXECUTABLES) libmuscle.a + diff --git a/server/admin.cpp b/server/admin.cpp new file mode 100644 index 00000000..918f740d --- /dev/null +++ b/server/admin.cpp @@ -0,0 +1,185 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +static void Kick(MessageIOGateway & gw, const char * arg); +void Kick(MessageIOGateway & gw, const char * arg) +{ + MessageRef msg = GetMessageFromPool(PR_COMMAND_KICK); + if (msg()) + { + String str("/"); + str += arg; + str += "/*"; + msg()->AddString(PR_NAME_KEYS, str); + gw.AddOutgoingMessage(msg); + LogTime(MUSCLE_LOG_INFO, "Kicking users matching pattern [%s]\n", str.Cstr()); + } +} + +static void Ban(MessageIOGateway & gw, const char * arg, bool unBan); +void Ban(MessageIOGateway & gw, const char * arg, bool unBan) +{ + MessageRef msg = GetMessageFromPool(unBan ? PR_COMMAND_REMOVEBANS : PR_COMMAND_ADDBANS); + if (msg()) + { + msg()->AddString(PR_NAME_KEYS, arg); + gw.AddOutgoingMessage(msg); + if (unBan) LogTime(MUSCLE_LOG_INFO, "Removing ban patterns that match pattern [%s]\n", arg); + else LogTime(MUSCLE_LOG_INFO, "Adding ban pattern [%s]\n", arg); + } +} + +static void Require(MessageIOGateway & gw, const char * arg, bool unRequire); +void Require(MessageIOGateway & gw, const char * arg, bool unRequire) +{ + MessageRef msg = GetMessageFromPool(unRequire ? PR_COMMAND_REMOVEREQUIRES : PR_COMMAND_ADDREQUIRES); + if (msg()) + { + msg()->AddString(PR_NAME_KEYS, arg); + gw.AddOutgoingMessage(msg); + if (unRequire) LogTime(MUSCLE_LOG_INFO, "Removing require patterns that match pattern [%s]\n", arg); + else LogTime(MUSCLE_LOG_INFO, "Adding require pattern [%s]\n", arg); + } +} + + +// This is a little admin program, useful for kicking, banning, or unbanning users +// without having to restart the MUSCLE server. Example command line: +// admin server=muscleserver.mycompany.com kick=192.168.0.23 ban=16.25.29.2 kickban=1.2.3.4 unban=1.2.3.4 ban=2.3.4.5 ban=3.4.5.* +// Note that you can only do this if your IP address has the requisite privileges on +// the MUSCLE server! (i.e. to ban, the server must have been run with an argument like privban=your.ip.address) +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + const char * hostName = "localhost"; + + // First, find out if there is a server specified. + for (int i=1; iGetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + + if (multiplexer.WaitForEvents() < 0) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "WaitForEvents() failed, exiting!\n"); + errorCount++; + break; + } + + bool readError = ((multiplexer.IsSocketReadyForRead(fd))&&(gw.DoInput(inQueue) < 0)); + bool writeError = ((multiplexer.IsSocketReadyForWrite(fd))&&(gw.DoOutput() < 0)); + if ((readError)||(writeError)) + { + LogTime(MUSCLE_LOG_ERROR, "TCP connection was cut prematurely!\n"); + errorCount++; + break; + } + + MessageRef incoming; + while(inQueue.RemoveHead(incoming) == B_NO_ERROR) + { + const Message * msg = incoming(); + if (msg) + { + switch(msg->what) + { + case PR_RESULT_PONG: + s.Reset(); + break; + + case PR_RESULT_ERRORACCESSDENIED: + { + errorCount++; + MessageRef subMsg; + LogTime(MUSCLE_LOG_ERROR, "Access denied! "); + const char * who; + if ((msg->FindMessage(PR_NAME_REJECTED_MESSAGE, subMsg) == B_NO_ERROR)&&(subMsg()->FindString(PR_NAME_KEYS, &who) == B_NO_ERROR)) + { + const char * action = "do that to"; + switch(subMsg() ? subMsg()->what : 0) + { + case PR_COMMAND_KICK: action = "kick"; break; + case PR_COMMAND_ADDBANS: action = "ban"; break; + case PR_COMMAND_REMOVEBANS: action = "unban"; break; + case PR_COMMAND_ADDREQUIRES: action = "require"; break; + case PR_COMMAND_REMOVEREQUIRES: action = "unrequire"; break; + } + Log(MUSCLE_LOG_ERROR, "You are not allowed to %s [%s]!", action, who); + } + Log(MUSCLE_LOG_ERROR, "\n"); + } + break; + } + } + } + } + LogTime(MUSCLE_LOG_INFO, "Exiting. (" UINT32_FORMAT_SPEC" errors)\n", errorCount); + return 0; +} diff --git a/server/muscled.cpp b/server/muscled.cpp new file mode 100644 index 00000000..6b27e46d --- /dev/null +++ b/server/muscled.cpp @@ -0,0 +1,342 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_SSL +# include "dataio/FileDataIO.h" +#endif + +#include "reflector/ReflectServer.h" +#include "reflector/DumbReflectSession.h" +#include "reflector/StorageReflectSession.h" +#include "reflector/FilterSessionFactory.h" +#include "reflector/RateLimitSessionIOPolicy.h" +#include "reflector/SignalHandlerSession.h" +#include "system/GlobalMemoryAllocator.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" +#include "util/StringTokenizer.h" + +using namespace muscle; + +#define DEFAULT_MUSCLED_PORT 2960 + +// Aux method; main() without the global stuff. This is a good method to +// call if you already have the global stuff set up the way you like it. +// The third argument can be passed in as NULL, or point to a UsageLimitProxyMemoryAllocator object +int muscledmainAux(int argc, char ** argv, void * cookie) +{ + TCHECKPOINT; + + UsageLimitProxyMemoryAllocator * usageLimitAllocator = (UsageLimitProxyMemoryAllocator*)cookie; + + uint32 maxBytes = MUSCLE_NO_LIMIT; + uint32 maxNodesPerSession = MUSCLE_NO_LIMIT; + uint32 maxReceiveRate = MUSCLE_NO_LIMIT; + uint32 maxSendRate = MUSCLE_NO_LIMIT; + uint32 maxCombinedRate = MUSCLE_NO_LIMIT; + uint32 maxMessageSize = MUSCLE_NO_LIMIT; + uint32 maxSessions = MUSCLE_NO_LIMIT; + uint32 maxSessionsPerHost = MUSCLE_NO_LIMIT; + + Hashtable listenPorts; + Queue bans; + Queue requires; + Message tempPrivs; + Hashtable tempRemaps; + + Message args; (void) ParseArgs(argc, argv, args); + HandleStandardDaemonArgs(args); + + const char * value; + if (args.HasName("help")) + { + Log(MUSCLE_LOG_INFO, "Usage: muscled [port=%u] [listen=ip:port] [displaylevel=lvl] [filelevel=lvl] [logfile=filename]\n", DEFAULT_MUSCLED_PORT); +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + Log(MUSCLE_LOG_INFO, " [maxmem=megs]\n"); +#endif + Log(MUSCLE_LOG_INFO, " [maxnodespersession=num] [remap=oldip=newip]\n"); + Log(MUSCLE_LOG_INFO, " [ban=ippattern] [require=ippattern]\n"); + Log(MUSCLE_LOG_INFO, " [privban=ippattern] [privunban=ippattern]\n"); + Log(MUSCLE_LOG_INFO, " [privkick=ippattern] [privall=ippattern]\n"); + Log(MUSCLE_LOG_INFO, " [maxsendrate=kBps] [maxreceiverate=kBps]\n"); + Log(MUSCLE_LOG_INFO, " [maxcombinedrate=kBps] [maxmessagesize=k]\n"); + Log(MUSCLE_LOG_INFO, " [maxsessions=num] [maxsessionsperhost=num]\n"); + Log(MUSCLE_LOG_INFO, " [localhost=ipaddress] [daemon]\n"); + Log(MUSCLE_LOG_INFO, " - port may be any number between 1 and 65536\n"); + Log(MUSCLE_LOG_INFO, " - listen is like port, except it includes a local interface IP as well.\n"); + Log(MUSCLE_LOG_INFO, " - lvl is: none, critical, errors, warnings, info, debug, or trace.\n"); +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + Log(MUSCLE_LOG_INFO, " - maxmem is the max megabytes of memory the server may use (default=unlimited)\n"); +#endif + Log(MUSCLE_LOG_INFO, " - You may also put one or more ban= arguments in.\n"); + Log(MUSCLE_LOG_INFO, " Each pattern specifies one or more IP addresses to\n"); + Log(MUSCLE_LOG_INFO, " disallow connections from, e.g. ban=192.168.*.*\n"); + Log(MUSCLE_LOG_INFO, " - You may put one or more require= arguments in.\n"); + Log(MUSCLE_LOG_INFO, " If any of these are present, then only IP addresses that match\n"); + Log(MUSCLE_LOG_INFO, " at least one of them will be allowed to connect.\n"); + Log(MUSCLE_LOG_INFO, " - To assign privileges, specify one of the following:\n"); + Log(MUSCLE_LOG_INFO, " privban=, privunban=,\n"); + Log(MUSCLE_LOG_INFO, " privkick= or privall=.\n"); + Log(MUSCLE_LOG_INFO, " privall assigns all privileges to the matching IP addresses.\n"); + Log(MUSCLE_LOG_INFO, " - remap tells muscled to treat connections from a given IP address\n"); + Log(MUSCLE_LOG_INFO, " as if they are coming from another (for stupid NAT tricks, etc)\n"); + Log(MUSCLE_LOG_INFO, " - If daemon is specified, muscled will run as a background process.\n"); + return(5); + } + + { + for (int32 i=0; (args.FindString("port", i, &value) == B_NO_ERROR); i++) + { + int16 port = atoi(value); + if (port >= 0) listenPorts.PutWithDefault(IPAddressAndPort(invalidIP, port)); + } + + for (int32 i=0; (args.FindString("listen", i, &value) == B_NO_ERROR); i++) + { + IPAddressAndPort iap(value, DEFAULT_MUSCLED_PORT, false); + if (iap.GetPort() > 0) listenPorts.PutWithDefault(iap); + else LogTime(MUSCLE_LOG_ERROR, "Unable to parse IP/port string [%s]\n", value); + } + } + + { + for (int32 i=0; (args.FindString("remap", i, &value) == B_NO_ERROR); i++) + { + StringTokenizer tok(value, ",="); + const char * from = tok(); + const char * to = tok(); + ip_address fromIP = from ? Inet_AtoN(from) : 0; + if ((fromIP != invalidIP)&&(to)) + { + char ipbuf[64]; Inet_NtoA(fromIP, ipbuf); + LogTime(MUSCLE_LOG_INFO, "Will treat connections coming from [%s] as if they were from [%s].\n", ipbuf, to); + tempRemaps.Put(fromIP, to); + } + else LogTime(MUSCLE_LOG_ERROR, "Error parsing remap argument (it should look something like remap=192.168.0.1,132.239.50.8).\n"); + } + } + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + if (args.FindString("maxmem", &value) == B_NO_ERROR) + { + int megs = muscleMax(1, atoi(value)); + LogTime(MUSCLE_LOG_INFO, "Limiting memory usage to %i megabyte%s.\n", megs, (megs==1)?"":"s"); + maxBytes = megs*1024L*1024L; + } +#endif + + if (args.FindString("maxmessagesize", &value) == B_NO_ERROR) + { + int k = muscleMax(1, atoi(value)); + LogTime(MUSCLE_LOG_INFO, "Limiting message sizes to %i kilobyte%s.\n", k, (k==1)?"":"s"); + maxMessageSize = k*1024L; + } + + if (args.FindString("maxsendrate", &value) == B_NO_ERROR) + { + float k = (float) atof(value); + maxSendRate = muscleMax((uint32)0, (uint32)(k*1024.0f)); + } + + if (args.FindString("maxreceiverate", &value) == B_NO_ERROR) + { + float k = (float) atof(value); + maxReceiveRate = muscleMax((uint32)0, (uint32)(k*1024.0f)); + } + + if (args.FindString("maxcombinedrate", &value) == B_NO_ERROR) + { + float k = (float) atof(value); + maxCombinedRate = muscleMax((uint32)0, (uint32)(k*1024.0f)); + } + + if (args.FindString("maxnodespersession", &value) == B_NO_ERROR) + { + maxNodesPerSession = atoi(value); + LogTime(MUSCLE_LOG_INFO, "Limiting nodes-per-session to " UINT32_FORMAT_SPEC".\n", maxNodesPerSession); + } + + if (args.FindString("maxsessions", &value) == B_NO_ERROR) + { + maxSessions = atoi(value); + LogTime(MUSCLE_LOG_INFO, "Limiting total session count to " UINT32_FORMAT_SPEC".\n", maxSessions); + } + + if (args.FindString("maxsessionsperhost", &value) == B_NO_ERROR) + { + maxSessionsPerHost = atoi(value); + LogTime(MUSCLE_LOG_INFO, "Limiting session count for any given host to " UINT32_FORMAT_SPEC".\n", maxSessionsPerHost); + } + + { + for (int32 i=0; (args.FindString("ban", i, &value) == B_NO_ERROR); i++) + { + LogTime(MUSCLE_LOG_INFO, "Banning all clients whose IP addresses match [%s].\n", value); + bans.AddTail(value); + } + } + + { + for (int32 i=0; (args.FindString("require", i, &value) == B_NO_ERROR); i++) + { + LogTime(MUSCLE_LOG_INFO, "Allowing only clients whose IP addresses match [%s].\n", value); + requires.AddTail(value); + } + } + + { + const char * privNames[] = {"privkick", "privban", "privunban", "privall"}; + for (int p=0; p<=PR_NUM_PRIVILEGES; p++) // if (p == PR_NUM_PRIVILEGES), that means all privileges + { + for (int32 q=0; (args.FindString(privNames[p], q, &value) == B_NO_ERROR); q++) + { + LogTime(MUSCLE_LOG_INFO, "Clients whose IP addresses match [%s] get %s privileges.\n", value, privNames[p]+4); + char tt[32]; + sprintf(tt, "priv%i", p); + tempPrivs.AddString(tt, value); + } + } + } + + if ((maxBytes != MUSCLE_NO_LIMIT)&&(usageLimitAllocator)) usageLimitAllocator->SetMaxNumBytes(maxBytes); + + int retVal = 0; + ReflectServer server; + + bool okay = true; + server.GetAddressRemappingTable() = tempRemaps; + + if (maxNodesPerSession != MUSCLE_NO_LIMIT) server.GetCentralState().AddInt32(PR_NAME_MAX_NODES_PER_SESSION, maxNodesPerSession); + for (MessageFieldNameIterator iter = tempPrivs.GetFieldNameIterator(); iter.HasData(); iter++) tempPrivs.CopyName(iter.GetFieldName(), server.GetCentralState()); + + // If the user asked for bandwidth limiting, create Policy objects to handle that. + AbstractSessionIOPolicyRef inputPolicyRef, outputPolicyRef; + if (maxCombinedRate != MUSCLE_NO_LIMIT) + { + inputPolicyRef.SetRef(newnothrow RateLimitSessionIOPolicy(maxCombinedRate)); + outputPolicyRef = inputPolicyRef; + if (inputPolicyRef()) LogTime(MUSCLE_LOG_INFO, "Limiting aggregate I/O bandwidth to %.02f kilobytes/second.\n", ((float)maxCombinedRate/1024.0f)); + else + { + WARN_OUT_OF_MEMORY; + okay = false; + } + } + else + { + if (maxReceiveRate != MUSCLE_NO_LIMIT) + { + inputPolicyRef.SetRef(newnothrow RateLimitSessionIOPolicy(maxReceiveRate)); + if (inputPolicyRef()) LogTime(MUSCLE_LOG_INFO, "Limiting aggregate receive bandwidth to %.02f kilobytes/second.\n", ((float)maxReceiveRate/1024.0f)); + else + { + WARN_OUT_OF_MEMORY; + okay = false; + } + } + if (maxSendRate != MUSCLE_NO_LIMIT) + { + outputPolicyRef.SetRef(newnothrow RateLimitSessionIOPolicy(maxSendRate)); + if (outputPolicyRef()) LogTime(MUSCLE_LOG_INFO, "Limiting aggregate send bandwidth to %.02f kilobytes/second.\n", ((float)maxSendRate/1024.0f)); + else + { + WARN_OUT_OF_MEMORY; + okay = false; + } + } + } + + // Set up the Session Factory. This factory object creates the new StorageReflectSessions + // as needed when people connect, and also has a filter to keep out the riff-raff. + StorageReflectSessionFactory factory; factory.SetMaxIncomingMessageSize(maxMessageSize); + FilterSessionFactory filter(ReflectSessionFactoryRef(&factory, false), maxSessionsPerHost, maxSessions); + filter.SetInputPolicy(inputPolicyRef); + filter.SetOutputPolicy(outputPolicyRef); + + for (int b=bans.GetNumItems()-1; ((okay)&&(b>=0)); b--) if (filter.PutBanPattern(bans[b]()) != B_NO_ERROR) okay = false; + for (int a=requires.GetNumItems()-1; ((okay)&&(a>=0)); a--) if (filter.PutRequirePattern(requires[a]()) != B_NO_ERROR) okay = false; + + const String * privateKeyFilePath = args.GetStringPointer("privatekey"); +#ifdef MUSCLE_ENABLE_SSL + ByteBufferRef optCryptoBuf; + if (privateKeyFilePath) + { + FileDataIO fdio(fopen(privateKeyFilePath->Cstr(), "rb")); + ByteBufferRef fileData = GetByteBufferFromPool((uint32)fdio.GetLength()); + if ((fdio.GetFile())&&(fileData())&&(fdio.ReadFully(fileData()->GetBuffer(), fileData()->GetNumBytes()) == fileData()->GetNumBytes())) + { + LogTime(MUSCLE_LOG_INFO, "Using private key file [%s] to authenticate with connecting clients\n", privateKeyFilePath->Cstr()); + server.SetSSLPrivateKey(fileData); + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't load private key file [%s] (file not found?)\n", privateKeyFilePath->Cstr()); + okay = false; + } + } +#else + if (privateKeyFilePath) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Can't loadp private key file [%s], SSL support is not compiled in!\n", privateKeyFilePath->Cstr()); + okay = false; + } +#endif + + // Set up ports. We allow multiple ports, mostly just to show how it can be done; + // they all get the same set of ban/require patterns (since they all do the same thing anyway). + if (listenPorts.IsEmpty()) listenPorts.PutWithDefault(IPAddressAndPort(invalidIP, DEFAULT_MUSCLED_PORT)); + for (HashtableIterator iter(listenPorts); iter.HasData(); iter++) + { + const IPAddressAndPort & iap = iter.GetKey(); + + if (server.PutAcceptFactory(iap.GetPort(), ReflectSessionFactoryRef(&filter, false), iap.GetIPAddress()) != B_NO_ERROR) + { + if (iap.GetIPAddress() == invalidIP) LogTime(MUSCLE_LOG_CRITICALERROR, "Error adding port %u, aborting.\n", iap.GetPort()); + else LogTime(MUSCLE_LOG_CRITICALERROR, "Error adding port %u to interface %s, aborting.\n", iap.GetPort(), Inet_NtoA(iap.GetIPAddress())()); + okay = false; + break; + } + } + + if (okay) + { + retVal = (server.ServerProcessLoop() == B_NO_ERROR) ? 0 : 10; + if (retVal > 0) LogTime(MUSCLE_LOG_CRITICALERROR, "Server process aborted!\n"); + else LogTime(MUSCLE_LOG_INFO, "Server process exiting.\n"); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Error occurred during setup, aborting!\n"); + + server.Cleanup(); + return retVal; +} + +#ifdef UNIFIED_DAEMON +int muscledmain(int argc, char ** argv) +#else +int main(int argc, char ** argv) +#endif +{ + CompleteSetupSystem css; + + TCHECKPOINT; + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + // Set up memory allocation policies for our server. These policies will make sure + // that the server can't allocate more than a specified amount of memory, and if it tries, + // some emergency callbacks will be called to free up cached info. + FunctionCallback fcb(AbstractObjectRecycler::GlobalFlushAllCachedObjects); + MemoryAllocatorRef nullRef; + AutoCleanupProxyMemoryAllocator cleanupAllocator(nullRef); + cleanupAllocator.GetCallbacksQueue().AddTail(GenericCallbackRef(&fcb, false)); + + UsageLimitProxyMemoryAllocator usageLimitAllocator(MemoryAllocatorRef(&cleanupAllocator, false)); + + SetCPlusPlusGlobalMemoryAllocator(MemoryAllocatorRef(&usageLimitAllocator, false)); + int ret = muscledmainAux(argc, argv, &usageLimitAllocator); + SetCPlusPlusGlobalMemoryAllocator(MemoryAllocatorRef()); // unset, so that none of our allocator objects will be used after they are gone + + return ret; +#else + // Without memory-allocation rules, things are much simpler :^) + return muscledmainAux(argc, argv, NULL); +#endif +} diff --git a/ssl_data/README_SSL.txt b/ssl_data/README_SSL.txt new file mode 100644 index 00000000..fed40687 --- /dev/null +++ b/ssl_data/README_SSL.txt @@ -0,0 +1,47 @@ +The .pem files in this folder are pre-generated OpenSSL private and public +keys, to give you an example for what the key files should look like. +Don't use them for anything serious, since the the private key has been +distributed to the world and thus it is not very private anymore. :) + +To Generate your own self-signed private key and matching public +certificate, execute the following shell commands: + + openssl req -x509 -days 3650 -nodes -newkey rsa:1024 -keyout my_private_key.pem -out my_public_key.pem + cat my_public_key.pem >> my_private_key.pem + +To test/verify MUSCLE's OpenSSL support under Linux or MacOS/X, do the +following: + + - In your Terminal window: + - Under Linux only, make sure package libssh-dev is installed + (MacOS/X has it installed by default) + - Edit muscle/server/Makefile and uncomment the line + CXXFLAGS += -DMUSCLE_ENABLE_SSL + - Compile muscled: cd muscle/server; make clean; make + - Run muscled: + ./muscled displaylevel=trace privatekey=../ssl_data/muscle_test_private_key.pem + + - In a second Terminal window: + - Edit muscle/test/Makefile and uncomment the line + CXXFLAGS += -DMUSCLE_ENABLE_SSL line + - cd ../test; make clean; make + - Run portablereflectclient: ./portablereflectclient localhost publickey=../ssl_data/muscle_test_public_key.pem + - portablereflectclient should connect to muscled and stay connected. + It should mention on stdout that it is using the specified public key + file to connect to the server. + - enter P (or L) and press return. If you get a bunch of parameter data + printed in response, then the SSL connection is working. + + - (Optional, if you like Qt-based GUI demos) In a third Terminal window + - Make sure a Qt 4.x or Qt 5.x development environment is installed on your system. + - cd ../qtsupport/qt_example ; touch ./muscle_enable_ssl + - qmake; make clean; make + - ./qt_example.app/Contents/MacOS/qt_example publickey=../../ssl_data/muscle_test_public_key.pem + (or under Linux, it's: ./qt_example publickey=../../ssl_data/muscle_test_public_key.pem ) + - In the window that appears, click the "Connect to Server" button if + necessary + - Click the "Clone" button to create a second window + - If everything is working, dragging in one window should show up in the + second window and vice versa. + - Click the "Animate" checkbox in one or both windows to animate positions. + diff --git a/ssl_data/muscle_test_private_key.pem b/ssl_data/muscle_test_private_key.pem new file mode 100644 index 00000000..5101862a --- /dev/null +++ b/ssl_data/muscle_test_private_key.pem @@ -0,0 +1,32 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDC+hkzCbeZyiB/t/wCsMQrNzPItxz8j5Wz6DK8O/S8Yk603vWN +buVoSVVLN94wbihfdpGBHE96A86IMwTRXibyqzHWMIM25jjUMH/1a3c34DcUL7k3 +QGUIJd8anC0mNxbryB7d/7NtVPZIRJ4UeUyWOTYh/U+C25wxctFWRUEnlwIDAQAB +AoGAU57FbfEt7+QTOhNjHphHGa2sJNn7sYm3D5h3Iemo1Z5n2QSCVW0JMEe7L+4+ ++ZBU+lQ0pSXKJC87xYfz+gMiOlJ9EUwsUH82//Pp9BkYKCLsa9XS6NXuGEIUh+9Z +AQ8DmlQ0Fn6wEtlDz5OLefurpzLMcPtcYeAxZKHADfPL8AECQQD3B8Bqswa/3hHj +kZSiyI+0ys2RUjcLcSwQ2pLSe1Aacl/9BDeRlnvXRcv/0e0bmxzUE8tnFJ9PCE0G +/LjK0DgBAkEAyg59VpIu+FYMcNqrCpqgTwMbfWpRubGmzGRbKNaCw5GwdH3pk6Rk +tGnmQ9c0xY5BKryRHfIfW0FR/WFKragflwJBAOt6MCmFzoFHJrZPJ+ikVph4WcyV +GnOFxgOq1xpdAda0AFwnkPDvCc4DcprBecw+6BQhalswhUvJFeP7NAvgwAECQA4i +z2AEICcdlgc7NSYrxTVh6UVv78HHDP1VtA76WvGluqubt4CtyqHYmtR6NOXxWtvL +j3eB3fG7cyn/6YKWtYcCQQC+wOO3cy7htTPBGqyBYczkeBMTfmUVPZDZnn1YaprT +BRZbk6qg4q47ZOF9ZxkJWsZ2JORBr9XJc6y7gnVWY3qZ +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICsDCCAhmgAwIBAgIJAMwOpxagK9sdMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTMxMDA4MjIzNzE2WhcNMjMxMDA2MjIzNzE2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQDC+hkzCbeZyiB/t/wCsMQrNzPItxz8j5Wz6DK8O/S8Yk603vWNbuVoSVVLN94w +bihfdpGBHE96A86IMwTRXibyqzHWMIM25jjUMH/1a3c34DcUL7k3QGUIJd8anC0m +NxbryB7d/7NtVPZIRJ4UeUyWOTYh/U+C25wxctFWRUEnlwIDAQABo4GnMIGkMB0G +A1UdDgQWBBRUDUVkzECKyT3F5NOK4+sKFOgNRzB1BgNVHSMEbjBsgBRUDUVkzECK +yT3F5NOK4+sKFOgNR6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt +U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMwOpxag +K9sdMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAGNQ+kjpvcKlMgWlE +suJdKl/rJAQl2Gk9LAYscIhgTAMQLDgOgFCMyQj7AUHXTUdclCjEYlTuSWIpXm2G +osJGh6a+6AzWzVHH1Mzkg2Q1qLcvt/c7iuSrVvNZjaIAwzyTnVoI+8gE6BjGUshV +3JEQAaPxHinPD/2rEjooXrkH4qU= +-----END CERTIFICATE----- diff --git a/ssl_data/muscle_test_public_key.pem b/ssl_data/muscle_test_public_key.pem new file mode 100644 index 00000000..7c42ce39 --- /dev/null +++ b/ssl_data/muscle_test_public_key.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsDCCAhmgAwIBAgIJAMwOpxagK9sdMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTMxMDA4MjIzNzE2WhcNMjMxMDA2MjIzNzE2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQDC+hkzCbeZyiB/t/wCsMQrNzPItxz8j5Wz6DK8O/S8Yk603vWNbuVoSVVLN94w +bihfdpGBHE96A86IMwTRXibyqzHWMIM25jjUMH/1a3c34DcUL7k3QGUIJd8anC0m +NxbryB7d/7NtVPZIRJ4UeUyWOTYh/U+C25wxctFWRUEnlwIDAQABo4GnMIGkMB0G +A1UdDgQWBBRUDUVkzECKyT3F5NOK4+sKFOgNRzB1BgNVHSMEbjBsgBRUDUVkzECK +yT3F5NOK4+sKFOgNR6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt +U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMwOpxag +K9sdMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAGNQ+kjpvcKlMgWlE +suJdKl/rJAQl2Gk9LAYscIhgTAMQLDgOgFCMyQj7AUHXTUdclCjEYlTuSWIpXm2G +osJGh6a+6AzWzVHH1Mzkg2Q1qLcvt/c7iuSrVvNZjaIAwzyTnVoI+8gE6BjGUshV +3JEQAaPxHinPD/2rEjooXrkH4qU= +-----END CERTIFICATE----- diff --git a/support/BitChord.h b/support/BitChord.h new file mode 100644 index 00000000..c63e8d33 --- /dev/null +++ b/support/BitChord.h @@ -0,0 +1,157 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleBitChord_h +#define MuscleBitChord_h + +#include "util/String.h" +#include "support/MuscleSupport.h" + +namespace muscle { + +/** A templated class for implement an N-bit-long bit-chord. Useful for doing efficient parallel boolean operations + * in bit-lengths that can't fit in any of the standard integer types. + */ +template class BitChord +{ +public: + /** Default ctor; All bits are set to false. */ + BitChord() {ClearAllBits();} + + /** Copy constructor */ + BitChord(const BitChord & copyMe) {*this = copyMe;} + + /** Destructor */ + ~BitChord() {/* empty */} + + /** Assignment operator. */ + BitChord & operator =(const BitChord & rhs) {if (this != &rhs) {for (int i=0; i rhs._words[i]) return false;}} return false;} + + /** Comparison Operator. Returns true if this bit-chord comes after (rhs) lexically. */ + bool operator > (const BitChord &rhs) const {if (this != &rhs) {for (int i=0; i rhs._words[i]) return true; if (_words[i] < rhs._words[i]) return false;}} return false;} + + /** Comparison Operator. Returns true if the two bit-chord are equal, or this bit-chord comes before (rhs) lexically. */ + bool operator <=(const BitChord &rhs) const {return !(*this > rhs);} + + /** Comparison Operator. Returns true if the two bit-chord are equal, or this bit-chord comes after (rhs) lexically. */ + bool operator >=(const BitChord &rhs) const {return !(*this < rhs);} + + /** Returns the state of the specified bit */ + bool GetBit(unsigned int whichBit) const + { + MASSERT(whichBit < NumBits, "BitChord::GetBit: whichBit was out of range!\n"); + return ((_words[whichBit/NUM_BITS_PER_WORD] & (1L<<(whichBit%NUM_BITS_PER_WORD))) != 0); + } + + /** Sets the state of the specified bit */ + void SetBit(unsigned int whichBit, bool value) + { + MASSERT(whichBit < NumBits, "BitChord::SetBit: whichBit was out of range!\n"); + unsigned int & w = _words[whichBit/NUM_BITS_PER_WORD]; + unsigned int ch = 1L<<(whichBit%NUM_BITS_PER_WORD); + if (value) w |= ch; + else w &= ~ch; + } + + /** Sets all our bits to false */ + void ClearAllBits() {for (uint32 i=0; i=0; i--) + { + uint8 c = b[i]; + if (c != 0) foundNonZero = true; + if (foundNonZero) + { + char buf[4]; + sprintf(buf, "%s%02x", (ret.IsEmpty())?"":" ", c); + ret += buf; + } + } + return ret; + } + +private: + void ClearExtraBits() + { + if ((NumBits != NUM_BITS_PER_WORD*NUM_WORDS)&&(_words[NUM_WORDS-1] != 0)) + for (unsigned int i=(NUM_WORDS*NUM_BITS_PER_WORD)-1; i>=NumBits; i--) + SetBit(i, false); + } + + enum {NUM_BITS_PER_WORD = sizeof(unsigned int)*8}; + enum {NUM_WORDS = (NumBits+NUM_BITS_PER_WORD-1)/NUM_BITS_PER_WORD}; + unsigned int _words[NUM_WORDS]; +}; + +#define DECLARE_ADDITIONAL_BITCHORD_OPERATORS(N) \ + inline const BitChord operator | (const BitChord & lhs, const BitChord & rhs) {BitChord ret(lhs); ret |= rhs; return ret;} \ + inline const BitChord operator & (const BitChord & lhs, const BitChord & rhs) {BitChord ret(lhs); ret &= rhs; return ret;} \ + inline const BitChord operator ^ (const BitChord & lhs, const BitChord & rhs) {BitChord ret(lhs); ret ^= rhs; return ret;} + +}; // end namespace muscle + +#endif diff --git a/support/Flattenable.h b/support/Flattenable.h new file mode 100644 index 00000000..eda53136 --- /dev/null +++ b/support/Flattenable.h @@ -0,0 +1,222 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/****************************************************************************** +/ +/ File: Flattenable.h +/ +/ Description: version of Be's BFlattenable base class. +/ +******************************************************************************/ + +#ifndef MuscleFlattenable_h +#define MuscleFlattenable_h + +#include "support/MuscleSupport.h" +#include "dataio/DataIO.h" +#include "util/RefCount.h" + +namespace muscle { + +class ByteBuffer; // forward reference to avoid chicken-and-egg problems + +/** This class is an interface representing an object that knows how + * to save itself into an array of bytes, and recover its state from + * an array of bytes. + */ +class Flattenable +{ +public: + /** Constructor */ + Flattenable() {/* empty */} + + /** Destructor */ + virtual ~Flattenable() {/* empty */} + + /** Should return true iff every object of this type has a size that is known at compile time. */ + virtual bool IsFixedSize() const = 0; + + /** Should return the type code identifying this type of object. */ + virtual uint32 TypeCode() const = 0; + + /** Should return the number of bytes needed to store this object in its current state. */ + virtual uint32 FlattenedSize() const = 0; + + /** + * Should store this object's state into (buffer). + * @param buffer The bytes to write this object's stat into. Buffer must be at least FlattenedSize() bytes long. + */ + virtual void Flatten(uint8 *buffer) const = 0; + + /** + * Should return true iff a buffer with uint32 (code) can be used to reconstruct + * this object's state. Defaults implementation returns true iff (code) equals TypeCode() or B_RAW_DATA. + * @param code A type code constant, e.g. B_RAW_TYPE or B_STRING_TYPE, or something custom. + * @return True iff this object can Unflatten from a buffer of the given type, false otherwise. + */ + virtual bool AllowsTypeCode(uint32 code) const {return ((code == B_RAW_TYPE)||(code == TypeCode()));} + + /** + * Should attempt to restore this object's state from the given buffer. + * @param buf The buffer of bytes to unflatten from. + * @param size Number of bytes in the buffer. + * @return B_NO_ERROR if the Unflattening was successful, else B_ERROR. + */ + virtual status_t Unflatten(const uint8 *buf, uint32 size) = 0; + + /** + * Causes (copyTo)'s state to set from this Flattenable, if possible. + * Default implementation is not very efficient, since it has to flatten + * this object into a byte buffer, and then unflatten the bytes back into + * (copyTo). However, you can override CopyFromImplementation() to provide + * a more efficient implementation when possible. + * @param copyTo Object to make into the equivalent of this object. (copyTo) + * May be any subclass of Flattenable. + * @return B_NO_ERROR on success, or B_ERROR on failure (typecode mismatch, out-of-memory, etc) + */ + status_t CopyTo(Flattenable & copyTo) const + { + return (this == ©To) ? B_NO_ERROR : ((copyTo.AllowsTypeCode(TypeCode())) ? copyTo.CopyFromImplementation(*this) : B_ERROR); + } + + /** + * Causes our state to be set from (copyFrom)'s state, if possible. + * Default implementation is not very efficient, since it has to flatten + * (copyFrom) into a byte buffer, and then unflatten the bytes back into + * (this). However, you can override CopyFromImplementation() to provide + * a more efficient implementation when possible. + * @param copyFrom Object to read from to set the state of this object. + * (copyFrom) may be any subclass of Flattenable. + * @return B_NO_ERROR on success, or B_ERROR on failure (typecode mismatch, out-of-memory, etc) + */ + status_t CopyFrom(const Flattenable & copyFrom) + { + return (this == ©From) ? B_NO_ERROR : ((AllowsTypeCode(copyFrom.TypeCode())) ? CopyFromImplementation(copyFrom) : B_ERROR); + } + + /** + * Convenience method for writing data into a byte buffer. + * Writes data consecutively into a byte buffer. The output buffer is + * assumed to be large enough to hold the data. + * @param outBuf Flat buffer to write to + * @param writeOffset Offset into buffer to write to. Incremented by (blockSize) on success. + * @param copyFrom memory location to copy bytes from + * @param blockSize number of bytes to copy + */ + static void WriteData(uint8 * outBuf, uint32 * writeOffset, const void * copyFrom, uint32 blockSize) + { + memcpy(&outBuf[*writeOffset], copyFrom, blockSize); + *writeOffset += blockSize; + }; + + /** + * Convenience method for safely reading bytes from a byte buffer. (Checks to avoid buffer overrun problems) + * @param inBuf Flat buffer to read bytes from + * @param inputBufferBytes total size of the input buffer + * @param readOffset Offset into buffer to read from. Incremented by (blockSize) on success. + * @param copyTo memory location to copy bytes to + * @param blockSize number of bytes to copy + * @return B_NO_ERROR if the data was successfully read, B_ERROR if the data couldn't be read (because the buffer wasn't large enough) + */ + static status_t ReadData(const uint8 * inBuf, uint32 inputBufferBytes, uint32 * readOffset, void * copyTo, uint32 blockSize) + { + if ((*readOffset + blockSize) > inputBufferBytes) return B_ERROR; + memcpy(copyTo, &inBuf[*readOffset], blockSize); + *readOffset += blockSize; + return B_NO_ERROR; + }; + + /** Convenience method. Flattens this object into the supplied ByteBuffer object. + * @param outBuf the ByteBuffer to dump our flattened bytes into. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t FlattenToByteBuffer(ByteBuffer & outBuf) const; + + /** Convenience method. Unflattens this object from the supplied ByteBuffer object. + * @param buf The ByteBuffer to unflatten from. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t UnflattenFromByteBuffer(const ByteBuffer & buf); + + /** Convenience method. Unflattens this object from the specified byte buffer reference. + * @param bufRef The ByteBufferRef to unflatten from. + * @returns B_NO_ERROR on success, or B_ERROR on failure (or if bufRef is a NULL reference) + */ + status_t UnflattenFromByteBuffer(const ConstRef & bufRef); + + /** Convenience method. Allocates an appropriately sized ByteBuffer object via GetByteBufferFromPool(), Flatten()s + * this object into the byte buffer, and returns the resulting ByteBufferRef. Returns a NULL reference on failure (out of memory?) + */ + Ref FlattenToByteBuffer() const; + + /** Convenience method. Flattens this object to the given DataIO object. + * @param outputStream The DataIO object to send our flattened data to. This DataIO should be in blocking I/O mode; this method won't work reliably + * if used with non-blocking I/O. + * @param addSizeHeader If true, we will prefix our flattened data with a four-byte little-endian uint32 indicating the number of bytes + * of flattened data that we are going to write. If false, then the buffer size will need to be determined by + * the reading code by some other means. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t FlattenToDataIO(DataIO & outputStream, bool addSizeHeader) const; + + /** Convenience method. Flattens this object from the given DataIO object. + * @param inputStream the DataIO object to read data from. This DataIO should be in blocking I/O mode; this method won't work reliably + * if used with non-blocking I/O. + * @param optReadSize If non-negative, this many bytes will be read from (inputStream). If set to -1, then the first four + * bytes read from the stream will be used to determine how many more bytes should be read from the stream. + * If the data was created with Flatten(io, true), then set this parameter to -1. + * @param optMaxReadSize An optional value indicating the largest number of bytes that should be read by this call. + * This value is only used if (optReadSize) is negative. If (optReadSize) is negative and + * the first four bytes read from (inputStream) are greater than this value, then this method + * will return B_ERROR instead of trying to read that many bytes. This can be useful if you + * want to prevent the possibility of huge buffers being allocated due to malicious or corrupted + * size headers. Defaults to MUSCLE_NO_LIMIT, meaning that no size limit is enforced. + * @returns B_NO_ERROR if the data was correctly read, or B_ERROR otherwise. + */ + status_t UnflattenFromDataIO(DataIO & inputStream, int32 optReadSize, uint32 optMaxReadSize = MUSCLE_NO_LIMIT); + +protected: + /** + * Called by CopyFrom() and CopyTo(). Sets our state from (copyFrom) if + * possible. Default implementation is not very efficient, since it has + * to flatten (copyFrom) into a byte buffer, and then unflatten the bytes + * back into (this). However, you can override CopyFromImplementation() + * to provide a more efficient implementation when possible. + * @param copyFrom Object to set this object's state from. + * May be any subclass of Flattenable, but it has been + * pre-screened by CopyFrom() (or CopyTo()) to make sure + * it's not (*this), and that we allow its type code. + * @return B_NO_ERROR on success, or B_ERROR on failure (out-of-memory, etc) + */ + virtual status_t CopyFromImplementation(const Flattenable & copyFrom); +}; + +/** This class is here to support lightweight subclasses that want to have a Flattenable-like + * API (Flatten(), Unflatten(), etc) without incurring the one-word-per-object memory + * required by the presence of virtual methods. To use this class, subclass your + * class from this one and declare Flatten(), Unflatten(), FlattenedSize(), etc methods + * in your class, but don't make them virtual. That will be enough to allow you to + * use Message::AddFlat(), Message::FindFlat(), etc on your objects, with no extra + * memory overhead. See the MUSCLE Point and Rect classes for examples of this technique. + */ +class PseudoFlattenable +{ +public: + /** + * Dummy implemention of CopyFrom(). It's here only so that Message::FindFlat() will + * compile when called with a PseudoFlattenable object as an argument. + * @param copyFrom This parameter is ignored. + * @returns B_ERROR always, because given that this object is not a Flattenable object, + * it's assumed that it can't receive the state of a Flattenable object either. + * (but if that's not the case for your class, your subclass can implement its + * own CopyFrom() method to taste) + */ + status_t CopyFrom(const Flattenable & copyFrom) {(void) copyFrom; return B_ERROR;} +}; + +/*-------------------------------------------------------------*/ +/*-------------------------------------------------------------*/ + +}; // end namespace muscle + +#endif /* _MUSCLEFLATTENABLE_H */ + diff --git a/support/MuscleSupport.h b/support/MuscleSupport.h new file mode 100644 index 00000000..0c780aec --- /dev/null +++ b/support/MuscleSupport.h @@ -0,0 +1,1185 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/****************************************************************************** +/ +/ File: MuscleSupport.h +/ +/ Description: Standard types, macros, functions, etc, for MUSCLE. +/ +*******************************************************************************/ + +#ifndef MuscleSupport_h +#define MuscleSupport_h + +#define MUSCLE_VERSION_STRING "6.05" +#define MUSCLE_VERSION 60500 // Format is decimal Mmmbb, where (M) is the number before the decimal point, (mm) is the number after the decimal point, and (bb) is reserved + +/*! \mainpage MUSCLE Documentation Page + * + * The MUSCLE API provides a robust, somewhat scalable, cross-platform client-server solution for + * network-distributed applications for Linux, MacOS/X, BSD, Windows, BeOS, AtheOS, and other operating + * systems. It allows (n) client programs (each of which may be running on a separate computer and/or + * under a different OS) to communicate with each other in a many-to-many message-passing style. It + * employs a central server to which client programs may connect or disconnect at any time (This design + * is similar to other client-server systems such as Quake servers, IRC servers, and Napster servers, + * but more general in application). In addition to the client-server system, MUSCLE contains classes + * to support peer-to-peer message streaming connections, as well as some handy miscellaneous utility + * classes, all of which are documented here. + * + * All classes documented here should compile under most modern OS's with a modern C++ compiler. + * Where platform-specific code is necessary, it has been provided (inside \#ifdef's) for various OS's. + * Templates are used throughout; exceptions are not. The code is usable in multithreaded environments, + * as long as you are careful. + * + * As distributed, the server side of the software is ready to compile and run, but to do much with it + * you'll want to write your own client software. Example client software can be found in the "test" + * subdirectory. + */ + +#include /* for assert() */ +#include /* for memcpy() */ +#include +#include +#include /* for errno */ + +/* Define this if the default FD_SETSIZE is too small for you (i.e. under Windows it's only 64) */ +#if defined(MUSCLE_FD_SETSIZE) +# if defined(FD_SETSIZE) +# error "MuscleSupport.h: Can not redefine FD_SETSIZE, someone else has already defined it! You need to include MuscleSupport.h before including any other header files that define FD_SETSIZE." +# else +# define FD_SETSIZE MUSCLE_FD_SETSIZE +# endif +#endif + +/* If we are in an environment where known assembly is available, make a note of that fact */ +#ifndef MUSCLE_AVOID_INLINE_ASSEMBLY +# if defined(__GNUC__) +# if defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(__amd64__) || defined(__x86_64__) || defined(__x86__) || defined(__pentium__) || defined(__pentiumpro__) || defined(__k6__) || defined(_M_AMD64) +# define MUSCLE_USE_X86_INLINE_ASSEMBLY 1 +# elif defined(__PPC__) || defined(__POWERPC__) +# define MUSCLE_USE_POWERPC_INLINE_ASSEMBLY 1 +# endif +# elif defined(_MSC_VER) && (defined(_X86_) || defined(_M_IX86)) +# define MUSCLE_USE_X86_INLINE_ASSEMBLY 1 +# endif +#endif + +#if (_MSC_VER >= 1310) +# define MUSCLE_USE_MSVC_SWAP_FUNCTIONS 1 +#endif + +#if (_MSC_VER >= 1300) && !defined(MUSCLE_AVOID_WINDOWS_STACKTRACE) +# define MUSCLE_USE_MSVC_STACKWALKER 1 +#endif + + +#ifdef __cplusplus +# ifdef MUSCLE_USE_CPLUSPLUS11 +# include // for std::move() +# endif +#else +# define NEW_H_NOT_AVAILABLE +#endif + +/* Borland C++ builder also runs under Win32, but it doesn't set this flag So we'd better set it ourselves. */ +#if defined(__BORLANDC__) || defined(__WIN32__) || defined(_MSC_VER) +# ifndef WIN32 +# define WIN32 1 +# endif +#endif + +/* Win32 can't handle this stuff, it's too lame */ +#ifdef WIN32 +# define SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE +# define UNISTD_H_NOT_AVAILABLE +# ifndef _MSC_VER /* 7/3/2006: Mika's patch allows VC++ to use newnothrow */ +# define NEW_H_NOT_AVAILABLE +# endif +#elif defined(__BEOS__) && !defined(__HAIKU__) +# define SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE +#endif + +#ifndef UNISTD_H_NOT_AVAILABLE +# include +#endif + +#ifndef NEW_H_NOT_AVAILABLE +# include +# ifndef __MWERKS__ +using std::bad_alloc; +using std::nothrow_t; +using std::nothrow; +# if (defined(_MSC_VER)) +// VC++ 6.0 and earlier lack this definition +# if (_MSC_VER < 1300) +inline void __cdecl operator delete(void *p, const std::nothrow_t&) _THROW0() {delete(p);} +# endif +# else +using std::new_handler; +using std::set_new_handler; +# endif +# endif +#else +# define MUSCLE_AVOID_NEWNOTHROW +#endif + +#ifndef newnothrow +# ifdef MUSCLE_AVOID_NEWNOTHROW +# define newnothrow new +# else +# define newnothrow new (nothrow) +# endif +#endif + +// These macros are implementation details, please ignore them +#define ___MUSCLE_UNIQUE_NAME_AUX1(name, line) name##line +#define ___MUSCLE_UNIQUE_NAME_AUX2(name, line) ___MUSCLE_UNIQUE_NAME_AUX1(name, line) + +/** This macro expands to a variable name which is (per-file) unique and unreferenceable. + * This can be useful to help make sure the temporary variables in your macros don't + * collide with each other. + */ +#define MUSCLE_UNIQUE_NAME ___MUSCLE_UNIQUE_NAME_AUX2(__uniqueName, __LINE__) + +/** This macro declares an object on the stack with the specified argument. */ +#ifdef _MSC_VER +# define DECLARE_ANONYMOUS_STACK_OBJECT(objType, ...) objType MUSCLE_UNIQUE_NAME = objType(__VA_ARGS__) +#else +# define DECLARE_ANONYMOUS_STACK_OBJECT(objType, args...) objType MUSCLE_UNIQUE_NAME = objType(args) +#endif + +#ifdef MUSCLE_AVOID_ASSERTIONS +# define MASSERT(x,msg) +#else +# define MASSERT(x,msg) {if(!(x)) MCRASH(msg)} +#endif + +#define MCRASH(msg) {muscle::LogTime(muscle::MUSCLE_LOG_CRITICALERROR, "ASSERTION FAILED: (%s:%i) %s\n", __FILE__,__LINE__,msg); muscle::LogStackTrace(MUSCLE_LOG_CRITICALERROR); assert(false);} +#define MEXIT(retVal,msg) {muscle::LogTime(muscle::MUSCLE_LOG_CRITICALERROR, "ASSERTION FAILED: (%s:%i) %s\n", __FILE__,__LINE__,msg); muscle::LogStackTrace(MUSCLE_LOG_CRITICALERROR); ExitWithoutCleanup(retVal);} +#define WARN_OUT_OF_MEMORY muscle::WarnOutOfMemory(__FILE__, __LINE__) +#define MCHECKPOINT muscle::LogTime(muscle::MUSCLE_LOG_WARNING, "Reached checkpoint at %s:%i\n", __FILE__, __LINE__) + +#ifdef __cplusplus +template unsigned int ARRAYITEMS(T(&)[size]) {return size;} /* returns # of items in array, will error out at compile time if you try it on a pointer */ +#else +# define ARRAYITEMS(x) (sizeof(x)/sizeof(x[0])) /* returns # of items in array */ +#endif +typedef void * muscleVoidPointer; /* it's a bit easier, syntax-wise, to use this type than (void *) directly in some cases. */ + +#if defined(__BEOS__) || defined(__HAIKU__) +# include +# include /* might as well use the real thing (and avoid complaints about duplication) */ +# include +# include +# if !((defined(BONE))||(defined(BONE_VERSION))||(defined(__HAIKU__))) +# define BEOS_OLD_NETSERVER +# endif +#else +# define B_ERROR -1 +# define B_NO_ERROR 0 +# define B_OK B_NO_ERROR +# ifdef __ATHEOS__ +# include +# else +# ifndef MUSCLE_TYPES_PREDEFINED /* certain (ahem) projects already set these themselves... */ +# ifndef __cplusplus +# define true 1 +# define false 0 +# endif + typedef unsigned char uchar; + typedef unsigned short unichar; + typedef signed char int8; + typedef unsigned char uint8; + typedef short int16; + typedef unsigned short uint16; +# if defined(MUSCLE_64_BIT_PLATFORM) || defined(__osf__) || defined(__amd64__) || defined(__PPC64__) || defined(__x86_64__) || defined(_M_AMD64) /* some 64bit systems will have long=64-bit, int=32-bit */ +# ifndef MUSCLE_64_BIT_PLATFORM +# define MUSCLE_64_BIT_PLATFORM 1 // auto-define it if it wasn't defined in the Makefile +# endif + typedef int int32; +# ifndef _UINT32 // Avoid conflict with typedef in OS/X Leopard system header + typedef unsigned int uint32; +# define _UINT32 // Avoid conflict with typedef in OS/X Leopard system header +# endif +# else + typedef long int32; +# ifndef _UINT32 // Avoid conflict with typedef in OS/X Leopard system header + typedef unsigned long uint32; +# define _UINT32 // Avoid conflict with typedef in OS/X Leopard system header +# endif +# endif +# if defined(WIN32) && !defined(__GNUWIN32__) + typedef __int64 int64; + typedef unsigned __int64 uint64; +# elif __APPLE__ +# ifndef _UINT64 // Avoid conflict with typedef in OS/X Leopard system header +# define _UINT64 + typedef long long int64; // these are what's expected in MacOS/X land, in both + typedef unsigned long long uint64; // 32-bit and 64-bit flavors. C'est la vie, non? +# endif +# elif defined(MUSCLE_64_BIT_PLATFORM) + typedef long int64; + typedef unsigned long uint64; +# else + typedef long long int64; + typedef unsigned long long uint64; +# endif + typedef int32 status_t; +# endif /* !MUSCLE_TYPES_PREDEFINED */ +# endif /* !__ATHEOS__*/ +#endif /* __BEOS__ || __HAIKU__ */ + +/** Ugly platform-neutral macros for problematic sprintf()-format-strings */ +#if defined(MUSCLE_64_BIT_PLATFORM) +# define INT32_FORMAT_SPEC_NOPERCENT "i" +# define UINT32_FORMAT_SPEC_NOPERCENT "u" +# define XINT32_FORMAT_SPEC_NOPERCENT "x" +# ifdef __APPLE__ +# define INT64_FORMAT_SPEC_NOPERCENT "lli" +# define UINT64_FORMAT_SPEC_NOPERCENT "llu" +# define XINT64_FORMAT_SPEC_NOPERCENT "llx" +# else +# define INT64_FORMAT_SPEC_NOPERCENT "li" +# define UINT64_FORMAT_SPEC_NOPERCENT "lu" +# define XINT64_FORMAT_SPEC_NOPERCENT "lx" +# endif +#else +# define INT32_FORMAT_SPEC_NOPERCENT "li" +# define UINT32_FORMAT_SPEC_NOPERCENT "lu" +# define XINT32_FORMAT_SPEC_NOPERCENT "lx" +# if defined(_MSC_VER) +# define INT64_FORMAT_SPEC_NOPERCENT "I64i" +# define UINT64_FORMAT_SPEC_NOPERCENT "I64u" +# define XINT64_FORMAT_SPEC_NOPERCENT "I64x" +# elif (defined(__BEOS__) && !defined(__HAIKU__)) || defined(__MWERKS__) || defined(__BORLANDC__) +# define INT64_FORMAT_SPEC_NOPERCENT "Li" +# define UINT64_FORMAT_SPEC_NOPERCENT "Lu" +# define XINT64_FORMAT_SPEC_NOPERCENT "Lx" +# else +# define INT64_FORMAT_SPEC_NOPERCENT "lli" +# define UINT64_FORMAT_SPEC_NOPERCENT "llu" +# define XINT64_FORMAT_SPEC_NOPERCENT "llx" +# endif +#endif + +# define INT32_FORMAT_SPEC "%" INT32_FORMAT_SPEC_NOPERCENT +# define UINT32_FORMAT_SPEC "%" UINT32_FORMAT_SPEC_NOPERCENT +# define XINT32_FORMAT_SPEC "%" XINT32_FORMAT_SPEC_NOPERCENT + +# define INT64_FORMAT_SPEC "%" INT64_FORMAT_SPEC_NOPERCENT +# define UINT64_FORMAT_SPEC "%" UINT64_FORMAT_SPEC_NOPERCENT +# define XINT64_FORMAT_SPEC "%" XINT64_FORMAT_SPEC_NOPERCENT + +#define MAKETYPE(x) ((((unsigned long)(x[0])) << 24) | \ + (((unsigned long)(x[1])) << 16) | \ + (((unsigned long)(x[2])) << 8) | \ + (((unsigned long)(x[3])) << 0)) + +#if !defined(__BEOS__) && !defined(__HAIKU__) +/* Be-style message-field type codes. + * I've calculated the integer equivalents for these codes + * because gcc whines like a little girl about the four-byte + * constants when compiling under Linux --jaf + */ +enum { + B_ANY_TYPE = 1095653716, /* 'ANYT' = wild card */ + B_BOOL_TYPE = 1112493900, /* 'BOOL' = boolean (1 byte per bool) */ + B_DOUBLE_TYPE = 1145195589, /* 'DBLE' = double-precision float (8 bytes per double) */ + B_FLOAT_TYPE = 1179406164, /* 'FLOT' = single-precision float (4 bytes per float) */ + B_INT64_TYPE = 1280069191, /* 'LLNG' = long long integer (8 bytes per int) */ + B_INT32_TYPE = 1280265799, /* 'LONG' = long integer (4 bytes per int) */ + B_INT16_TYPE = 1397248596, /* 'SHRT' = short integer (2 bytes per int) */ + B_INT8_TYPE = 1113150533, /* 'BYTE' = byte integer (1 byte per int) */ + B_MESSAGE_TYPE = 1297303367, /* 'MSGG' = sub Message objects (reference counted) */ + B_POINTER_TYPE = 1347310674, /* 'PNTR' = pointers (will not be flattened) */ + B_POINT_TYPE = 1112559188, /* 'BPNT' = Point objects (each Point has two floats) */ + B_RECT_TYPE = 1380270932, /* 'RECT' = Rect objects (each Rect has four floats) */ + B_STRING_TYPE = 1129534546, /* 'CSTR' = String objects (variable length) */ + B_OBJECT_TYPE = 1330664530, /* 'OPTR' = Flattened user objects (obsolete) */ + B_RAW_TYPE = 1380013908, /* 'RAWT' = Raw data (variable number of bytes) */ + B_MIME_TYPE = 1296649541 /* 'MIME' = MIME strings (obsolete) */ +}; +#endif + +/* This one isn't defined by BeOS, so we have to enumerate it separately. */ +enum { + B_TAG_TYPE = 1297367367 /* 'MTAG' = new for v2.00; for in-mem-only tags */ +}; + +/* This constant is used in various places to mean 'as much as you want' */ +#define MUSCLE_NO_LIMIT ((uint32)-1) + +#ifdef __cplusplus + +/** A handy little method to swap the bytes of any int-style datatype around */ +template inline T muscleSwapBytes(T swapMe) +{ + union {T _iWide; uint8 _i8[sizeof(T)];} u1, u2; + u1._iWide = swapMe; + + int i = 0; + int numBytes = sizeof(T); + while(numBytes>0) u2._i8[i++] = u1._i8[--numBytes]; + return u2._iWide; +} + +/* This template safely copies a value in from an untyped byte buffer to a typed value, and returns the typed value. */ +template inline T muscleCopyIn(const void * source) {T dest; memcpy(&dest, source, sizeof(dest)); return dest;} + +/* This template safely copies a value in from an untyped byte buffer to a typed value. */ +template inline void muscleCopyIn(T & dest, const void * source) {memcpy(&dest, source, sizeof(dest));} + +/** This template safely copies a value in from a typed value to an untyped byte buffer. */ +template inline void muscleCopyOut(void * dest, const T & source) {memcpy(dest, &source, sizeof(source));} + +/** This macro should be used instead of "newnothrow T[count]". It works the + * same, except that it hacks around an ugly bug in gcc 3.x where newnothrow + * would return ((T*)0x4) on memory failure instead of NULL. + * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10300 + */ +#if __GNUC__ == 3 +template inline T * broken_gcc_newnothrow_array(size_t count) +{ + T * ret = newnothrow T[count]; + return (ret <= (T *)(sizeof(void *))) ? NULL : ret; +} +# define newnothrow_array(T, count) broken_gcc_newnothrow_array(count) +#else +# define newnothrow_array(T, count) newnothrow T[count] +#endif + +/** This function returns a reference to a read-only, default-constructed + * static object of type T. There will be exactly one of these objects + * present per instantiated type, per process. + */ +template const T & GetDefaultObjectForType() +{ + static T _defaultObject; + return _defaultObject; +} + +/** This function returns a reference to a read/write, default-constructed + * static object of type T. There will be exactly one of these objects + * present per instantiated type, per process. + * + * Note that this object is different from the one returned by + * GetDefaultObjectForType(); the difference (other than there being + * possibly two separate objects) is that this one can be written to, + * whereas GetDefaultObjectForType()'s object must always be in its default state. + */ +template T & GetGlobalObjectForType() +{ + static T _defaultObject; + return _defaultObject; +} + +#if __GNUC__ >= 4 +# define MUSCLE_MAY_ALIAS __attribute__((__may_alias__)) +#else +# define MUSCLE_MAY_ALIAS +#endif + +/** Returns the smallest of the two arguments */ +template inline const T & muscleMin(const T & p1, const T & p2) {return (p1 < p2) ? p1 : p2;} + +/** Returns the smallest of the three arguments */ +template inline const T & muscleMin(const T & p1, const T & p2, const T & p3) {return muscleMin(p3, muscleMin(p1, p2));} + +/** Returns the smallest of the four arguments */ +template inline const T & muscleMin(const T & p1, const T & p2, const T & p3, const T & p4) {return muscleMin(p3, p4, muscleMin(p1, p2));} + +/** Returns the smallest of the five arguments */ +template inline const T & muscleMin(const T & p1, const T & p2, const T & p3, const T & p4, const T & p5) {return muscleMin(p3, p4, p5, muscleMin(p1, p2));} + +/** Returns the largest of the two arguments */ +template inline const T & muscleMax(const T & p1, const T & p2) {return (p1 < p2) ? p2 : p1;} + +/** Returns the largest of the three arguments */ +template inline const T & muscleMax(const T & p1, const T & p2, const T & p3) {return muscleMax(p3, muscleMax(p1, p2));} + +/** Returns the largest of the four arguments */ +template inline const T & muscleMax(const T & p1, const T & p2, const T & p3, const T & p4) {return muscleMax(p3, p4, muscleMax(p1, p2));} + +/** Returns the largest of the five arguments */ +template inline const T & muscleMax(const T & p1, const T & p2, const T & p3, const T & p4, const T & p5) {return muscleMax(p3, p4, p5, muscleMax(p1, p2));} + +namespace ugly_swapcontents_method_sfinae_implementation +{ + // This code was from the example code at http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Member_Functions_at_Compile-Time + // It is used by the muscleSwap() function (below) to automatically call SwapContents() if such a method + // is available, otherwise muscleSwap() will use a naive copy-to-temporary-object technique. + + typedef char yes; + typedef char (&no)[2]; + template struct test_swapcontents_wrapper {}; + + // via SFINAE only one of these overloads will be considered + template yes swapcontents_tester(test_swapcontents_wrapper*); + template no swapcontents_tester(...); + + template struct test_swapcontents_impl {static const bool value = sizeof(swapcontents_tester(0)) == sizeof(yes);}; + + template struct has_swapcontents_method : test_swapcontents_impl {}; + template struct if_; + template struct if_ {typedef TrueResult result;}; + template struct if_ {typedef FalseResult result;}; + + template class PODSwapper + { + public: + PODSwapper(T & t1, T & t2) + { +#ifdef MUSCLE_USE_CPLUSPLUS11 + T tmp(std::move(t1)); + t1 = std::move(t2); + t2 = std::move(tmp); +#else + T tmp = t1; + t1 = t2; + t2 = tmp; +#endif + } + }; + + template class SwapContentsSwapper + { + public: + SwapContentsSwapper(T & t1, T & t2) {t1.SwapContents(t2);} + }; + + template class AutoChooseSwapperHelper + { + public: + typedef typename if_::value, SwapContentsSwapper, PODSwapper >::result Type; + }; +} + +/** Swaps the two arguments. + * @param t1 First item to swap. After this method returns, it will be equal to the old value of t2. + * @param t2 Second item to swap. After this method returns, it will be equal to the old value of t1. + */ +template inline void muscleSwap(T & t1, T & t2) {typename ugly_swapcontents_method_sfinae_implementation::AutoChooseSwapperHelper::Type swapper(t1,t2);} + +/** Returns true iff (i) is a valid index into array (a) + * @param i An index value + * @param array an array of any type + * @returns True iff i is non-negative AND less than ARRAYITEMS(array)) + */ +template inline bool muscleArrayIndexIsValid(int i, T(&)[size] /*array*/) {return (((unsigned int)i) < size);} + +/** Returns the value nearest to (v) that is still in the range [lo, hi]. + * @param v A value + * @param lo a minimum value + * @param hi a maximum value + * @returns The value in the range [lo, hi] that is closest to (v). + */ +template inline const T & muscleClamp(const T & v, const T & lo, const T & hi) {return (v < lo) ? lo : ((v > hi) ? hi : v);} + +/** Returns true iff (v) is in the range [lo,hi]. + * @param v A value + * @param lo A minimum value + * @param hi A maximum value + * @returns true iff (v >= lo) and (v <= hi) + */ +template inline bool muscleInRange(const T & v, const T & lo, const T & hi) {return ((v >= lo)&&(v <= hi));} + +/** Returns -1 if arg1 is larger, or 1 if arg2 is larger, or 0 if they are equal. + * @param arg1 First item to compare + * @param arg2 Second item to compare + * @returns -1 if (arg1 inline int muscleCompare(const T & arg1, const T & arg2) {return (arg2 inline T muscleAbs(const T & arg) {return (arg<0)?(-arg):arg;} + +/** Rounds the given float to the nearest integer value. */ +inline int muscleRintf(float f) {return (f>=0.0f) ? ((int)(f+0.5f)) : -((int)((-f)+0.5f));} + +/** Returns -1 if the value is less than zero, +1 if it is greater than zero, or 0 otherwise. */ +template inline int muscleSgn(const T & arg) {return (arg<0)?-1:((arg>0)?1:0);} + +#endif /* __cplusplus */ + +#if !defined(__BEOS__) && !defined(__HAIKU__) + +/* + * Copyright(c) 1983, 1989 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * from nameser.h 8.1 (Berkeley) 6/2/93 + */ + +#ifndef BYTE_ORDER + #if (BSD >= 199103) + #include + #else + #if defined(linux) || defined(__linux__) + #include + #elif defined( __APPLE__ ) + #include + #else + #define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */ + #define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ + + #if defined(vax) || defined(ns32000) || defined(sun386) || defined(i386) || \ + defined(__i386) || defined(__ia64) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ + defined(__alpha__) || defined(__alpha) || defined(__CYGWIN__) || \ + defined(_M_IX86) || defined(_M_AMD64) || defined(__GNUWIN32__) || defined(__LITTLEENDIAN__) || \ + (defined(__Lynx__) && defined(__x86__)) + #define BYTE_ORDER LITTLE_ENDIAN + #endif + + #if defined(__POWERPC__) || defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ + defined(__sparc) || \ + defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ + defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\ + defined(apollo) || defined(__convex__) || defined(_CRAY) || \ + defined(__hppa) || defined(__hp9000) || \ + defined(__hp9000s300) || defined(__hp9000s700) || \ + defined(__hp3000s900) || defined(MPE) || \ + defined(BIT_ZERO_ON_LEFT) || defined(m68k) || \ + (defined(__Lynx__) && \ + (defined(__68k__) || defined(__sparc__) || defined(__powerpc__))) + #define BYTE_ORDER BIG_ENDIAN + #endif + #endif /* linux */ + #endif /* BSD */ +#endif /* BYTE_ORDER */ + +#if !defined(BYTE_ORDER) || (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN) + /* + * you must determine what the correct bit order is for + * your compiler - the next line is an intentional error + * which will force your compiles to bomb until you fix + * the above macros. + */ +# error "Undefined or invalid BYTE_ORDER -- you will need to modify MuscleSupport.h to correct this"; +#endif + +/* End replacement code from Sun/University of California */ + +/*********************************************************************************************** + * FLOAT_TROUBLE COMMENT + * + * NOTE: The *_FLOAT_* macros listed below are obsolete and must no longer be used, because + * they are inherently unsafe. The reason is that on certain processors (read x86), the + * byte-swapped representation of certain floating point and double values can end up + * representing an invalid value (NaN)... in which case the x86 FPU feels free to munge + * some of the bits into a "standard NaN" bit-pattern... causing silent data corruption + * when the value is later swapped back into its native form and again interpreted as a + * float or double value. Instead, you need to change your code to use the *_IFLOAT_* + * macros, which work similarly except that the externalized value is safely stored as + * a int32 (for floats) or a int64 (for doubles). --Jeremy 1/8/2007 + **********************************************************************************************/ + +#define B_HOST_TO_BENDIAN_FLOAT(arg) B_HOST_TO_BENDIAN_FLOAT_was_removed_use_B_HOST_TO_BENDIAN_IFLOAT_instead___See_the_FLOAT_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_HOST_TO_LENDIAN_FLOAT(arg) B_HOST_TO_LENDIAN_FLOAT_was_removed_use_B_HOST_TO_LENDIAN_IFLOAT_instead___See_the_FLOAT_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_BENDIAN_TO_HOST_FLOAT(arg) B_BENDIAN_TO_HOST_FLOAT_was_removed_use_B_BENDIAN_TO_HOST_IFLOAT_instead___See_the_FLOAT_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_LENDIAN_TO_HOST_FLOAT(arg) B_LENDIAN_TO_HOST_FLOAT_was_removed_use_B_LENDIAN_TO_HOST_IFLOAT_instead___See_the_FLOAT_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_SWAP_FLOAT(arg) B_SWAP_FLOAT_was_removed___See_the_FLOAT_TROUBLE_comment_in_support_MuscleSupport_h_for_details. + +/*********************************************************************************************** + * DOUBLE_TROUBLE COMMENT + * + * NOTE: The *_DOUBLE_* macros listed below are obsolete and must no longer be used, because + * they are inherently unsafe. The reason is that on certain processors (read x86), the + * byte-swapped representation of certain floating point and double values can end up + * representing an invalid value (NaN)... in which case the x86 FPU feels free to munge + * some of the bits into a "standard NaN" bit-pattern... causing silent data corruption + * when the value is later swapped back into its native form and again interpreted as a + * float or double value. Instead, you need to change your code to use the *_IDOUBLE_* + * macros, which work similarly except that the externalized value is safely stored as + * a int32 (for floats) or a int64 (for doubles). --Jeremy 1/8/2007 + **********************************************************************************************/ + +#define B_HOST_TO_BENDIAN_DOUBLE(arg) B_HOST_TO_BENDIAN_DOUBLE_was_removed_use_B_HOST_TO_BENDIAN_IDOUBLE_instead___See_the_DOUBLE_TROUBLE_comment_in_support/MuscleSupport_h_for_details_ +#define B_HOST_TO_LENDIAN_DOUBLE(arg) B_HOST_TO_LENDIAN_DOUBLE_was_removed_use_B_HOST_TO_LENDIAN_IDOUBLE_instead___See_the_DOUBLE_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_BENDIAN_TO_HOST_DOUBLE(arg) B_BENDIAN_TO_HOST_DOUBLE_was_removed_use_B_BENDIAN_TO_HOST_IDOUBLE_instead___See_the_DOUBLE_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_LENDIAN_TO_HOST_DOUBLE(arg) B_LENDIAN_TO_HOST_DOUBLE_was_removed_use_B_LENDIAN_TO_HOST_IDOUBLE_instead___See_the_DOUBLE_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ +#define B_SWAP_DOUBLE(arg) B_SWAP_DOUBLE_was_removed___See_the_DOUBLE_TROUBLE_comment_in_support_MuscleSupport_h_for_details_ + +# if defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) +static inline uint16 MusclePowerPCSwapInt16(uint16 val) +{ + uint16 a; + uint16 * addr = &a; + __asm__ ("sthbrx %1,0,%2" : "=m" (*addr) : "r" (val), "r" (addr)); + return a; +} +static inline uint32 MusclePowerPCSwapInt32(uint32 val) +{ + uint32 a; + uint32 * addr = &a; + __asm__ ("stwbrx %1,0,%2" : "=m" (*addr) : "r" (val), "r" (addr)); + return a; +} +static inline uint64 MusclePowerPCSwapInt64(uint64 val) +{ + return ((uint64)(MusclePowerPCSwapInt32((uint32)((val>>32)&0xFFFFFFFF))))|(((uint64)(MusclePowerPCSwapInt32((uint32)(val&0xFFFFFFFF))))<<32); +} +# define B_SWAP_INT64(arg) MusclePowerPCSwapInt64((uint64)(arg)) +# define B_SWAP_INT32(arg) MusclePowerPCSwapInt32((uint32)(arg)) +# define B_SWAP_INT16(arg) MusclePowerPCSwapInt16((uint16)(arg)) +# elif defined(MUSCLE_USE_MSVC_SWAP_FUNCTIONS) +# define B_SWAP_INT64(arg) _byteswap_uint64((uint64)(arg)) +# define B_SWAP_INT32(arg) _byteswap_ulong((uint32)(arg)) +# define B_SWAP_INT16(arg) _byteswap_ushort((uint16)(arg)) +# elif defined(MUSCLE_USE_X86_INLINE_ASSEMBLY) +static inline uint16 MuscleX86SwapInt16(uint16 val) +{ +#ifdef _MSC_VER + __asm { + mov ax, val; + xchg al, ah; + mov val, ax; + }; +#elif defined(MUSCLE_64_BIT_PLATFORM) + __asm__ ("xchgb %h0, %b0" : "+Q" (val)); +#else + __asm__ ("xchgb %h0, %b0" : "+q" (val)); +#endif + return val; +} +static inline uint32 MuscleX86SwapInt32(uint32 val) +{ +#ifdef _MSC_VER + __asm { + mov eax, val; + bswap eax; + mov val, eax; + }; +#else + __asm__ ("bswap %0" : "+r" (val)); +#endif + return val; +} +static inline uint64 MuscleX86SwapInt64(uint64 val) +{ +#ifdef _MSC_VER + __asm { + mov eax, DWORD PTR val; + mov edx, DWORD PTR val + 4; + bswap eax; + bswap edx; + mov DWORD PTR val, edx; + mov DWORD PTR val + 4, eax; + }; + return val; +#elif defined(MUSCLE_64_BIT_PLATFORM) + __asm__ ("bswap %0" : "=r" (val) : "0" (val)); + return val; +#else + return ((uint64)(MuscleX86SwapInt32((uint32)((val>>32)&0xFFFFFFFF))))|(((uint64)(MuscleX86SwapInt32((uint32)(val&0xFFFFFFFF))))<<32); +#endif +} +# define B_SWAP_INT64(arg) MuscleX86SwapInt64((uint64)(arg)) +# define B_SWAP_INT32(arg) MuscleX86SwapInt32((uint32)(arg)) +# define B_SWAP_INT16(arg) MuscleX86SwapInt16((uint16)(arg)) +# else + +// No assembly language available... so we'll use inline C + +# if defined(__cplusplus) +# define MUSCLE_INLINE inline +# else +# define MUSCLE_INLINE static inline +# endif + +MUSCLE_INLINE int64 B_SWAP_INT64(int64 arg) +{ + union {int64 _i64; uint8 _i8[8];} u1, u2; + u1._i64 = arg; + u2._i8[0] = u1._i8[7]; + u2._i8[1] = u1._i8[6]; + u2._i8[2] = u1._i8[5]; + u2._i8[3] = u1._i8[4]; + u2._i8[4] = u1._i8[3]; + u2._i8[5] = u1._i8[2]; + u2._i8[6] = u1._i8[1]; + u2._i8[7] = u1._i8[0]; + return u2._i64; +} +MUSCLE_INLINE int32 B_SWAP_INT32(int32 arg) +{ + union {int32 _i32; uint8 _i8[4];} u1, u2; + u1._i32 = arg; + u2._i8[0] = u1._i8[3]; + u2._i8[1] = u1._i8[2]; + u2._i8[2] = u1._i8[1]; + u2._i8[3] = u1._i8[0]; + return u2._i32; +} +MUSCLE_INLINE int16 B_SWAP_INT16(int16 arg) +{ + union {int16 _i16; uint8 _i8[2];} u1, u2; + u1._i16 = arg; + u2._i8[0] = u1._i8[1]; + u2._i8[1] = u1._i8[0]; + return u2._i16; +} +# endif +# if BYTE_ORDER == LITTLE_ENDIAN +# define B_HOST_IS_LENDIAN 1 +# define B_HOST_IS_BENDIAN 0 +# define B_HOST_TO_LENDIAN_INT64(arg) ((uint64)(arg)) +# define B_HOST_TO_LENDIAN_INT32(arg) ((uint32)(arg)) +# define B_HOST_TO_LENDIAN_INT16(arg) ((uint16)(arg)) +# define B_HOST_TO_BENDIAN_INT64(arg) B_SWAP_INT64(arg) +# define B_HOST_TO_BENDIAN_INT32(arg) B_SWAP_INT32(arg) +# define B_HOST_TO_BENDIAN_INT16(arg) B_SWAP_INT16(arg) +# define B_LENDIAN_TO_HOST_INT64(arg) ((uint64)(arg)) +# define B_LENDIAN_TO_HOST_INT32(arg) ((uint32)(arg)) +# define B_LENDIAN_TO_HOST_INT16(arg) ((uint16)(arg)) +# define B_BENDIAN_TO_HOST_INT64(arg) B_SWAP_INT64(arg) +# define B_BENDIAN_TO_HOST_INT32(arg) B_SWAP_INT32(arg) +# define B_BENDIAN_TO_HOST_INT16(arg) B_SWAP_INT16(arg) +# else +# define B_HOST_IS_LENDIAN 0 +# define B_HOST_IS_BENDIAN 1 +# define B_HOST_TO_LENDIAN_INT64(arg) B_SWAP_INT64(arg) +# define B_HOST_TO_LENDIAN_INT32(arg) B_SWAP_INT32(arg) +# define B_HOST_TO_LENDIAN_INT16(arg) B_SWAP_INT16(arg) +# define B_HOST_TO_BENDIAN_INT64(arg) ((uint64)(arg)) +# define B_HOST_TO_BENDIAN_INT32(arg) ((uint32)(arg)) +# define B_HOST_TO_BENDIAN_INT16(arg) ((uint16)(arg)) +# define B_LENDIAN_TO_HOST_INT64(arg) B_SWAP_INT64(arg) +# define B_LENDIAN_TO_HOST_INT32(arg) B_SWAP_INT32(arg) +# define B_LENDIAN_TO_HOST_INT16(arg) B_SWAP_INT16(arg) +# define B_BENDIAN_TO_HOST_INT64(arg) ((uint64)(arg)) +# define B_BENDIAN_TO_HOST_INT32(arg) ((uint32)(arg)) +# define B_BENDIAN_TO_HOST_INT16(arg) ((uint16)(arg)) +# endif +#endif /* !__BEOS__ && !__HAIKU__*/ + +/** Newer, architecture-safe float and double endian-ness swappers. Note that for these + * macros, the externalized value is expected to be stored in an int32 (for floats) or + * in an int64 (for doubles). See the FLOAT_TROUBLE and DOUBLE_TROUBLE comments elsewhere + * in this header file for an explanation as to why. --Jeremy 01/08/2007 + */ + +/* Yes, the memcpy() is necessary... mere pointer-casting plus assignment operations don't cut it under x86 */ +static inline uint32 B_REINTERPRET_FLOAT_AS_INT32(float arg) {uint32 r; memcpy(&r, &arg, sizeof(r)); return r;} +static inline float B_REINTERPRET_INT32_AS_FLOAT(uint32 arg) {float r; memcpy(&r, &arg, sizeof(r)); return r;} +static inline uint64 B_REINTERPRET_DOUBLE_AS_INT64(double arg) {uint64 r; memcpy(&r, &arg, sizeof(r)); return r;} +static inline double B_REINTERPRET_INT64_AS_DOUBLE(uint64 arg) {double r; memcpy(&r, &arg, sizeof(r)); return r;} + +#define B_HOST_TO_BENDIAN_IFLOAT(arg) B_HOST_TO_BENDIAN_INT32(B_REINTERPRET_FLOAT_AS_INT32(arg)) +#define B_BENDIAN_TO_HOST_IFLOAT(arg) B_REINTERPRET_INT32_AS_FLOAT(B_BENDIAN_TO_HOST_INT32(arg)) +#define B_HOST_TO_LENDIAN_IFLOAT(arg) B_HOST_TO_LENDIAN_INT32(B_REINTERPRET_FLOAT_AS_INT32(arg)) +#define B_LENDIAN_TO_HOST_IFLOAT(arg) B_REINTERPRET_INT32_AS_FLOAT(B_LENDIAN_TO_HOST_INT32(arg)) + +#define B_HOST_TO_BENDIAN_IDOUBLE(arg) B_HOST_TO_BENDIAN_INT64(B_REINTERPRET_DOUBLE_AS_INT64(arg)) +#define B_BENDIAN_TO_HOST_IDOUBLE(arg) B_REINTERPRET_INT64_AS_DOUBLE(B_BENDIAN_TO_HOST_INT64(arg)) +#define B_HOST_TO_LENDIAN_IDOUBLE(arg) B_HOST_TO_LENDIAN_INT64(B_REINTERPRET_DOUBLE_AS_INT64(arg)) +#define B_LENDIAN_TO_HOST_IDOUBLE(arg) B_REINTERPRET_INT64_AS_DOUBLE(B_LENDIAN_TO_HOST_INT64(arg)) + +/* Macro to turn a type code into a string representation. + * (typecode) is the type code to get the string for + * (buf) is a (char *) to hold the output string; it must be >= 5 bytes long. + */ +static inline void MakePrettyTypeCodeString(uint32 typecode, char *buf) +{ + uint32 i; // keep separate, for C compatibility + uint32 bigEndian = B_HOST_TO_BENDIAN_INT32(typecode); + + memcpy(buf, (const char *)&bigEndian, sizeof(bigEndian)); + buf[sizeof(bigEndian)] = '\0'; + for (i=0; i'~')) buf[i] = '?'; +} + +#ifdef WIN32 +# include // this will bring in windows.h for us +#endif + +#ifdef _MSC_VER +typedef UINT_PTR uintptr; // Use these under MSVC so that the compiler +typedef INT_PTR ptrdiff; // doesn't give spurious warnings in /Wp64 mode +#else +# if defined(MUSCLE_64_BIT_PLATFORM) +typedef uint64 uintptr; +typedef int64 ptrdiff; +# else +typedef uint32 uintptr; +typedef int32 ptrdiff; +# endif +#endif + +#ifdef __cplusplus +# include "syslog/SysLog.h" /* for LogTime() */ +#endif /* __cplusplus */ + +/** Checks errno and returns true iff the last I/O operation + * failed because it would have had to block otherwise. + * NOTE: Returns int so that it will compile even in C environments where no bool type is defined. + */ +static inline int PreviousOperationWouldBlock() +{ +#ifdef WIN32 + return (WSAGetLastError() == WSAEWOULDBLOCK); +#else + return (errno == EWOULDBLOCK); +#endif +} + +/** Checks errno and returns true iff the last I/O operation + * failed because it was interrupted by a signal or etc. + * NOTE: Returns int so that it will compile even in C environments where no bool type is defined. + */ +static inline int PreviousOperationWasInterrupted() +{ +#ifdef WIN32 + return (WSAGetLastError() == WSAEINTR); +#else + return (errno == EINTR); +#endif +} + +/** Checks errno and returns true iff the last I/O operation + * failed because of a transient condition which wasn't fatal to the socket. + * NOTE: Returns int so that it will compile even in C environments where no bool type is defined. + */ +static inline int PreviousOperationHadTransientFailure() +{ +#ifdef WIN32 + int e = WSAGetLastError(); + return ((e == WSAEINTR)||(e == WSAENOBUFS)); +#else + int e = errno; + return ((e == EINTR)||(e == ENOBUFS)); +#endif +} + +/** This function applies semi-standard logic to convert the return value + * of a system I/O call and (errno) into a proper MUSCLE-standard return value. + * (A MUSCLE-standard return value's semantics are: Negative on error, + * otherwise the return value is the number of bytes that were transferred) + * @param origRet The return value of the original system call (e.g. to read()/write()/send()/recv()) + * @param maxSize The maximum number of bytes that the system call was permitted to send during that call. + * @param blocking True iff the socket/file descriptor is in blocking I/O mode. (Type is int for C compatibility -- it's really a boolean parameter) + * @returns The system call's return value equivalent in MUSCLE return value semantics. + */ +static inline int32 ConvertReturnValueToMuscleSemantics(int origRet, uint32 maxSize, int blocking) +{ + int32 retForBlocking = ((origRet > 0)||(maxSize == 0)) ? origRet : -1; + return blocking ? retForBlocking : ((origRet<0)&&((PreviousOperationWouldBlock())||(PreviousOperationHadTransientFailure()))) ? 0 : retForBlocking; +} + +#ifdef __cplusplus +namespace muscle { +#endif + +#if MUSCLE_TRACE_CHECKPOINTS > 0 + +/** Exposed as an implementation detail. Please ignore! */ +extern volatile uint32 * _muscleTraceValues; + +/** Exposed as an implementation detail. Please ignore! */ +extern uint32 _muscleNextTraceValueIndex; + +/** Sets the location of the trace-checkpoints array to store trace checkpoints into. + * @param location A pointer to an array of at least (MUSCLE_TRACE_CHECKPOINTS) uint32s, or NULL. + * If NULL (or if this function is never called), the default array will be used. + */ +void SetTraceValuesLocation(volatile uint32 * location); + +/** Set this process's current trace value to (v). This can be used as a primitive debugging tool, to determine + * where this process was last seen executing -- useful for determining where the process is spinning at. + * @note this function is a no-op if MUSCLE_TRACE_CHECKPOINTS is not defined to a value greater than zero. + */ +static inline void StoreTraceValue(uint32 v) +{ + _muscleTraceValues[_muscleNextTraceValueIndex] = v; /* store the current value */ + _muscleNextTraceValueIndex = (_muscleNextTraceValueIndex+1)%MUSCLE_TRACE_CHECKPOINTS; /* move the pointer */ + _muscleTraceValues[_muscleNextTraceValueIndex] = MUSCLE_NO_LIMIT; /* mark the next position with a special tag to show that it's next */ +} + +/** Returns a pointer to the first value in the trace-values array. */ +static inline const volatile uint32 * GetTraceValues() {return _muscleTraceValues;} + +/** A macro for automatically setting a trace checkpoint value based on current code location. + * The value will be the two characters of the function or file name, left-shifted by 16 bits, + * and then OR'd together with the current line number. This should give the debugging person a + * fairly good clue as to where the checkpoint was located, while still being very cheap to implement. + * + * @note This function will be a no-op unless MUSCLE_TRACE_CHECKPOINTS is defined to be greater than zero. + */ +#if defined(__GNUC__) +#define TCHECKPOINT \ +{ \ + const char * d = __FUNCTION__; \ + StoreTraceValue((d[0]<<24)|(d[1]<<16)|(__LINE__)); \ +} +#else +#define TCHECKPOINT \ +{ \ + const char * d = __FILE__; \ + StoreTraceValue((d[0]<<24)|(d[1]<<16)|(__LINE__)); \ +} +#endif + +#else +/* no-op implementations for when we aren't using the trace facility */ +static inline void SetTraceValuesLocation(volatile uint32 * location) {(void) location;} /* named param is necessary for C compatibility */ +static inline void StoreTraceValue(uint32 v) {(void) v;} /* named param is necessary for C compatibility */ +#define TCHECKPOINT {/* empty */} +#endif + +#ifdef __cplusplus + +/** This templated class is used as a "comparison callback" for sorting items in a Queue or Hashtable. + * For many types, this default CompareFunctor template will do the job, but you also have the option of specifying + * a different custom CompareFunctor for times when you want to sort in ways other than simply using the + * less than and equals operators of the ItemType type. + */ +template class CompareFunctor +{ +public: + /** + * This is the signature of the type of callback function that you must pass + * into the Sort() method. This function should work like strcmp(), returning + * a negative value if (item1) is less than item2, or zero if the items are + * equal, or a positive value if (item1) is greater than item2. + * The default implementation simply calls the muscleCompare() function. + * @param item1 The first item + * @param item2 The second item + * @param cookie A user-defined value that was passed in to the Sort() method. + * @return A value indicating which item is "larger", as defined above. + */ + int Compare(const ItemType & item1, const ItemType & item2, void * /*cookie*/) const {return muscleCompare(item1, item2);} +}; + +/** Same as above, but used for pointers instead of direct items */ +template class CompareFunctor +{ +public: + int Compare(const ItemType * item1, const ItemType * item2, void * cookie) const {return CompareFunctor().Compare(*item1, *item2, cookie);} +}; + +/** For void pointers, we just compare the pointer values, since they can't be de-referenced. */ +template<> class CompareFunctor +{ +public: + int Compare(void * s1, void * s2, void * /*cookie*/) const {return muscleCompare(s1, s2);} +}; + +/** We assume that (const char *)'s should always be compared using strcmp(). */ +template<> class CompareFunctor +{ +public: + int Compare(const char * s1, const char * s2, void * /*cookie*/) const {return strcmp(s1, s2);} +}; + +/** We assume that (const char *)'s should always be compared using strcmp(). */ +template<> class CompareFunctor +{ +public: + int Compare(char * s1, char * s2, void * /*cookie*/) const {return strcmp(s1, s2);} +}; + +/** For cases where you really do want to just use a pointer as the key, instead + * of cleverly trying to dereference the object it points to and sorting on that, you can specify this. + */ +template class PointerCompareFunctor +{ +public: + int Compare(PointerType s1, PointerType s2, void * /*cookie*/) const {return muscleCompare(s1, s2);} +}; + +/** Hash function for arbitrary data. Note that the current implementation of this function + * is MurmurHash2/Aligned, taken from http://murmurhash.googlepages.com/ and used as public domain code. + * Thanks to Austin Appleby for the cool algorithm! + * Note that these hash codes should not be passed outside of the + * host that generated them, as different host architectures may give + * different hash results for the same key data. + * @param key Pointer to the data to hash + * @param numBytes Number of bytes to hash start at (key) + * @param seed An arbitrary number that affects the output values. Defaults to zero. + * @returns a 32-bit hash value corresponding to the hashed data. + */ +uint32 CalculateHashCode(const void * key, uint32 numBytes, uint32 seed = 0); + +/** Same as HashCode(), but this version produces a 64-bit result. + * This code is also part of MurmurHash2, written by Austin Appleby + * Note that these hash codes should not be passed outside of the + * host that generated them, as different host architectures may give + * different hash results for the same key data. + * @param key Pointer to the data to hash + * @param numBytes Number of bytes to hash start at (key) + * @param seed An arbitrary number that affects the output values. Defaults to zero. + * @returns a 32-bit hash value corresponding to the hashed data. + */ +uint64 CalculateHashCode64(const void * key, unsigned int numBytes, unsigned int seed = 0); + +/** Convenience method; returns the hash code of the given data item. Any POD type will do. + * @param val The value to calculate a hashcode for + * @returns a hash code. + */ +template inline uint32 CalculateHashCode(const T & val) {return CalculateHashCode(&val, sizeof(val));} + +/** Convenience method; returns the 64-bit hash code of the given data item. Any POD type will do. + * @param val The value to calculate a hashcode for + * @returns a hash code. + */ +template inline uint64 CalculateHashCode64(const T & val) {return CalculateHashCode64(&val, sizeof(val));} + +/** This is a convenience function that will read through the passed-in byte + * buffer and create a 32-bit checksum corresponding to its contents. + * Note: As of MUSCLE 5.22, this function is merely a synonym for CalculateHashCode(). + * @param buffer Pointer to the data to creat a checksum for. + * @param numBytes Number of bytes that (buffer) points to. + * @returns A 32-bit number based on the contents of the buffer. + */ +static inline uint32 CalculateChecksum(const void * buffer, uint32 numBytes) {return CalculateHashCode(buffer, numBytes);} + +/** Convenience method: Given a uint64, returns a corresponding 32-bit checksum value */ +static inline uint32 CalculateChecksumForUint64(uint64 v) {uint64 le = B_HOST_TO_LENDIAN_INT64(v); return CalculateChecksum(&le, sizeof(le));} + +/** Convenience method: Given a float, returns a corresponding 32-bit checksum value */ +static inline uint32 CalculateChecksumForFloat(float v) {uint32 le = (v==0.0f) ? 0 : B_HOST_TO_LENDIAN_IFLOAT(v); return CalculateChecksum(&le, sizeof(le));} // yes, the special case for 0.0f IS necessary, because the sign-bit might be set. :( + +/** Convenience method: Given a double, returns a corresponding 32-bit checksum value */ +static inline uint32 CalculateChecksumForDouble(double v) {uint64 le = (v==0.0) ? 0 : B_HOST_TO_LENDIAN_IDOUBLE(v); return CalculateChecksum(&le, sizeof(le));} // yes, the special case for 0.0 IS necessary, because the sign-bit might be set. :( + +/** This hashing functor type handles the trivial cases, where the KeyType is + * Plain Old Data that we can just feed directly into the CalculateHashCode() function. + * For more complicated key types, you should define a method in your KeyType class + * that looks like this: + * uint32 HashCode() const {return the_calculated_hash_code_for_this_object();} + * And that will be enough for the template magic to kick in and use MethodHashFunctor + * by default instead. (See util/String.h for an example of this) + */ +template class PODHashFunctor +{ +public: + uint32 operator()(const KeyType & x) const {return CalculateHashCode(x);} + bool AreKeysEqual(const KeyType & k1, const KeyType & k2) const {return (k1==k2);} +}; + +/** This hashing functor type calls HashCode() on the supplied object to retrieve the hash code. */ +template class MethodHashFunctor +{ +public: + uint32 operator()(const KeyType & x) const {return x.HashCode();} + bool AreKeysEqual(const KeyType & k1, const KeyType & k2) const {return (k1==k2);} +}; + +/** This hashing functor type calls HashCode() on the supplied object to retrieve the hash code. Used for pointers to key values. */ +template class MethodHashFunctor +{ +public: + uint32 operator()(const KeyType * x) const {return x->HashCode();} + + /** Note that when pointers are used as hash keys, we determine equality by comparing the objects + * the pointers point to, NOT by just comparing the pointers themselves! + */ + bool AreKeysEqual(const KeyType * k1, const KeyType * k2) const {return ((*k1)==(*k2));} +}; + +/** This macro can be used whenever you want to explicitly specify the default AutoChooseHashFunctorHelper functor for your type. It's easier than remembering the tortured C++ syntax */ +#define DEFAULT_HASH_FUNCTOR(type) AutoChooseHashFunctorHelper::Type + +namespace ugly_hashcode_method_sfinae_implementation +{ + // This code was from the example code at http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Member_Functions_at_Compile-Time + // It is used by the AutoChooseHashFunctorHelper (below) to automatically choose the appropriate HashFunctor + // type based on whether or not class given in the template argument has a "uint32 HashCode() const" method defined. + + typedef char yes; + typedef char (&no)[2]; + template struct test_hashcode_wrapper {}; + + // via SFINAE only one of these overloads will be considered + template yes hashcode_tester(test_hashcode_wrapper*); + template no hashcode_tester(...); + + template struct test_hashcode_impl {static const bool value = sizeof(hashcode_tester(0)) == sizeof(yes);}; + + template struct has_hashcode_method : test_hashcode_impl {}; + template struct if_; + template struct if_ {typedef TrueResult result;}; + template struct if_ {typedef FalseResult result;}; +} + +template class AutoChooseHashFunctorHelper +{ +public: + typedef typename ugly_hashcode_method_sfinae_implementation::if_::value, MethodHashFunctor, PODHashFunctor >::result Type; +}; +template class AutoChooseHashFunctorHelper +{ +public: + typedef typename ugly_hashcode_method_sfinae_implementation::if_::value, MethodHashFunctor, PODHashFunctor >::result Type; +}; + +/** This HashFunctor lets us use (const char *)'s as keys in a Hashtable. They will be + * hashed based on contents of the string they point to. + */ +template <> class PODHashFunctor +{ +public: + /** Returns a hash code for the given C string. + * @param str The C string to compute a hash code for. + */ + uint32 operator () (const char * str) const {return CalculateHashCode(str, (uint32)strlen(str));} + bool AreKeysEqual(const char * k1, const char * k2) const {return (strcmp(k1,k2)==0);} +}; + +template <> class AutoChooseHashFunctorHelper {typedef PODHashFunctor Type;}; +template <> class AutoChooseHashFunctorHelper {typedef PODHashFunctor Type;}; +template <> class AutoChooseHashFunctorHelper {typedef PODHashFunctor Type;}; + +#endif + +// VC++6 and earlier can't handle partial template specialization, so +// they need some extra help at various places. Lame.... +#if defined(_MSC_VER) +# if (_MSC_VER < 1300) +# define MUSCLE_USING_OLD_MICROSOFT_COMPILER 1 // VC++6 and earlier +# else +# define MUSCLE_USING_NEW_MICROSOFT_COMPILER 1 // VC.net2004 and later +# endif +#endif + +/** Given an ASCII representation of a non-negative number, returns that number as a uint64. */ +uint64 Atoull(const char * str); + +/** Similar to Atoll(), but handles negative numbers as well */ +int64 Atoll(const char * str); + +#ifdef __cplusplus +}; // end namespace muscle +#endif + +#endif /* _MUSCLE_SUPPORT_H */ diff --git a/support/Point.h b/support/Point.h new file mode 100644 index 00000000..4431d25b --- /dev/null +++ b/support/Point.h @@ -0,0 +1,153 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/******************************************************************************* +/ +/ File: Point.h +/ +/ Description: version of Be's Point class +/ +*******************************************************************************/ + +#ifndef MusclePoint_h +#define MusclePoint_h + +#include // for sqrt() +#include "support/Flattenable.h" +#include "support/Tuple.h" + +namespace muscle { + +/*----------------------------------------------------------------------*/ +/*----- Point class --------------------------------------------*/ + +/** A portable version of Be's BPoint class. */ +class Point : public Tuple<2,float>, public PseudoFlattenable +{ +public: + /** Default constructor, sets the point to be (0.0f, 0.0f) */ + Point() {/* empty */} + + /** Constructor where you specify the initial value of the point + * @param ax Initial x position + * @param ay Initial y position + */ + Point(float ax, float ay) {Set(ax, ay);} + + /** Copy Constructor. */ + Point(const Point & rhs) : Tuple<2,float>(rhs) {/* empty */} + + /** Destructor */ + ~Point() {/* empty */} + + /** convenience method to set the x value of this Point */ + inline float & x() {return (*this)[0];} + + /** convenience method to get the x value of this Point */ + inline float x() const {return (*this)[0];} + + /** convenience method to set the y value of this Point */ + inline float & y() {return (*this)[1];} + + /** convenience method to get the y value of this Point */ + inline float y() const {return (*this)[1];} + + /** Sets a new value for the point. + * @param ax The new x value + * @param ay The new y value + */ + void Set(float ax, float ay) {x() = ax; y() = ay;} + + /** If the point is outside the rectangle specified by the two arguments, + * it will be moved horizontally and/or vertically until it falls inside the rectangle. + * @param topLeft Minimum values acceptable for x and y + * @param bottomRight Maximum values acceptable for x and y + */ + void ConstrainTo(Point topLeft, Point bottomRight) + { + if (x() < topLeft.x()) x() = topLeft.x(); + if (y() < topLeft.y()) y() = topLeft.y(); + if (x() > bottomRight.x()) x() = bottomRight.x(); + if (y() > bottomRight.y()) y() = bottomRight.y(); + } + + /** Print debug information about the point to stdout or to a file you specify. + * @param optFile If non-NULL, the text will be printed to this file. If left as NULL, stdout will be used as a default. + */ + void PrintToStream(FILE * optFile = NULL) const + { + if (optFile == NULL) optFile = stdout; + fprintf(optFile, "Point: %f %f\n", x(), y()); + } + + /** Part of the Flattenable pseudo-interface: Returns true */ + bool IsFixedSize() const {return true;} + + /** Part of the Flattenable pseudo-interface: Returns B_POINT_TYPE */ + uint32 TypeCode() const {return B_POINT_TYPE;} + + /** Returns true iff (tc) equals B_POINT_TYPE. */ + bool AllowsTypeCode(uint32 tc) const {return (TypeCode()==tc);} + + /** Part of the Flattenable pseudo-interface: 2*sizeof(float) */ + uint32 FlattenedSize() const {return 2*sizeof(float);} + + /** Returns a 32-bit checksum for this object. */ + uint32 CalculateChecksum() const {return CalculateChecksumForFloat(x()) + (3*CalculateChecksumForFloat(y()));} + + /** Copies this point into an endian-neutral flattened buffer. + * @param buffer Points to an array of at least FlattenedSize() bytes. + */ + void Flatten(uint8 * buffer) const + { + float * buf = (float *) buffer; + uint32 ox = B_HOST_TO_LENDIAN_IFLOAT(x()); muscleCopyOut(&buf[0], ox); + uint32 oy = B_HOST_TO_LENDIAN_IFLOAT(y()); muscleCopyOut(&buf[1], oy); + } + + /** Restores this point from an endian-neutral flattened buffer. + * @param buffer Points to an array of (size) bytes + * @param size The number of bytes (buffer) points to (should be at least FlattenedSize()) + * @return B_NO_ERROR on success, B_ERROR on failure (size was too small) + */ + status_t Unflatten(const uint8 * buffer, uint32 size) + { + if (size >= FlattenedSize()) + { + float * buf = (float *) buffer; + uint32 i0; muscleCopyIn(i0, &buf[0]); x() = B_LENDIAN_TO_HOST_IFLOAT(i0); + uint32 i1; muscleCopyIn(i1, &buf[1]); y() = B_LENDIAN_TO_HOST_IFLOAT(i1); + return B_NO_ERROR; + } + else return B_ERROR; + } + + /** This is implemented so that if Rect is used as the key in a Hashtable, the Tuple HashCode() method will be + * selected by the AutoChooseHashFunctor template logic, instead of the PODHashFunctor. (Unfortunately + * AutoChooseHashFunctor doesn't check the superclasses when it looks for a HashCode method) + */ + uint32 HashCode() const {return Tuple<2,float>::HashCode();} + + /** Returns the distance between this point and (pt). + * @param pt The point we want to calculate the distance to. + * @returns a non-negative distance value. + */ + float GetDistanceTo(const Point & pt) const {return (float)sqrt(GetDistanceToSquared(pt));} + + /** Returns the square of the distance between this point and (pt). + * @param pt The point we want to calculate the distance to. + * @returns a non-negative distance-squared value. + * @note this method is more efficient that calling GetDistanceTo(), since it doesn't have to call sqrt(). + */ + float GetDistanceToSquared(const Point & pt) const + { + float dx = pt.x()-x(); + float dy = pt.y()-y(); + return ((dx*dx)+(dy*dy)); + } +}; + +DECLARE_ALL_TUPLE_OPERATORS(Point,float); + +}; // end namespace muscle + +#endif diff --git a/support/Rect.h b/support/Rect.h new file mode 100644 index 00000000..b7256343 --- /dev/null +++ b/support/Rect.h @@ -0,0 +1,250 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/******************************************************************************* +/ +/ File: Rect.h +/ +/ Description: version of Be's BRect class +/ +*******************************************************************************/ + +#ifndef MuscleRect_h +#define MuscleRect_h + +#include "support/Flattenable.h" +#include "support/Point.h" + +namespace muscle { + +/** A portable version of Be's BRect class. */ +class Rect : public Tuple<4,float>, public PseudoFlattenable +{ +public: + /** Default Constructor. + * Creates a rectangle with upper left point (0,0), and lower right point (-1,-1). + * Note that this rectangle has a negative area! (that is to say, it's imaginary) + */ + Rect() {Set(0.0f,0.0f,-1.0f,-1.0f);} + + /** Constructor where you specify the left, top, right, and bottom coordinates */ + Rect(float l, float t, float r, float b) {Set(l,t,r,b);} + + /** Copy constructor */ + Rect(const Rect & rhs) : Tuple<4,float>(rhs) {/* empty */} + + /** Constructor where you specify the leftTop point and the rightBottom point. */ + Rect(Point leftTop, Point rightBottom) {Set(leftTop.x(), leftTop.y(), rightBottom.x(), rightBottom.y());} + + /** Destructor */ + ~Rect() {/* empty */} + + /** convenience method to get the left edge of this Rect */ + inline float left() const {return (*this)[0];} + + /** convenience method to set the left edge of this Rect */ + inline float & left() {return (*this)[0];} + + /** convenience method to get the top edge of this Rect */ + inline float top() const {return (*this)[1];} + + /** convenience method to set the top edge of this Rect */ + inline float & top() {return (*this)[1];} + + /** convenience method to get the right edge of this Rect */ + inline float right() const {return (*this)[2];} + + /** convenience method to set the right edge of this Rect */ + inline float & right() {return (*this)[2];} + + /** convenience method to get the bottom edge of this Rect */ + inline float bottom() const {return (*this)[3];} + + /** convenience method to set the bottom edge of this Rect */ + inline float & bottom() {return (*this)[3];} + + /** Set a new position for the rectangle. */ + inline void Set(float l, float t, float r, float b) + { + left() = l; + top() = t; + right() = r; + bottom() = b; + } + + /** Print debug information about this rectangle to stdout or to a file you specify. + * @param optFile If non-NULL, the text will be printed to this file. If left as NULL, stdout will be used as a default. + */ + void PrintToStream(FILE * optFile = NULL) const + { + if (optFile == NULL) optFile = stdout; + fprintf(optFile, "Rect: leftTop=(%f,%f) rightBottom=(%f,%f)\n", left(), top(), right(), bottom()); + } + + /** Returns the left top corner of the rectangle. */ + inline Point LeftTop() const {return Point(left(), top());} + + /** Returns the right bottom corner of the rectangle. */ + inline Point RightBottom() const {return Point(right(), bottom());} + + /** Returns the left bottom corner of the rectangle. */ + inline Point LeftBottom() const {return Point(left(), bottom());} + + /** Returns the right top corner of the rectangle. */ + inline Point RightTop() const {return Point(right(), top());} + + /** Set the left top corner of the rectangle. */ + inline void SetLeftTop(const Point p) {left() = p.x(); top() = p.y();} + + /** Set the right bottom corner of the rectangle. */ + inline void SetRightBottom(const Point p) {right() = p.x(); bottom() = p.y();} + + /** Set the left bottom corner of the rectangle. */ + inline void SetLeftBottom(const Point p) {left() = p.x(); bottom() = p.y();} + + /** Set the right top corner of the rectangle. */ + inline void SetRightTop(const Point p) {right() = p.x(); top() = p.y();} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + inline void InsetBy(Point p) {InsetBy(p.x(), p.y());} + + /** Makes the rectangle smaller by the amount specified in both the x and y dimensions */ + inline void InsetBy(float dx, float dy) {left() += dx; top() += dy; right() -= dx; bottom() -= dy;} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + inline void OffsetBy(Point p) {OffsetBy(p.x(), p.y());} + + /** Translates the rectangle by the amount specified in both the x and y dimensions */ + inline void OffsetBy(float dx, float dy) {left() += dx; top() += dy; right() += dx; bottom() += dy;} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + inline void OffsetTo(Point p) {OffsetTo(p.x(), p.y());} + + /** Translates the rectangle so that its top left corner is at the point specified. */ + inline void OffsetTo(float x, float y) {right() = x + Width(); bottom() = y + Height(); left() = x; top() = y;} + + /** If this Rect has negative width or height, modifies it to have positive width and height. */ + void Rationalize() + { + if (left() > right()) {float t = left(); left() = right(); right() = t;} + if (top() > bottom()) {float t = top(); top() = bottom(); bottom() = t;} + } + + /** Returns a rectangle whose area is the intersecting subset of this rectangle's and (r)'s */ + inline Rect operator&(Rect r) const + { + Rect ret(*this); + if (this != &r) + { + if (ret.left() < r.left()) ret.left() = r.left(); + if (ret.right() > r.right()) ret.right() = r.right(); + if (ret.top() < r.top()) ret.top() = r.top(); + if (ret.bottom() > r.bottom()) ret.bottom() = r.bottom(); + } + return ret; + } + + /** Returns a rectangle whose area is a superset of the union of this rectangle's and (r)'s */ + inline Rect operator|(Rect r) const + { + Rect ret(*this); + if (this != &r) + { + if (r.left() < ret.left()) ret.left() = r.left(); + if (r.right() > ret.right()) ret.right() = r.right(); + if (r.top() < ret.top()) ret.top() = r.top(); + if (r.bottom() > ret.bottom()) ret.bottom() = r.bottom(); + } + return ret; + } + + /** Causes this rectangle to be come the union of itself and (rhs). */ + inline Rect & operator |= (const Rect & rhs) {(*this) = (*this) | rhs; return *this;} + + /** Causes this rectangle to be come the intersection of itself and (rhs). */ + inline Rect & operator &= (const Rect & rhs) {(*this) = (*this) & rhs; return *this;} + + /** Returns true iff this rectangle and (r) overlap in space. */ + inline bool Intersects(Rect r) const {return (r&(*this)).IsValid();} + + /** Returns true iff this rectangle's area is non imaginary (i.e. Width() and Height()) are both non-negative) */ + inline bool IsValid() const {return ((Width() >= 0.0f)&&(Height() >= 0.0f));} + + /** Returns the area of this rectangle. */ + inline float Area() const {return IsValid() ? (Width()*Height()) : 0.0f;} + + /** Returns the width of this rectangle. */ + inline float Width() const {return right() - left();} + + /** Returns the width of this rectangle as an integer. */ + inline int32 IntegerWidth() const {return (int32)ceil(Width());} + + /** Returns the height of this rectangle. */ + inline float Height() const {return bottom()-top();} + + /** Returns the height of this rectangle as an integer. */ + inline int32 IntegerHeight() const {return (int32)ceil(Height());} + + /** Returns true iff this rectangle contains the specified point. */ + inline bool Contains(Point p) const {return ((p.x() >= left())&&(p.x() <= right())&&(p.y() >= top())&&(p.y() <= bottom()));} + + /** Returns true iff this rectangle fully the specified rectangle. */ + inline bool Contains(Rect p) const {return ((Contains(p.LeftTop()))&&(Contains(p.RightTop()))&&(Contains(p.LeftBottom()))&&(Contains(p.RightBottom())));} + + /** Part of the pseudo-Flattenable API: Returns true. */ + bool IsFixedSize() const {return true;} + + /** Part of the pseudo-Flattenable API: Returns B_RECT_TYPE. */ + uint32 TypeCode() const {return B_RECT_TYPE;} + + /** Returns true iff (tc) equals B_RECT_TYPE. */ + bool AllowsTypeCode(uint32 tc) const {return (TypeCode()==tc);} + + /** Part of the pseudo-Flattenable API: Returns 4*sizeof(float). */ + uint32 FlattenedSize() const {return 4*sizeof(float);} + + /** Returns a 32-bit checksum for this object. */ + uint32 CalculateChecksum() const {return CalculateChecksumForFloat(left()) + (3*CalculateChecksumForFloat(top())) + (5*CalculateChecksumForFloat(right())) + (7*CalculateChecksumForFloat(bottom()));} + + /** Flattens this rectangle into an endian-neutral byte buffer. + * @param buffer Points to the byte buffer to write into. There must be at least FlattenedSize() bytes there. + */ + void Flatten(uint8 * buffer) const + { + float * buf = (float *) buffer; + uint32 oL = B_HOST_TO_LENDIAN_IFLOAT(left()); muscleCopyOut(&buf[0], oL); + uint32 oT = B_HOST_TO_LENDIAN_IFLOAT(top()); muscleCopyOut(&buf[1], oT); + uint32 oR = B_HOST_TO_LENDIAN_IFLOAT(right()); muscleCopyOut(&buf[2], oR); + uint32 oB = B_HOST_TO_LENDIAN_IFLOAT(bottom()); muscleCopyOut(&buf[3], oB); + } + + /** Unflattens this rectangle from an endian-neutral byte buffer. + * @param buffer Points to the byte buffer to read data from. + * @param size The number of bytes available in (buffer). There should be at least FlattenedSize() bytes there. + * @return B_NO_ERROR on success, or B_ERROR if (buffer) was too small. + */ + status_t Unflatten(const uint8 * buffer, uint32 size) + { + if (size >= FlattenedSize()) + { + float * buf = (float *) buffer; + uint32 i0; muscleCopyIn(i0, &buf[0]); left() = B_LENDIAN_TO_HOST_IFLOAT(i0); + uint32 i1; muscleCopyIn(i1, &buf[1]); top() = B_LENDIAN_TO_HOST_IFLOAT(i1); + uint32 i2; muscleCopyIn(i2, &buf[2]); right() = B_LENDIAN_TO_HOST_IFLOAT(i2); + uint32 i3; muscleCopyIn(i3, &buf[3]); bottom() = B_LENDIAN_TO_HOST_IFLOAT(i3); + return B_NO_ERROR; + } + else return B_ERROR; + } + + /** This is implemented so that if Rect is used as the key in a Hashtable, the Tuple HashCode() method will be + * selected by the AutoChooseHashFunctor template logic, instead of the PODHashFunctor. (Unfortunately + * AutoChooseHashFunctor doesn't check the superclasses when it looks for a HashCode method) + */ + uint32 HashCode() const {return Tuple<4,float>::HashCode();} +}; + +DECLARE_ALL_TUPLE_OPERATORS(Rect,float); + +}; // end namespace muscle + +#endif diff --git a/support/TamperEvidentValue.h b/support/TamperEvidentValue.h new file mode 100644 index 00000000..3eef6df3 --- /dev/null +++ b/support/TamperEvidentValue.h @@ -0,0 +1,59 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleTamperEvidentValue_h +#define MuscleTamperEvidentValue_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** A simple templated class that holds a single value of the template-specified type. + * The only difference between using a TamperEvidentValue and just using the T object directly + * is that the TamperEvidentValue class will automatically set a flag whenever the stored + * value is set (via SetValue() or assignment operator), so that you can tell later on if + * anyone has explicitly set this value after it was constructed. + */ +template class TamperEvidentValue +{ +public: + /** Default constructor */ + TamperEvidentValue() : _value(), _wasExplicitlySet(false) {/* empty */} + + /** Explicit constructor. */ + TamperEvidentValue(const T & val) : _value(val), _wasExplicitlySet(false) {/* empty */} + + /** Copy constructor. Copies both the value and the flag-state from the passed-in TamperEvidentValue object. */ + TamperEvidentValue(const TamperEvidentValue & copyMe) : _value(copyMe._value), _wasExplicitlySet(copyMe._wasExplicitlySet) {/* empty */} + + /** Destructor */ + ~TamperEvidentValue() {/* empty */} + + /** Assignment operator. */ + TamperEvidentValue & operator =(const TamperEvidentValue & rhs) {SetValue(rhs.GetValue()); return *this;} + + /** Assignment operator. */ + TamperEvidentValue & operator =(const T & rhs) {SetValue(rhs); return *this;} + + /** Sets a new value, and also sets our HasValueBeenSet() flag to true. */ + void SetValue(const T & newVal) {_value = newVal; _wasExplicitlySet = true;} + + /** Returns our current value */ + const T & GetValue() const {return _value;} + + /** Conversion operator, for convenience */ + operator T() const {return _value;} + + /** Returns true iff SetValue() was called after this object was constructed. */ + bool HasValueBeenSet() const {return _wasExplicitlySet;} + + /** Call this if you want to set the HasValueBeenSet() flag back to false again. */ + void ClearValueWasSetFlag() {_wasExplicitlySet = false;} + +private: + T _value; + bool _wasExplicitlySet; +}; + +}; // end namespace muscle + +#endif diff --git a/support/Tuple.h b/support/Tuple.h new file mode 100644 index 00000000..4c68e8d5 --- /dev/null +++ b/support/Tuple.h @@ -0,0 +1,292 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleTuple_h +#define MuscleTuple_h + +#include // here for subclasses to use +#include "support/MuscleSupport.h" + +namespace muscle { + +/** Templated base class representing a fixed-size set of numeric values that can be operated on in parallel. */ +template class Tuple +{ +public: + /** Default ctor; All values are set to their default value. */ + Tuple() {Reset();} + + /** Value constructor. All items are set to (value) */ + Tuple(const ItemType & value) {*this = value;} + + /** Copy constructor */ + Tuple(const Tuple & copyMe) {*this = copyMe;} + + /** Silly constructor -- This constructor does no initialization at all. The arguments are here merely to differentiate it + * from the other constructors, and are ignored. When this constructor is used, the items in this Tuple will be in an + * undefined state and their state should be set to something definite before use. (Exception: if the items are + * class objects with constructors, those constructors will still be called) + */ + Tuple(bool, bool) {/* empty */} + + /** Destructor */ + ~Tuple() {/* empty */} + + /** Assignment operator. */ + Tuple & operator =(const Tuple & rhs) {if (this != &rhs) {for (int i=0; i>=(int numPlaces) {ShiftValuesRight(numPlaces); return *this;} + + /** Returns true iff all indices in this object are equal to their counterparts in (rhs). */ + bool operator ==(const Tuple & rhs) const {if (this != &rhs) {for (int i=0; i rhs._items[i]) return false;}} return false;} + + /** Comparison Operator. Returns true if this tuple comes after (rhs) lexically. */ + bool operator > (const Tuple &rhs) const {if (this != &rhs) {for (int i=0; i rhs._items[i]) return true; if (_items[i] < rhs._items[i]) return false;}} return false;} + + /** Comparison Operator. Returns true if the two tuple are equal, or this tuple comes before (rhs) lexically. */ + bool operator <=(const Tuple &rhs) const {return !(*this > rhs);} + + /** Comparison Operator. Returns true if the two tuple are equal, or this tuple comes after (rhs) lexically. */ + bool operator >=(const Tuple &rhs) const {return !(*this < rhs);} + + /** Read-write array operator (not bounds-checked) */ + ItemType & operator [](uint32 i) {return _items[i];} + + /** Read-only array operator (not bounds-checked) */ + const ItemType & operator [](uint32 i) const {return _items[i];} + + /** Returns the dot-product of (this) and (rhs) */ + ItemType DotProduct(const Tuple & rhs) const {ItemType dp = ItemType(); for (int i=0; i= 0);} + + /** Returns the index of the first value equal to (value), or -1 if not found. */ + int IndexOf(const ItemType & value) const {for (int i=0; i=0; i--) if (_items[i] == value) return i; return -1;} + + /** Works like strcmp(), only for a tuple. */ + int Compare(const Tuple & rhs) const {for (int i=0; i rhs[i]) return 1;} return 0;} + + /** Returns the minimum value from amongst all the items in the tuple */ + ItemType GetMaximumValue() const {ItemType maxv = _items[0]; for (int i=1; i maxv) maxv = _items[i]; return maxv;} + + /** Returns the maximum value from amongst all the items in the tuple */ + ItemType GetMinimumValue() const {ItemType minv = _items[0]; for (int i=1; i NumItems) endIndex = NumItems; + for (uint32 i=startIndex; i NumItems) endIndex = NumItems; + for (uint32 i=startIndex; i NumItems) endIndex = NumItems; + for (uint32 i=startIndex; i NumItems) endIndex = NumItems; + for (uint32 i=startIndex; i 0) + { + const ItemType def = ItemType(); + int i=0, j=numPlaces; + for (; j 0) + { + const ItemType def = ItemType(); + int i=NumItems-1, j=(NumItems-numPlaces)-1; + for(; j>=0; --i, --j) _items[i] = _items[j]; + for(; i>=0; --i) _items[i] = def; + } + else if (numPlaces < 0) ShiftValuesLeft(-numPlaces); + } + +private: + ItemType _items[NumItems]; +}; + +template inline const Tuple operator - (const Tuple & lhs) {Tuple ret(lhs); ret -= lhs+lhs; return ret;} +template inline const Tuple operator + (const Tuple & lhs, const T & rhs) {Tuple ret(lhs); ret += rhs; return ret;} +template inline const Tuple operator - (const Tuple & lhs, const T & rhs) {Tuple ret(lhs); ret -= rhs; return ret;} +template inline const Tuple operator + (const T & lhs, const Tuple & rhs) {Tuple ret(lhs); ret += rhs; return ret;} +template inline const Tuple operator - (const T & lhs, const Tuple & rhs) {Tuple ret(lhs); ret -= rhs; return ret;} +template inline const Tuple operator + (const Tuple & lhs, const Tuple & rhs) {Tuple ret(lhs); ret += rhs; return ret;} +template inline const Tuple operator - (const Tuple & lhs, const Tuple & rhs) {Tuple ret(lhs); ret -= rhs; return ret;} +template inline const Tuple operator * (const Tuple & lhs, const T & rhs) {Tuple ret(lhs); ret *= rhs; return ret;} +template inline const Tuple operator / (const Tuple & lhs, const T & rhs) {Tuple ret(lhs); ret /= rhs; return ret;} +template inline const Tuple operator * (const T & lhs, const Tuple & rhs) {Tuple ret(lhs); ret *= rhs; return ret;} +template inline const Tuple operator / (const T & lhs, const Tuple & rhs) {Tuple ret(lhs); ret /= rhs; return ret;} +template inline const Tuple operator * (const Tuple & lhs, const Tuple & rhs) {Tuple ret(lhs); ret *= rhs; return ret;} +template inline const Tuple operator / (const Tuple & lhs, const Tuple & rhs) {Tuple ret(lhs); ret /= rhs; return ret;} + +#define DECLARE_ADDITION_TUPLE_OPERATORS(C,I) \ + inline const C operator + (const C & lhs, const I & rhs) {C ret(lhs); ret += rhs; return ret;} \ + inline const C operator + (const I & lhs, const C & rhs) {C ret; ret.FillSubrange(lhs); ret += rhs; return ret;} \ + inline const C operator + (const C & lhs, const C & rhs) {C ret(lhs); ret += rhs; return ret;} + +#define DECLARE_SUBTRACTION_TUPLE_OPERATORS(C,I) \ + inline const C operator - (const C & lhs) {C ret(lhs); ret -= lhs+lhs; return ret;} \ + inline const C operator - (const C & lhs, const I & rhs) {C ret(lhs); ret -= rhs; return ret;} \ + inline const C operator - (const I & lhs, const C & rhs) {C ret; ret.FillSubrange(lhs); ret -= rhs; return ret;} \ + inline const C operator - (const C & lhs, const C & rhs) {C ret(lhs); ret -= rhs; return ret;} + +#define DECLARE_MULTIPLICATION_TUPLE_OPERATORS(C,I) \ + inline const C operator * (const C & lhs, const I & rhs) {C ret(lhs); ret *= rhs; return ret;} \ + inline const C operator * (const I & lhs, const C & rhs) {C ret; ret.FillSubrange(lhs); ret *= rhs; return ret;} \ + inline const C operator * (const C & lhs, const C & rhs) {C ret(lhs); ret *= rhs; return ret;} + +#define DECLARE_DIVISION_TUPLE_OPERATORS(C,I) \ + inline const C operator / (const C & lhs, const I & rhs) {C ret(lhs); ret /= rhs; return ret;} \ + inline const C operator / (const I & lhs, const C & rhs) {C ret; ret.FillSubrange(lhs); ret /= rhs; return ret;} \ + inline const C operator / (const C & lhs, const C & rhs) {C ret(lhs); ret /= rhs; return ret;} + +#define DECLARE_SHIFT_TUPLE_OPERATORS(C) \ + inline const C operator >> (const C & lhs, int rhs) {C ret(lhs); ret >>= rhs; return ret;} \ + inline const C operator << (const C & lhs, int rhs) {C ret(lhs); ret <<= rhs; return ret;} + +// Classes sublassing a Tuple class can use this macro to get the all the standard operators without having to rewrite them. +// If anyone knows how to accomplish this without resorting to preprocessor macro hacks, I'd love to hear about it... +#define DECLARE_ALL_TUPLE_OPERATORS(C,I) \ + DECLARE_ADDITION_TUPLE_OPERATORS(C,I) \ + DECLARE_SUBTRACTION_TUPLE_OPERATORS(C,I) \ + DECLARE_MULTIPLICATION_TUPLE_OPERATORS(C,I) \ + DECLARE_DIVISION_TUPLE_OPERATORS(C,I) \ + DECLARE_SHIFT_TUPLE_OPERATORS(C) + +}; // end namespace muscle + +#endif diff --git a/syslog/LogCallback.h b/syslog/LogCallback.h new file mode 100644 index 00000000..8d3ef946 --- /dev/null +++ b/syslog/LogCallback.h @@ -0,0 +1,305 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.TXT file for details. */ + +#ifndef MuscleLogCallback_h +#define MuscleLogCallback_h + +#include "dataio/FileDataIO.h" +#include "syslog/SysLog.h" +#include "util/CountedObject.h" +#include "util/RefCount.h" +#include "util/String.h" + +namespace muscle { + +/** This class encapsulates the information that is sent to the Log() and LogLine() callback methods of the LogCallback and LogLineCallback classes. By putting all the information into a class object, we only have to push one parameter onto the stack with each call instead of many. */ +class LogCallbackArgs +{ +public: + /** Default Constructor */ + LogCallbackArgs() : _when(0), _logLevel(MUSCLE_LOG_INFO), _sourceFile(""), _sourceFunction(""), _sourceLine(0), _text("") {/* empty */} + + /** Constructor + * @param when Timestamp for this log message, in (seconds past 1970) format. + * @param logLevel The MUSCLE_LOG_* severity level of this log message + * @param sourceFile The name of the source code file that contains the LogLine() call that generated this callback. + * Note that this parameter will only be valid if -DMUSCLE_INCLUDE_SOURCE_CODE_LOCATION_IN_LOGTIME + * was defined when muscle was compiled. Otherwise this value may be passed as "". + * @param sourceFunction The name of the source code function that contains the LogLine() call that generated this callback. + * Note that this parameter will only be valid if -DMUSCLE_INCLUDE_SOURCE_CODE_LOCATION_IN_LOGTIME + * was defined when muscle was compiled. Otherwise this value may be passed as "". + * @param sourceLine The line number of the LogLine() call that generated this callback. + * Note that this parameter will only be valid if -DMUSCLE_INCLUDE_SOURCE_CODE_LOCATION_IN_LOGTIME + * was defined when muscle was compiled. Otherwise this value may be passed as -1. + * @param text The format text if this object is being passed in a Log() callback. If this object is being passed + * in a LogLine() callback, this will be the verbatim text of the line. + * @param argList In a Log() callback, this is a pointer to a va_list object that can be used to expand (text). + * In a LogLine() callback, this value will be NULL. + */ + LogCallbackArgs(const time_t & when, int logLevel, const char * sourceFile, const char * sourceFunction, int sourceLine, const char * text, va_list * argList) : _when(when), _logLevel(logLevel), _sourceFile(sourceFile), _sourceFunction(sourceFunction), _sourceLine(sourceLine), _text(text), _argList(argList) {/* empty */} + + /** Returns the timestamp indicating when this message was generated, in (seconds since 1970) format. */ + const time_t & GetWhen() const {return _when;} + + /** Returns the MUSCLE_LOG_* severity level of this log message. */ + int GetLogLevel() const {return _logLevel;} + + /** Returns the name of the source code file that contains the LogLine() call that generated this callback, or "" if it's not available. */ + const char * GetSourceFile() const {return _sourceFile;} + + /** Returns the name of the source code function that contains the LogLine() call that generated this callback, + * or "" if it's not available. + */ + const char * GetSourceFunction() const {return _sourceFunction;} + + /** Returns the line number of the LogLine() call that generated this callback, or -1 if it's not available. */ + int GetSourceLineNumber() const {return _sourceLine;} + + /** Returns the format text if this object is being passed in a Log() callback. If this object is being passed + * in a LogLine() callback, this will be the verbatim text of the line. + */ + const char * GetText() const {return _text;} + + /** Returns the Log() callback, this is a pointer to a va_list object that can be used to expand (text). + * In a LogLine() callback, this value will be NULL.. + */ + va_list * GetArgList() const {return _argList;} + + /** Set the timestamp associated with the Log message + * @param when A time value (seconds since 1970) + */ + void SetWhen(const time_t & when) {_when = when;} + + /** Set the MUSCLE_LOG_* severity level of this Log message + * param ll A MUSCLE_LOG_* value + */ + void SetLogLevel(int ll) {_logLevel = ll;} + + /** Set the source file name of this Log Message. + * @param sf A source file name string. Note that this string will not be copied and therefore must remain valid! + */ + void SetSourceFile(const char * sf) {_sourceFile = sf;} + + /** Set the source function name of this Log Message. + * @param sf A source function name string. Note that this string will not be copied and therefore must remain valid! + */ + void SetSourceFunction(const char * sf) {_sourceFunction = sf;} + + /** Set the source line number of this Log Message. + * @param sourceLine A source line number, or -1 to indicate invalid. + */ + void SetSourceLineNumber(int sourceLine) {_sourceLine = sourceLine;} + + /** Set the text string of this Log Message. + * @param txt A text string. Note that this string will not be copied and therefore must remain valid! + */ + void SetText(const char * txt) {_text = txt;} + + /** Sets the pointer to a va_list that can be used to expand our text. + * @param va Pointer to a va_list, or NULL if there is none. Noe that this object is not copied and therefore must remain valid! + */ + void SetArgList(va_list * va) {_argList = va;} + +private: + time_t _when; + int _logLevel; + const char * _sourceFile; + const char * _sourceFunction; + int _sourceLine; + const char * _text; + va_list * _argList; +}; + +/** Callback object that can be added with PutLogCallback() + * Whenever a log message is generated, all added LogCallback + * objects will have their Log() methods called. All log callbacks + * are synchronized via a global lock, hence they will be thread safe. + */ +class LogCallback : public RefCountable, private CountedObject +{ +public: + /** Default constructor */ + LogCallback() {/* empty */} + + /** Destructor, to keep C++ honest */ + virtual ~LogCallback() {/* empty */} + + /** Callback method. Called whenever a message is logged with Log() or LogTime(). + * @param a LogCallbackArgs object containing all the arguments to this method. + */ + virtual void Log(const LogCallbackArgs & a) = 0; + + /** Callback method. When this method is called, the callback should flush any + * held buffers out. (i.e. call fflush() or whatever) + */ + virtual void Flush() = 0; +}; +DECLARE_REFTYPES(LogCallback); + +/** Specialization of LogCallback that parses the Log() calls + * into nicely formatted lines of text, then calls LogLine() + * to hand them to your code. Easier than having lots of classes + * that all have to do this themselves. Assumes that all log + * lines will be less than 2048 characters long. + */ +class LogLineCallback : public LogCallback, private CountedObject +{ +public: + /** Constructor */ + LogLineCallback(); + + /** Destructor */ + virtual ~LogLineCallback(); + + /** Implemented to call LogLine() when appropriate */ + virtual void Log(const LogCallbackArgs & a); + + /** Implemented to call LogLine() when appropriate */ + virtual void Flush(); + +protected: + /** This will be called whenever a fully-formed line of log text is ready. + * implement it to do whatever you like with the text. + * @param a The log callback arguments. The (format) string + * in this case will always be a literal string that can be printed verbatim. + */ + virtual void LogLine(const LogCallbackArgs & a) = 0; + +private: + LogCallbackArgs _lastLog; // stored for use by Flush() + char * _writeTo; // points to the next spot in (_buf) to sprintf() into + char _buf[2048]; // where we assemble our text +}; + +/** Add a custom LogCallback object to the global log callbacks set. + * @param cbRef Reference to a LogCallback object. + */ +status_t PutLogCallback(const LogCallbackRef & cbRef); + +/** Removes the given callback from our list. + * @param cbRef Reference of the LogCallback to remove from the callback list. + * @returns B_NO_ERROR on success, or B_ERROR if the given callback wasn't found, or the lock couldn't be locked. + */ +status_t RemoveLogCallback(const LogCallbackRef & cbRef); + +/** Removes all log callbacks from the callback set + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t ClearLogCallbacks(); + +/** This class is used to send log information to stdout. An object of this class is instantiated + * and used internally by MUSCLE, so typically you don't need to instantiate one yourself, but the class + * is exposed here anyway in case it might come in useful for other reasons. + */ +class DefaultConsoleLogger : public LogCallback +{ +public: + /** Default constructor */ + DefaultConsoleLogger(); + + virtual void Log(const LogCallbackArgs & a); + virtual void Flush(); + + /** Returns the maximum MUSCLE_LOG_* log level we will log to stdout for. Default value is MUSCLE_LOG_INFO. */ + int GetConsoleLogLevel() {return _consoleLogLevel;} + + /** Sets the maximum MUSCLE_LOG_* log level we will log to stdout for. + * @param logLevel a MUSCLE_LOG_* value. + */ + void SetConsoleLogLevel(int logLevel) {_consoleLogLevel = logLevel;} + +private: + int _consoleLogLevel; +}; + +/** This class is used to send log information to a file, rotate log files, etc. An object of this class + * is instantiated and used internally by MUSCLE, so typically you don't need to instantiate one yourself, + * but the class is exposed here anyway in case it comes in useful for other reasons (e.g. for creating and + * rotating a separate set of log files in an additional directory) + */ +class DefaultFileLogger : public LogCallback +{ +public: + /** Default constructor */ + DefaultFileLogger(); + + virtual ~DefaultFileLogger(); + + virtual void Log(const LogCallbackArgs & a); + + virtual void Flush(); + + /** Specify a pattern of already-existing log files to include in our log-file-history. + * This would be called at startup, in case there are old log files already extant from previous runs. + * @param filePattern a filepath with wildcards indicating which files to match on. + * @returns the number of existing files found and added to our files-list. + */ + uint32 AddPreExistingLogFiles(const String & filePattern); + + /** Returns the threshold MUSCLE_LOG_* level that we will output to the log file. Default value is MUSCLE_LOG_NONE. */ + int GetFileLogLevel() const {return _fileLogLevel;} + + /** Returns the name of the log file we will output to. Default is an empty string (i.e. file logging disabled) */ + const String & GetFileLogName() const {return _prototypeLogFileName;} + + /** Returns the maximum size of the log file we will output to. When the file reaches this size we'll create another. Default is MUSCLE_NO_LIMIT (aka no maximum size). */ + uint32 GetMaxLogFileSize() const {return _maxLogFileSize;} + + /** Returns the maximum number of log files we will keep present on disk at once (before starting to delete the old ones). Default is MUSCLE_NO_LIMIT (aka no maximum number of files) */ + uint32 GetMaxNumLogFiles() const {return _maxNumLogFiles;} + + /** Returns whether or not we should compress old log files to save disk space. Defaults to false. */ + bool GetFileCompressionEnabled() {return _compressionEnabled;} + + /** Sets the name of the file to log to. + * @param logName File name/path (including %-tokens as necessary) + */ + void SetLogFileName(const String & logName) {_prototypeLogFileName = logName;} + + /** Sets the maximum log file size, in bytes. + * @param maxSizeBytes New maximum log file size. + */ + void SetMaxLogFileSize(uint32 maxSizeBytes) {_maxLogFileSize = maxSizeBytes;} + + /** Sets the maximum allowed number of log files. + * @param maxNumLogFiles New maximum number of log files, or MUSCLE_NO_LIMIT. + */ + void SetMaxNumLogFiles(uint32 maxNumLogFiles) {_maxNumLogFiles = maxNumLogFiles;} + + /** Set whether old log files should be gzip-compressed, or not. + * @param enable True if old log files should be compressed, or false otherwise. + */ + void SetFileCompressionEnabled(bool enable) {_compressionEnabled = enable;} + + /** Set the severity-threshold under which log entries will be added to the log file. + * @param logLevel a MUSCLE_LOG_* value. + */ + void SetFileLogLevel(int logLevel) {_fileLogLevel = logLevel;} + + /** Forces the closing of any log file that we currently have open. */ + void CloseLogFile(); + +protected: + /** May be overridden by a subclass to generate a line of text that will be placed at the top of + * each generated log file. Default implementation always returns an empty string. + * @param a Info about the first log message that will be placed into the new file. + */ + virtual String GetLogFileHeaderString(const LogCallbackArgs & a) const {(void) a; return GetEmptyString();} + +private: + status_t EnsureLogFileCreated(const LogCallbackArgs & a); + + int _fileLogLevel; + String _prototypeLogFileName; + uint32 _maxLogFileSize; + uint32 _maxNumLogFiles; + bool _compressionEnabled; + + String _activeLogFileName; + FileDataIO _logFile; + bool _logFileOpenAttemptFailed; + Queue _oldLogFileNames; +}; + +}; // end namespace muscle + +#endif diff --git a/syslog/SysLog.cpp b/syslog/SysLog.cpp new file mode 100644 index 00000000..8f0c7e2e --- /dev/null +++ b/syslog/SysLog.cpp @@ -0,0 +1,2367 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include "dataio/FileDataIO.h" +#include "regex/StringMatcher.h" +#include "syslog/LogCallback.h" +#include "system/SetupSystem.h" +#include "system/SystemInfo.h" // for GetFilePathSeparator() +#include "util/Directory.h" +#include "util/FilePathInfo.h" +#include "util/Hashtable.h" +#include "util/NestCount.h" +#include "util/String.h" +#include "util/StringTokenizer.h" + +#ifdef MUSCLE_USE_MSVC_STACKWALKER +# include +# include +#endif + +#if !defined(MUSCLE_INLINE_LOGGING) && defined(MUSCLE_ENABLE_ZLIB_ENCODING) +# include "zlib/zlib/zlib.h" +#endif + +#if defined(__APPLE__) +# include "AvailabilityMacros.h" // so we can find out if this version of MacOS/X is new enough to include backtrace() and friends +#endif + +#if (defined(__linux__) && !defined(ANDROID)) || (defined(MAC_OS_X_VERSION_10_5) && defined(MAC_OS_X_VERSION_MAX_ALLOWED) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)) +# include +# define MUSCLE_USE_BACKTRACE 1 +#endif + +// Work-around for Android not providing timegm()... we'll just include the implementation inline, right here! --jaf, jfm +#if defined(ANDROID) +/* + * Copyright (c) 1997 Kungliga Tekniska Hˆgskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +# include +# include +static bool is_leap(unsigned y) {y += 1900; return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0);} +static time_t timegm (struct tm *tm) +{ + static const unsigned ndays[2][12] ={{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + time_t res = 0; + for (int i = 70; i < tm->tm_year; ++i) res += is_leap(i) ? 366 : 365; + for (int i = 0; i < tm->tm_mon; ++i) res += ndays[is_leap(tm->tm_year)][i]; + res += tm->tm_mday - 1; res *= 24; + res += tm->tm_hour; res *= 60; + res += tm->tm_min; res *= 60; + res += tm->tm_sec; + return res; +} +#endif + +namespace muscle { + +#ifdef THIS_FUNCTION_IS_NOT_ACTUALLY_USED_I_JUST_KEEP_IT_HERE_SO_I_CAN_QUICKLY_COPY_AND_PASTE_IT_INTO_THIRD_PARTY_CODE_WHEN_NECESSARY_SAYS_JAF +# include +void PrintStackTrace() +{ + FILE * optFile = stdout; + void *array[256]; + size_t size = backtrace(array, 256); + char ** strings = backtrace_symbols(array, 256); + if (strings) + { + fprintf(optFile, "--Stack trace follows (%zd frames):\n", size); + for (size_t i = 0; i < size; i++) fprintf(optFile, " %s\n", strings[i]); + fprintf(optFile, "--End Stack trace\n"); + free(strings); + } + else fprintf(optFile, "PrintStackTrace: Error, could not generate stack trace!\n"); +} +#endif + +// Begin windows stack trace code. This code is simplified, so it Only works for _MSC_VER >= 1300 +#if defined(MUSCLE_USE_MSVC_STACKWALKER) && !defined(MUSCLE_INLINE_LOGGING) + +/********************************************************************** + * + * Liberated from: + * + * http://www.codeproject.com/KB/threads/StackWalker.aspx + * + **********************************************************************/ + +class StackWalkerInternal; // forward +class StackWalker +{ +public: + typedef enum StackWalkOptions + { + // No addition info will be retrived + // (only the address is available) + RetrieveNone = 0, + + // Try to get the symbol-name + RetrieveSymbol = 1, + + // Try to get the line for this symbol + RetrieveLine = 2, + + // Try to retrieve the module-infos + RetrieveModuleInfo = 4, + + // Also retrieve the version for the DLL/EXE + RetrieveFileVersion = 8, + + // Contains all the abouve + RetrieveVerbose = 0xF, + + // Generate a "good" symbol-search-path + SymBuildPath = 0x10, + + // Also use the public Microsoft-Symbol-Server + SymUseSymSrv = 0x20, + + // Contains all the above "Sym"-options + SymAll = 0x30, + + // Contains all options (default) + OptionsAll = 0x3F, + + OptionsJAF = (RetrieveSymbol|RetrieveLine) + + } StackWalkOptions; + + StackWalker( + FILE * optOutFile, + String * optOutString, + int options = OptionsAll, // 'int' is by design, to combine the enum-flags + LPCSTR szSymPath = NULL, + DWORD dwProcessId = GetCurrentProcessId(), + HANDLE hProcess = GetCurrentProcess() + ); + ~StackWalker(); + + typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( + HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead, + LPVOID pUserData // optional data, which was passed in "ShowCallstack" + ); + + BOOL LoadModules(); + + BOOL ShowCallstack( + uint32 maxDepth, + HANDLE hThread = GetCurrentThread(), + const CONTEXT *context = NULL, + PReadProcessMemoryRoutine readMemoryFunction = NULL, + LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback + ); + +protected: + enum {STACKWALK_MAX_NAMELEN = 1024}; // max name length for found symbols + +protected: + // Entry for each Callstack-Entry + typedef struct CallstackEntry + { + DWORD64 offset; // if 0, we have no valid entry + CHAR name[STACKWALK_MAX_NAMELEN]; + CHAR undName[STACKWALK_MAX_NAMELEN]; + CHAR undFullName[STACKWALK_MAX_NAMELEN]; + DWORD64 offsetFromSmybol; + DWORD offsetFromLine; + DWORD lineNumber; + CHAR lineFileName[STACKWALK_MAX_NAMELEN]; + DWORD symType; + LPCSTR symTypeString; + CHAR moduleName[STACKWALK_MAX_NAMELEN]; + DWORD64 baseOfImage; + CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; + } CallstackEntry; + + typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; + + void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); + void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); + void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); + void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); + void OnOutput(LPCSTR szText) + { + if (this->m_outFile) fputs(" ", m_outFile); + else puts(" "); + if (this->m_outFile) fputs(szText, m_outFile); + else puts(szText); + if (this->m_outString) (*this->m_outString) += szText; + } + + + StackWalkerInternal *m_sw; + HANDLE m_hProcess; + DWORD m_dwProcessId; + BOOL m_modulesLoaded; + LPSTR m_szSymPath; + + FILE * m_outFile; // added by jaf (because subclassing is overkill for my needs here) + String * m_outString; // ditto + + int m_options; + + static BOOL __stdcall ReadProcMemCallback(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); + + friend StackWalkerInternal; +}; + +void _Win32PrintStackTraceForContext(FILE * optFile, CONTEXT * context, uint32 maxDepth) +{ + fprintf(optFile, "--Stack trace follows:\n"); + StackWalker(optFile, NULL, StackWalker::OptionsJAF).ShowCallstack(maxDepth, GetCurrentThread(), context); + fprintf(optFile, "--End Stack trace\n"); +} + + +// The following is defined for x86 (XP and higher), x64 and IA64: +#define GET_CURRENT_CONTEXT(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + RtlCaptureContext(&c); \ +} while(0); + +// Some missing defines (for VC5/6): +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + +// secure-CRT_functions are only available starting with VC8 +#if _MSC_VER < 1400 +#define strcpy_s strcpy +#define strcat_s(dst, len, src) strcat(dst, src) +#define _snprintf_s _snprintf +#define _tcscat_s _tcscat +#endif + +// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL') +#define USED_CONTEXT_FLAGS CONTEXT_FULL + +class StackWalkerInternal +{ +public: + StackWalkerInternal(StackWalker *parent, HANDLE hProcess) + { + m_parent = parent; + m_hDbhHelp = NULL; + pSC = NULL; + m_hProcess = hProcess; + m_szSymPath = NULL; + pSFTA = NULL; + pSGLFA = NULL; + pSGMB = NULL; + pSGMI = NULL; + pSGO = NULL; + pSGSFA = NULL; + pSI = NULL; + pSLM = NULL; + pSSO = NULL; + pSW = NULL; + pUDSN = NULL; + pSGSP = NULL; + } + ~StackWalkerInternal() + { + if (pSC != NULL) + pSC(m_hProcess); // SymCleanup + if (m_hDbhHelp != NULL) + FreeLibrary(m_hDbhHelp); + m_hDbhHelp = NULL; + m_parent = NULL; + if(m_szSymPath != NULL) + free(m_szSymPath); + m_szSymPath = NULL; + } + BOOL Init(LPCSTR szSymPath) + { + if (m_parent == NULL) + return FALSE; + // Dynamically load the Entry-Points for dbghelp.dll: + // First try to load the newsest one from + CHAR szTemp[4096]; + // But before wqe do this, we first check if the ".local" file exists + if (GetModuleFileNameA(NULL, szTemp, 4096) > 0) + { + strncat(szTemp, ".local", sizeof(szTemp)); + if (GetFileAttributesA(szTemp) == INVALID_FILE_ATTRIBUTES) + { + // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" + if (GetEnvironmentVariableA("ProgramFiles", szTemp, 4096) > 0) + { + strncat(szTemp, "\\Debugging Tools for Windows\\dbghelp.dll", sizeof(szTemp)); + // now check if the file exists: + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibraryA(szTemp); + } + } + // Still not found? Then try to load the 64-Bit version: + if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariableA("ProgramFiles", szTemp, 4096) > 0) ) + { + strncat(szTemp, "\\Debugging Tools for Windows 64-Bit\\dbghelp.dll", sizeof(szTemp)); + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibraryA(szTemp); + } + } + } + } + if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one + m_hDbhHelp = LoadLibraryA("dbghelp.dll"); + if (m_hDbhHelp == NULL) + return FALSE; + pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" ); + pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" ); + + pSW = (tSW) GetProcAddress(m_hDbhHelp, "StackWalk64" ); + pSGO = (tSGO) GetProcAddress(m_hDbhHelp, "SymGetOptions" ); + pSSO = (tSSO) GetProcAddress(m_hDbhHelp, "SymSetOptions" ); + + pSFTA = (tSFTA) GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64" ); + pSGLFA = (tSGLFA) GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64" ); + pSGMB = (tSGMB) GetProcAddress(m_hDbhHelp, "SymGetModuleBase64" ); + pSGMI = (tSGMI) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" ); + //pSGMI_V3 = (tSGMI_V3) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" ); + pSGSFA = (tSGSFA) GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64" ); + pUDSN = (tUDSN) GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName" ); + pSLM = (tSLM) GetProcAddress(m_hDbhHelp, "SymLoadModule64" ); + pSGSP =(tSGSP) GetProcAddress(m_hDbhHelp, "SymGetSearchPath" ); + + if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL || + pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL || + pSW == NULL || pUDSN == NULL || pSLM == NULL ) + { + FreeLibrary(m_hDbhHelp); + m_hDbhHelp = NULL; + pSC = NULL; + return FALSE; + } + + // SymInitialize + if (szSymPath != NULL) + m_szSymPath = _strdup(szSymPath); + if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) + this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); + + DWORD symOptions = this->pSGO(); // SymGetOptions + symOptions |= SYMOPT_LOAD_LINES; + symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; + //symOptions |= SYMOPT_NO_PROMPTS; + // SymSetOptions + symOptions = this->pSSO(symOptions); + + char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0}; + if (this->pSGSP != NULL) + { + if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE) + this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0); + } + char szUserName[1024] = {0}; + DWORD dwSize = 1024; + GetUserNameA(szUserName, &dwSize); + this->m_parent->OnSymInit(buf, symOptions, szUserName); + + return TRUE; + } + + StackWalker *m_parent; + + HMODULE m_hDbhHelp; + HANDLE m_hProcess; + LPSTR m_szSymPath; + +typedef struct IMAGEHLP_MODULE64_V2 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name +}; + + // SymCleanup() + typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess ); + tSC pSC; + + // SymFunctionTableAccess64() + typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase ); + tSFTA pSFTA; + + // SymGetLineFromAddr64() + typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line ); + tSGLFA pSGLFA; + + // SymGetModuleBase64() + typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr ); + tSGMB pSGMB; + + // SymGetModuleInfo64() + typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V2 *ModuleInfo ); + tSGMI pSGMI; + + // SymGetOptions() + typedef DWORD (__stdcall *tSGO)( VOID ); + tSGO pSGO; + + // SymGetSymFromAddr64() + typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol ); + tSGSFA pSGSFA; + + // SymInitialize() + typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); + tSI pSI; + + // SymLoadModule64() + typedef DWORD64 (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); + tSLM pSLM; + + // SymSetOptions() + typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions ); + tSSO pSSO; + + // StackWalk64() + typedef BOOL (__stdcall *tSW)( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); + tSW pSW; + + // UnDecorateSymbolName() + typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags ); + tUDSN pUDSN; + + typedef BOOL (__stdcall WINAPI *tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength); + tSGSP pSGSP; + +private: + // **************************************** ToolHelp32 ************************ + #define MAX_MODULE_NAME32 255 + #define TH32CS_SNAPMODULE 0x00000008 + #pragma pack( push, 8 ) + typedef struct tagMODULEENTRY32 + { + DWORD dwSize; + DWORD th32ModuleID; // This module + DWORD th32ProcessID; // owning process + DWORD GlblcntUsage; // Global usage count on the module + DWORD ProccntUsage; // Module usage count in th32ProcessID's context + BYTE * modBaseAddr; // Base address of module in th32ProcessID's context + DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr + HMODULE hModule; // The hModule of this module in th32ProcessID's context + char szModule[MAX_MODULE_NAME32 + 1]; + char szExePath[MAX_PATH]; + } MODULEENTRY32; + typedef MODULEENTRY32 * PMODULEENTRY32; + typedef MODULEENTRY32 * LPMODULEENTRY32; + #pragma pack( pop ) + + BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) + { + // CreateToolhelp32Snapshot() + typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID); + // Module32First() + typedef BOOL (__stdcall *tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); + // Module32Next() + typedef BOOL (__stdcall *tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); + + // try both dlls... + const CHAR *dllname[] = { "kernel32.dll", "tlhelp32.dll" }; + HINSTANCE hToolhelp = NULL; + tCT32S pCT32S = NULL; + tM32F pM32F = NULL; + tM32N pM32N = NULL; + + HANDLE hSnap; + MODULEENTRY32 me; + me.dwSize = sizeof(me); + BOOL keepGoing; + size_t i; + + for (i = 0; i<(sizeof(dllname) / sizeof(dllname[0])); i++ ) + { + hToolhelp = LoadLibraryA( dllname[i] ); + if (hToolhelp == NULL) + continue; + pCT32S = (tCT32S) GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot"); + pM32F = (tM32F) GetProcAddress(hToolhelp, "Module32First"); + pM32N = (tM32N) GetProcAddress(hToolhelp, "Module32Next"); + if ( (pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL) ) + break; // found the functions! + FreeLibrary(hToolhelp); + hToolhelp = NULL; + } + + if (hToolhelp == NULL) + return FALSE; + + hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); + if (hSnap == (HANDLE) -1) + return FALSE; + + keepGoing = !!pM32F( hSnap, &me ); + int cnt = 0; + while (keepGoing) + { + this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize); + cnt++; + keepGoing = !!pM32N( hSnap, &me ); + } + CloseHandle(hSnap); + FreeLibrary(hToolhelp); + return (cnt <= 0) ? FALSE : TRUE; + } // GetModuleListTH32 + + // **************************************** PSAPI ************************ + typedef struct _MODULEINFO { + LPVOID lpBaseOfDll; + DWORD SizeOfImage; + LPVOID EntryPoint; + } MODULEINFO, *LPMODULEINFO; + + BOOL GetModuleListPSAPI(HANDLE hProcess) + { + // EnumProcessModules() + typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); + // GetModuleFileNameEx() + typedef DWORD (__stdcall *tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); + // GetModuleBaseName() + typedef DWORD (__stdcall *tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); + // GetModuleInformation() + typedef BOOL (__stdcall *tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize ); + + HINSTANCE hPsapi; + tEPM pEPM; + tGMFNE pGMFNE; + tGMBN pGMBN; + tGMI pGMI; + + DWORD i; + //ModuleEntry e; + DWORD cbNeeded; + MODULEINFO mi; + HMODULE *hMods = 0; + char *tt = NULL; + char *tt2 = NULL; + const SIZE_T TTBUFLEN = 8096; + int cnt = 0; + + hPsapi = LoadLibraryA("psapi.dll"); + if (hPsapi == NULL) + return FALSE; + + pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" ); + pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" ); + pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" ); + pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" ); + if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) ) + { + // we couldn´t find all functions + FreeLibrary(hPsapi); + return FALSE; + } + + hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof HMODULE)); + tt = (char*) malloc(sizeof(char) * TTBUFLEN); + tt2 = (char*) malloc(sizeof(char) * TTBUFLEN); + if ( (hMods == NULL) || (tt == NULL) || (tt2 == NULL) ) + goto cleanup; + + if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) ) + { + //_fprintf(fLogFile, "%lu: EPM failed, GetLastError = %lu\n", g_dwShowCount, gle ); + goto cleanup; + } + + if ( cbNeeded > TTBUFLEN ) + { + //_fprintf(fLogFile, "%lu: More than %lu module handles. Huh?\n", g_dwShowCount, lenof( hMods ) ); + goto cleanup; + } + + for ( i = 0; i < cbNeeded / sizeof hMods[0]; i++ ) + { + // base address, size + pGMI(hProcess, hMods[i], &mi, sizeof mi ); + // image file name + tt[0] = 0; + pGMFNE(hProcess, hMods[i], tt, TTBUFLEN ); + + // module name + tt2[0] = 0; + pGMBN(hProcess, hMods[i], tt2, TTBUFLEN ); + + DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage); + if (dwRes != ERROR_SUCCESS) this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0); + cnt++; + } + + cleanup: + if (hPsapi != NULL) FreeLibrary(hPsapi); + if (tt2 != NULL) free(tt2); + if (tt != NULL) free(tt); + if (hMods != NULL) free(hMods); + + return cnt != 0; + } // GetModuleListPSAPI + + DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size) + { + CHAR *szImg = _strdup(img); + CHAR *szMod = _strdup(mod); + DWORD result = ERROR_SUCCESS; + if ( (szImg == NULL) || (szMod == NULL) ) + result = ERROR_NOT_ENOUGH_MEMORY; + else + { + if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0) + result = GetLastError(); + } + ULONGLONG fileVersion = 0; + if ( (m_parent != NULL) && (szImg != NULL) ) + { + // try to retrive the file-version: + if ( (this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0) + { + VS_FIXEDFILEINFO *fInfo = NULL; + DWORD dwHandle; + DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle); + if (dwSize > 0) + { + LPVOID vData = malloc(dwSize); + if (vData != NULL) + { + if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0) + { + UINT len; + CHAR szSubBlock[] = "\\"; + if (VerQueryValueA(vData, szSubBlock, (LPVOID*) &fInfo, &len) == 0) + fInfo = NULL; + else + { + fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32); + } + } + free(vData); + } + } + } + + // Retrive some additional-infos about the module + IMAGEHLP_MODULE64_V2 Module; + const char *szSymType = "-unknown-"; + if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE) + { + switch(Module.SymType) + { + case SymNone: + szSymType = "-nosymbols-"; + break; + case SymCoff: + szSymType = "COFF"; + break; + case SymCv: + szSymType = "CV"; + break; + case SymPdb: + szSymType = "PDB"; + break; + case SymExport: + szSymType = "-exported-"; + break; + case SymDeferred: + szSymType = "-deferred-"; + break; + case SymSym: + szSymType = "SYM"; + break; + case 8: //SymVirtual: + szSymType = "Virtual"; + break; + case 9: // SymDia: + szSymType = "DIA"; + break; + } + } + this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, Module.LoadedImageName, fileVersion); + } + if (szImg != NULL) free(szImg); + if (szMod != NULL) free(szMod); + return result; + } +public: + BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) + { + // first try toolhelp32 + if (GetModuleListTH32(hProcess, dwProcessId)) + return true; + // then try psapi + return GetModuleListPSAPI(hProcess); + } + + BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V2 *pModuleInfo) + { + if(this->pSGMI == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + // as defined in VC7.1)... + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); + void *pData = malloc(4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites... + if (pData == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2)); + if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V2*) pData) != FALSE) + { + // only copy as much memory as is reserved... + memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); + free(pData); + return TRUE; + } + free(pData); + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } +}; + +// ############################################################# +StackWalker::StackWalker(FILE * optOutFile, String * optOutString, int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) +{ + this->m_outFile = optOutFile; + this->m_outString = optOutString; + this->m_options = options; + this->m_modulesLoaded = FALSE; + this->m_hProcess = hProcess; + this->m_sw = new StackWalkerInternal(this, this->m_hProcess); + this->m_dwProcessId = dwProcessId; + if (szSymPath != NULL) + { + this->m_szSymPath = _strdup(szSymPath); + this->m_options |= SymBuildPath; + } + else + this->m_szSymPath = NULL; +} + +StackWalker::~StackWalker() +{ + if (m_szSymPath != NULL) + free(m_szSymPath); + m_szSymPath = NULL; + if (this->m_sw != NULL) + delete this->m_sw; + this->m_sw = NULL; +} + +BOOL StackWalker::LoadModules() +{ + if (this->m_sw == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + if (m_modulesLoaded != FALSE) + return TRUE; + + // Build the sym-path: + char *szSymPath = NULL; + if ( (this->m_options & SymBuildPath) != 0) + { + const size_t nSymPathLen = 4096; + szSymPath = (char*) malloc(nSymPathLen); + if (szSymPath == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + szSymPath[0] = 0; + // Now first add the (optional) provided sympath: + if (this->m_szSymPath != NULL) + { + strcat_s(szSymPath, nSymPathLen, this->m_szSymPath); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + strcat_s(szSymPath, nSymPathLen, ".;"); + + const size_t nTempLen = 1024; + char szTemp[nTempLen]; + // Now add the current directory: + if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) + { + szTemp[nTempLen-1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + // Now add the path for the main-module: + if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0) + { + szTemp[nTempLen-1] = 0; + for (char *p = (szTemp+strlen(szTemp)-1); p >= szTemp; --p) + { + // locate the rightmost path separator + if ( (*p == '\\') || (*p == '/') || (*p == ':') ) + { + *p = 0; + break; + } + } // for (search for path separator...) + if (strlen(szTemp) > 0) + { + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + } + if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) + { + szTemp[nTempLen-1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) + { + szTemp[nTempLen-1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) + { + szTemp[nTempLen-1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + // also add the "system32"-directory: + strcat_s(szTemp, nTempLen, "\\system32"); + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + if ( (this->m_options & SymBuildPath) != 0) + { + if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0) + { + szTemp[nTempLen-1] = 0; + strcat_s(szSymPath, nSymPathLen, "SRV*"); + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, "\\websymbols"); + strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;"); + } + else + strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"); + } + } + + // First Init the whole stuff... + BOOL bRet = this->m_sw->Init(szSymPath); + if (szSymPath != NULL) free(szSymPath); szSymPath = NULL; + if (bRet == FALSE) + { + this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); + if (bRet != FALSE) + m_modulesLoaded = TRUE; + return bRet; +} + + +// The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction +// This has to be done due to a problem with the "hProcess"-parameter in x64... +// Because this class is in no case multi-threading-enabled (because of the limitations +// of dbghelp.dll) it is "safe" to use a static-variable +static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; +static LPVOID s_readMemoryFunction_UserData = NULL; + +BOOL StackWalker::ShowCallstack(uint32 maxDepth, HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) +{ + CONTEXT c;; + CallstackEntry csEntry; + IMAGEHLP_SYMBOL64 *pSym = NULL; + StackWalkerInternal::IMAGEHLP_MODULE64_V2 Module; + IMAGEHLP_LINE64 Line; + + if (m_modulesLoaded == FALSE) + this->LoadModules(); // ignore the result... + + if (this->m_sw->m_hDbhHelp == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + s_readMemoryFunction = readMemoryFunction; + s_readMemoryFunction_UserData = pUserData; + + if (context == NULL) + { + // If no context is provided, capture the context + if (hThread == GetCurrentThread()) + { + GET_CURRENT_CONTEXT(c, USED_CONTEXT_FLAGS); + } + else + { + SuspendThread(hThread); + memset(&c, 0, sizeof(CONTEXT)); + c.ContextFlags = USED_CONTEXT_FLAGS; + if (GetThreadContext(hThread, &c) == FALSE) + { + ResumeThread(hThread); + return FALSE; + } + } + } + else + c = *context; + + // init STACKFRAME for first call + STACKFRAME64 s; // in/out stackframe + memset(&s, 0, sizeof(s)); + DWORD imageType; +#ifdef _M_IX86 + // normally, call ImageNtHeader() and use machine info from PE header + imageType = IMAGE_FILE_MACHINE_I386; + s.AddrPC.Offset = c.Eip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Ebp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Esp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + imageType = IMAGE_FILE_MACHINE_AMD64; + s.AddrPC.Offset = c.Rip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Rsp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Rsp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + imageType = IMAGE_FILE_MACHINE_IA64; + s.AddrPC.Offset = c.StIIP; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.IntSp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrBStore.Offset = c.RsBSP; + s.AddrBStore.Mode = AddrModeFlat; + s.AddrStack.Offset = c.IntSp; + s.AddrStack.Mode = AddrModeFlat; +#else +# error "StackWalker: Platform not supported!" +#endif + + pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + if (!pSym) goto cleanup; // not enough memory... + memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; + + memset(&Line, 0, sizeof(Line)); + Line.SizeOfStruct = sizeof(Line); + + memset(&Module, 0, sizeof(Module)); + Module.SizeOfStruct = sizeof(Module); + + for (uint32 frameNum=0; frameNumm_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, ReadProcMemCallback, this->m_sw->pSFTA, this->m_sw->pSGMB, NULL) ) + { + this->OnDbgHelpErr("StackWalk64", GetLastError(), s.AddrPC.Offset); + break; + } + + csEntry.offset = s.AddrPC.Offset; + csEntry.name[0] = 0; + csEntry.undName[0] = 0; + csEntry.undFullName[0] = 0; + csEntry.offsetFromSmybol = 0; + csEntry.offsetFromLine = 0; + csEntry.lineFileName[0] = 0; + csEntry.lineNumber = 0; + csEntry.loadedImageName[0] = 0; + csEntry.moduleName[0] = 0; + if (s.AddrPC.Offset == s.AddrReturn.Offset) + { + this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset); + break; + } + if (s.AddrPC.Offset != 0) + { + // we seem to have a valid PC + // show procedure info (SymGetSymFromAddr64()) + if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE) + { + // TODO: Mache dies sicher...! + strcpy_s(csEntry.name, pSym->Name); + // UnDecorateSymbolName() + this->m_sw->pUDSN( pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY ); + this->m_sw->pUDSN( pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE ); + } + else + { + this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); + } + + // show line number info, NT5.0-method (SymGetLineFromAddr64()) + if (this->m_sw->pSGLFA != NULL ) + { // yes, we have SymGetLineFromAddr64() + if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE) + { + csEntry.lineNumber = Line.LineNumber; + // TODO: Mache dies sicher...! + strcpy_s(csEntry.lineFileName, Line.FileName); + } + else + { + this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset); + } + } // yes, we have SymGetLineFromAddr64() + + // show module info (SymGetModuleInfo64()) + if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module ) != FALSE) + { // got module info OK + switch ( Module.SymType ) + { + case SymNone: + csEntry.symTypeString = "-nosymbols-"; + break; + case SymCoff: + csEntry.symTypeString = "COFF"; + break; + case SymCv: + csEntry.symTypeString = "CV"; + break; + case SymPdb: + csEntry.symTypeString = "PDB"; + break; + case SymExport: + csEntry.symTypeString = "-exported-"; + break; + case SymDeferred: + csEntry.symTypeString = "-deferred-"; + break; + case SymSym: + csEntry.symTypeString = "SYM"; + break; +#if API_VERSION_NUMBER >= 9 + case SymDia: + csEntry.symTypeString = "DIA"; + break; +#endif + case 8: //SymVirtual: + csEntry.symTypeString = "Virtual"; + break; + default: + //_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType ); + csEntry.symTypeString = NULL; + break; + } + + // TODO: Mache dies sicher...! + strcpy_s(csEntry.moduleName, Module.ModuleName); + csEntry.baseOfImage = Module.BaseOfImage; + strcpy_s(csEntry.loadedImageName, Module.LoadedImageName); + } // got module info OK + else + { + this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset); + } + } // we seem to have a valid PC + + CallstackEntryType et = nextEntry; + if (frameNum == 0) et = firstEntry; + this->OnCallstackEntry(et, csEntry); + + if (s.AddrReturn.Offset == 0) + { + this->OnCallstackEntry(lastEntry, csEntry); + SetLastError(ERROR_SUCCESS); + break; + } + } // for ( frameNum ) + + cleanup: + if (pSym) free( pSym ); + + if (context == NULL) + ResumeThread(hThread); + + return TRUE; +} + +BOOL __stdcall StackWalker::ReadProcMemCallback( + HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead + ) +{ + if (s_readMemoryFunction == NULL) + { + SIZE_T st; + BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st); + *lpNumberOfBytesRead = (DWORD) st; + //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet); + return bRet; + } + else + { + return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData); + } +} + +void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + if (fileVersion == 0) _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName); + else + { + DWORD v4 = (DWORD) fileVersion & 0xFFFF; + DWORD v3 = (DWORD) (fileVersion>>16) & 0xFFFF; + DWORD v2 = (DWORD) (fileVersion>>32) & 0xFFFF; + DWORD v1 = (DWORD) (fileVersion>>48) & 0xFFFF; + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4); + } +#ifdef REMOVED_BY_JAF_TOO_MUCH_INFORMATION + OnOutput(buffer); +#endif +} + +void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + if ( (eType != lastEntry) && (entry.offset != 0) ) + { + if (entry.name[0] == 0) + strcpy_s(entry.name, "(function-name not available)"); + if (entry.undName[0] != 0) + strcpy_s(entry.name, entry.undName); + if (entry.undFullName[0] != 0) + strcpy_s(entry.name, entry.undFullName); + if (entry.lineFileName[0] == 0) + { + strcpy_s(entry.lineFileName, "(filename not available)"); + if (entry.moduleName[0] == 0) + strcpy_s(entry.moduleName, "(module-name not available)"); + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%p (%s): %s: %s\n", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name); + } + else + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s (%d): %s\n", entry.lineFileName, entry.lineNumber, entry.name); + OnOutput(buffer); + } +} + +void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr); + OnOutput(buffer); +} + +void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName); +#ifdef REMOVED_BY_JAF_TOO_MUCH_INFORMATION + OnOutput(buffer); +#endif + + // Also display the OS-version + OSVERSIONINFOEXA ver; ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA)); + ver.dwOSVersionInfoSize = sizeof(ver); + if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE) + { + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); +#ifdef REMOVED_BY_JAF_TOO_MUCH_INFORMATION + OnOutput(buffer); +#endif + } +} + +#endif // Windows stack trace code + +// Win32 doesn't have localtime_r, so we have to roll our own +#if defined(WIN32) +static inline struct tm * muscle_localtime_r(time_t * clock, struct tm * result) +{ + // Note that in Win32, (ret) points to thread-local storage, so this really + // is thread-safe despite the fact that it looks like it isn't! + struct tm * ret = localtime(clock); + if (ret) *result = *ret; + return ret; +} +static inline struct tm * muscle_gmtime_r(time_t * clock, struct tm * result) +{ + // Note that in Win32, (ret) points to thread-local storage, so this really + // is thread-safe despite the fact that it looks like it isn't! + struct tm * ret = gmtime(clock); + if (ret) *result = *ret; + return ret; +} +#else +static inline struct tm * muscle_localtime_r(time_t * clock, struct tm * result) {return localtime_r(clock, result);} +static inline struct tm * muscle_gmtime_r( time_t * clock, struct tm * result) {return gmtime_r(clock, result);} +#endif + +#ifndef MUSCLE_INLINE_LOGGING + +#define MAX_STACK_TRACE_DEPTH ((uint32)(256)) + +status_t PrintStackTrace(FILE * optFile, uint32 maxDepth) +{ + TCHECKPOINT; + + if (optFile == NULL) optFile = stdout; + +#if defined(MUSCLE_USE_BACKTRACE) + void *array[MAX_STACK_TRACE_DEPTH]; + size_t size = backtrace(array, muscleMin(maxDepth, MAX_STACK_TRACE_DEPTH)); + char ** strings = backtrace_symbols(array, size); + if (strings) + { + fprintf(optFile, "--Stack trace follows (%zd frames):\n", size); + for (size_t i = 0; i < size; i++) fprintf(optFile, " %s\n", strings[i]); + fprintf(optFile, "--End Stack trace\n"); + free(strings); + return B_NO_ERROR; + } + else fprintf(optFile, "PrintStackTrace: Error, could not generate stack trace!\n"); +#elif defined(MUSCLE_USE_MSVC_STACKWALKER) + _Win32PrintStackTraceForContext(optFile, NULL, maxDepth); +#else + (void) maxDepth; // shut the compiler up + fprintf(optFile, "PrintStackTrace: Error, stack trace printing not available on this platform!\n"); +#endif + + return B_ERROR; // I don't know how to do this for other systems! +} + +status_t GetStackTrace(String & retStr, uint32 maxDepth) +{ + TCHECKPOINT; + +#if defined(MUSCLE_USE_BACKTRACE) + void *array[MAX_STACK_TRACE_DEPTH]; + size_t size = backtrace(array, muscleMin(maxDepth, MAX_STACK_TRACE_DEPTH)); + char ** strings = backtrace_symbols(array, size); + if (strings) + { + char buf[128]; + sprintf(buf, "--Stack trace follows (%zd frames):", size); retStr += buf; + for (size_t i = 0; i < size; i++) + { + retStr += "\n "; + retStr += strings[i]; + } + retStr += "\n--End Stack trace\n"; + free(strings); + return B_NO_ERROR; + } +#elif defined(MUSCLE_USE_MSVC_STACKWALKER) + StackWalker(NULL, &retStr, StackWalker::OptionsJAF).ShowCallstack(maxDepth); +#else + (void) retStr; // shut the compiler up + (void) maxDepth; +#endif + + return B_ERROR; +} + +static NestCount _inLogPreamble; + +static const char * const _logLevelNames[] = { + "None", + "Critical Errors Only", + "Errors Only", + "Warnings and Errors Only", + "Informational", + "Debug", + "Trace" +}; + +static const char * const _logLevelKeywords[] = { + "none", + "critical", + "errors", + "warnings", + "info", + "debug", + "trace" +}; + +DefaultConsoleLogger :: DefaultConsoleLogger() : _consoleLogLevel(MUSCLE_LOG_INFO) +{ + // empty +} + +void DefaultConsoleLogger :: Log(const LogCallbackArgs & a) +{ + if (a.GetLogLevel() <= _consoleLogLevel) + { + vprintf(a.GetText(), *a.GetArgList()); + fflush(stdout); + } +} + +void DefaultConsoleLogger :: Flush() +{ + fflush(stdout); +} + +DefaultFileLogger :: DefaultFileLogger() : _fileLogLevel(MUSCLE_LOG_NONE), _maxLogFileSize(MUSCLE_NO_LIMIT), _maxNumLogFiles(MUSCLE_NO_LIMIT), _compressionEnabled(false), _logFileOpenAttemptFailed(false) +{ + // empty +} + +DefaultFileLogger :: ~DefaultFileLogger() +{ + CloseLogFile(); +} + +void DefaultFileLogger :: Log(const LogCallbackArgs & a) +{ + if ((a.GetLogLevel() <= GetFileLogLevel())&&(EnsureLogFileCreated(a) == B_NO_ERROR)) + { + vfprintf(_logFile.GetFile(), a.GetText(), *a.GetArgList()); + _logFile.FlushOutput(); + if ((_maxLogFileSize != MUSCLE_NO_LIMIT)&&(_inLogPreamble.IsInBatch() == false)) // wait until we're outside the preamble to avoid breaking up lines too much + { + int64 curFileSize = _logFile.GetPosition(); + if ((curFileSize < 0)||(curFileSize >= (int64)_maxLogFileSize)) + { + uint32 tempStoreSize = _maxLogFileSize; + _maxLogFileSize = MUSCLE_NO_LIMIT; // otherwise we'd recurse indefinitely here! + CloseLogFile(); + _maxLogFileSize = tempStoreSize; + (void) EnsureLogFileCreated(a); // force the opening of the new log file right now, so that the open message show up in the right order + } + } + } +} + +void DefaultFileLogger :: Flush() +{ + _logFile.FlushOutput(); +} + +uint32 DefaultFileLogger :: AddPreExistingLogFiles(const String & filePattern) +{ + String dirPart, filePart; + int32 lastSlash = filePattern.LastIndexOf(GetFilePathSeparator()); + if (lastSlash >= 0) + { + dirPart = filePattern.Substring(0, lastSlash); + filePart = filePattern.Substring(lastSlash+1); + } + else + { + dirPart = "."; + filePart = filePattern; + } + + Hashtable pathToTime; + if (filePart.HasChars()) + { + StringMatcher sm(filePart); + + Directory d(dirPart()); + if (d.IsValid()) + { + const char * nextName; + while((nextName = d.GetCurrentFileName()) != NULL) + { + String fn = nextName; + if (sm.Match(fn)) + { + String fullPath = dirPart+GetFilePathSeparator()+fn; + FilePathInfo fpi(fullPath()); + if ((fpi.Exists())&&(fpi.IsRegularFile())) pathToTime.Put(fullPath, fpi.GetCreationTime()); + } + d++; + } + } + + // Now we sort by creation time... + pathToTime.SortByValue(); + + // And add the results to our _oldFileNames queue. That way when the log file is opened, the oldest files will be deleted (if appropriate) + for (HashtableIterator iter(pathToTime); iter.HasData(); iter++) (void) _oldLogFileNames.AddTail(iter.GetKey()); + } + return pathToTime.GetNumItems(); +} + +status_t DefaultFileLogger :: EnsureLogFileCreated(const LogCallbackArgs & a) +{ + if ((_logFile.GetFile() == NULL)&&(_logFileOpenAttemptFailed == false)) + { + String logFileName = _prototypeLogFileName; + if (logFileName.IsEmpty()) logFileName = "%f.log"; + + HumanReadableTimeValues hrtv; (void) GetHumanReadableTimeValues(SecondsToMicros(a.GetWhen()), hrtv); + logFileName = hrtv.ExpandTokens(logFileName); + + _logFile.SetFile(fopen(logFileName(), "w")); + if (_logFile.GetFile() != NULL) + { + _activeLogFileName = logFileName; + LogTime(MUSCLE_LOG_DEBUG, "Created Log file [%s]\n", _activeLogFileName()); + + while(_oldLogFileNames.GetNumItems() >= _maxNumLogFiles) + { + const char * c = _oldLogFileNames.Head()(); + if (remove(c) == 0) LogTime(MUSCLE_LOG_DEBUG, "Deleted old Log file [%s]\n", c); + else if (errno != ENOENT) LogTime(MUSCLE_LOG_ERROR, "Error deleting old Log file [%s]\n", c); + _oldLogFileNames.RemoveHead(); + } + + String headerString = GetLogFileHeaderString(a); + if (headerString.HasChars()) fprintf(_logFile.GetFile(), "%s\n", headerString()); + } + else + { + _activeLogFileName.Clear(); + _logFileOpenAttemptFailed = true; // avoid an indefinite number of log-failed messages + LogTime(MUSCLE_LOG_ERROR, "Failed to open Log file [%s], logging to file is now disabled.\n", logFileName()); + } + } + return (_logFile.GetFile() != NULL) ? B_NO_ERROR : B_ERROR; +} + +void DefaultFileLogger :: CloseLogFile() +{ + if (_logFile.GetFile()) + { + LogTime(MUSCLE_LOG_DEBUG, "Closing Log file [%s]\n", _activeLogFileName()); + String oldFileName = _activeLogFileName; // default file to delete later, will be changed if/when we've made the .gz file + _activeLogFileName.Clear(); // do this first to avoid reentrancy issues + _logFile.Shutdown(); + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + if (_compressionEnabled) + { + FileDataIO inIO(fopen(oldFileName(), "rb")); + if (inIO.GetFile() != NULL) + { + String gzName = oldFileName + ".gz"; + gzFile gzOut = gzopen(gzName(), "wb9"); // 9 for maximum compression + if (gzOut != Z_NULL) + { + bool ok = true; + while(1) + { + char buf[128*1024]; + int32 bytesRead = inIO.Read(buf, sizeof(buf)); + if (bytesRead < 0) break; // EOF + + int bytesWritten = gzwrite(gzOut, buf, bytesRead); + if (bytesWritten <= 0) + { + ok = false; // write error, oh dear + break; + } + } + gzclose(gzOut); + + if (ok) + { + inIO.Shutdown(); + if (remove(oldFileName()) != 0) LogTime(MUSCLE_LOG_ERROR, "Error deleting log file [%s] after compressing it to [%s]!\n", oldFileName(), gzName()); + oldFileName = gzName; + } + else + { + if (remove(gzName()) != 0) LogTime(MUSCLE_LOG_ERROR, "Error deleting gzip'd log file [%s] after compression failed!\n", gzName()); + } + } + else LogTime(MUSCLE_LOG_ERROR, "Could not open compressed Log file [%s]!\n", gzName()); + } + else LogTime(MUSCLE_LOG_ERROR, "Could not reopen Log file [%s] to compress it!\n", oldFileName()); + } +#endif + if (_maxNumLogFiles != MUSCLE_NO_LIMIT) (void) _oldLogFileNames.AddTail(oldFileName); // so we can delete it later + } +} + +LogLineCallback :: LogLineCallback() : _writeTo(_buf) +{ + _buf[0] = '\0'; + _buf[sizeof(_buf)-1] = '\0'; // just in case vsnsprintf() has to truncate +} + +LogLineCallback :: ~LogLineCallback() +{ + // empty +} + +void LogLineCallback :: Log(const LogCallbackArgs & a) +{ + TCHECKPOINT; + + // Generate the new text +#ifdef __MWERKS__ + int bytesAttempted = vsprintf(_writeTo, a.GetText(), *a.GetArgList()); // BeOS/PPC doesn't know vsnprintf :^P +#elif WIN32 + int bytesAttempted = _vsnprintf(_writeTo, (sizeof(_buf)-1)-(_writeTo-_buf), a.GetText(), *a.GetArgList()); // the -1 is for the guaranteed NUL terminator +#else + int bytesAttempted = vsnprintf(_writeTo, (sizeof(_buf)-1)-(_writeTo-_buf), a.GetText(), *a.GetArgList()); // the -1 is for the guaranteed NUL terminator +#endif + bool wasTruncated = (bytesAttempted != (int)strlen(_writeTo)); // do not combine with above line! + + // Log any newly completed lines + char * logFrom = _buf; + char * searchAt = _writeTo; + LogCallbackArgs tmp(a); + while(true) + { + char * nextReturn = strchr(searchAt, '\n'); + if (nextReturn) + { + *nextReturn = '\0'; // terminate the string + tmp.SetText(logFrom); + LogLine(tmp); + searchAt = logFrom = nextReturn+1; + } + else + { + // If we ran out of buffer space and no carriage returns were detected, + // then we need to just dump what we have and move on, there's nothing else we can do + if (wasTruncated) + { + tmp.SetText(logFrom); + LogLine(tmp); + _buf[0] = '\0'; + _writeTo = searchAt = logFrom = _buf; + } + break; + } + } + + // And finally, move any remaining incomplete lines back to the beginning of the array, for next time + if (logFrom > _buf) + { + int slen = (int) strlen(logFrom); + memmove(_buf, logFrom, slen+1); // include NUL byte + _writeTo = &_buf[slen]; // point to our just-moved NUL byte + } + else _writeTo = strchr(searchAt, '\0'); + + _lastLog = a; +} + +void LogLineCallback :: Flush() +{ + TCHECKPOINT; + + if (_writeTo > _buf) + { + _lastLog.SetText(_buf); + LogLine(_lastLog); + _writeTo = _buf; + _buf[0] = '\0'; + } +} + +static Mutex _logMutex; +static Hashtable _logCallbacks; +static DefaultConsoleLogger _dcl; +static DefaultFileLogger _dfl; + +status_t LockLog() +{ +#ifdef MUSCLE_SINGLE_THREAD_ONLY + return B_NO_ERROR; +#else + return _logMutex.Lock(); +#endif +} + +status_t UnlockLog() +{ +#ifdef MUSCLE_SINGLE_THREAD_ONLY + return B_NO_ERROR; +#else + return _logMutex.Unlock(); +#endif +} + +const char * GetLogLevelName(int ll) +{ + return ((ll>=0)&&(ll<(int) ARRAYITEMS(_logLevelNames))) ? _logLevelNames[ll] : "???"; +} + +const char * GetLogLevelKeyword(int ll) +{ + return ((ll>=0)&&(ll<(int) ARRAYITEMS(_logLevelKeywords))) ? _logLevelKeywords[ll] : "???"; +} + +int ParseLogLevelKeyword(const char * keyword) +{ + for (uint32 i=0; i= _keySpaceSize) return "????"; // values greater than or equal to our key space size are errors + + char buf[5]; buf[4] = '\0'; + for (int32 i=3; i>=0; i--) + { + buf[i] = _keyAlphabet[key % NUM_CHARS_IN_KEY_ALPHABET]; + key /= NUM_CHARS_IN_KEY_ALPHABET; + } + return buf; +} + +uint32 SourceCodeLocationKeyFromString(const String & ss) +{ + String s = ss.ToUpperCase().Trim(); + if (s.Length() != 4) return 0; // codes must always be exactly 4 characters long! + + s.Replace('0', 'O'); + s.Replace('1', 'I'); + s.Replace('5', 'S'); + + uint32 ret = 0; + uint32 base = 1; + for (int32 i=3; i>=0; i--) + { + const char * p = strchr(_keyAlphabet, s[i]); + if (p == NULL) return 0; // invalid character! + + int whichChar = (int) (p-_keyAlphabet); + ret += (whichChar*base); + base *= NUM_CHARS_IN_KEY_ALPHABET; + } + return ret; +} + +void GetStandardLogLinePreamble(char * buf, const LogCallbackArgs & a) +{ + struct tm ltm; + time_t when = a.GetWhen(); + struct tm * temp = muscle_localtime_r(&when, <m); +#ifdef MUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME + sprintf(buf, "[%c %02i/%02i %02i:%02i:%02i] [%s] ", GetLogLevelName(a.GetLogLevel())[0], temp->tm_mon+1, temp->tm_mday, temp->tm_hour, temp->tm_min, temp->tm_sec, SourceCodeLocationKeyToString(GenerateSourceCodeLocationKey(a.GetSourceFile(), a.GetSourceLineNumber()))()); +#else + sprintf(buf, "[%c %02i/%02i %02i:%02i:%02i] ", GetLogLevelName(a.GetLogLevel())[0], temp->tm_mon+1, temp->tm_mday, temp->tm_hour, temp->tm_min, temp->tm_sec); +#endif +} + +#define DO_LOGGING_CALLBACK(cb) \ +{ \ + va_list argList; \ + va_start(argList, fmt); \ + cb.Log(LogCallbackArgs(when, ll, sourceFile, sourceFunction, sourceLine, fmt, &argList)); \ + va_end(argList); \ +} + +#define DO_LOGGING_CALLBACKS for (HashtableIterator iter(_logCallbacks); iter.HasData(); iter++) if (iter.GetKey()()) DO_LOGGING_CALLBACK((*iter.GetKey()())); + +#ifdef MUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME +status_t _LogTime(const char * sourceFile, const char * sourceFunction, int sourceLine, int ll, const char * fmt, ...) +#else +status_t LogTime(int ll, const char * fmt, ...) +#endif +{ +#ifndef MUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME + static const char * sourceFile = ""; + static const char * sourceFunction = ""; + static const int sourceLine = -1; +#endif + + status_t lockRet = LockLog(); + { + // First, log the preamble + time_t when = time(NULL); + char buf[128]; + + // First, send to the log file + { + NestCountGuard g(_inLogPreamble); // must be inside the braces! + va_list dummyList; + va_start(dummyList, fmt); // not used + LogCallbackArgs lca(when, ll, sourceFile, sourceFunction, sourceLine, buf, &dummyList); + GetStandardLogLinePreamble(buf, lca); + lca.SetText(buf); + _dfl.Log(lca); + va_end(dummyList); + } + DO_LOGGING_CALLBACK(_dfl); + + // Then, send to the display + { + NestCountGuard g(_inLogPreamble); // must be inside the braces! + va_list dummyList; + va_start(dummyList, fmt); // not used + _dcl.Log(LogCallbackArgs(when, ll, sourceFile, sourceFunction, sourceLine, buf, &dummyList)); + va_end(dummyList); + } + DO_LOGGING_CALLBACK(_dcl); // must be outside of the braces! + + // Then log the actual message as supplied by the user + if (lockRet == B_NO_ERROR) DO_LOGGING_CALLBACKS; + } + if (lockRet == B_NO_ERROR) UnlockLog(); + + return lockRet; +} + +status_t LogFlush() +{ + TCHECKPOINT; + + if (LockLog() == B_NO_ERROR) + { + for (HashtableIterator iter(_logCallbacks); iter.HasData(); iter++) if (iter.GetKey()()) iter.GetKey()()->Flush(); + (void) UnlockLog(); + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t LogStackTrace(int ll, uint32 maxDepth) +{ + TCHECKPOINT; + +#if defined(MUSCLE_USE_BACKTRACE) + void *array[MAX_STACK_TRACE_DEPTH]; + size_t size = backtrace(array, muscleMin(maxDepth, MAX_STACK_TRACE_DEPTH)); + char ** strings = backtrace_symbols(array, size); + if (strings) + { + LogTime(ll, "--Stack trace follows (%zd frames):\n", size); + for (size_t i = 0; i < size; i++) LogTime(ll, " %s\n", strings[i]); + LogTime(ll, "--End Stack trace\n"); + free(strings); + return B_NO_ERROR; + } +#else + (void) ll; // shut the compiler up + (void) maxDepth; // shut the compiler up +#endif + + return B_ERROR; // I don't know how to do this for other systems! +} + +status_t Log(int ll, const char * fmt, ...) +{ + // No way to get these, since #define Log() as a macro causes + // nasty namespace collisions with other methods/functions named Log() + static const char * sourceFile = ""; + static const char * sourceFunction = ""; + static const int sourceLine = -1; + + status_t lockRet = LockLog(); + { + time_t when = time(NULL); // don't inline this, ya dummy + DO_LOGGING_CALLBACK(_dfl); + DO_LOGGING_CALLBACK(_dcl); + if (lockRet == B_NO_ERROR) DO_LOGGING_CALLBACKS; + } + if (lockRet == B_NO_ERROR) (void) UnlockLog(); + return lockRet; +} + +status_t PutLogCallback(const LogCallbackRef & cb) +{ + status_t ret = B_ERROR; + if (LockLog() == B_NO_ERROR) + { + ret = _logCallbacks.PutWithDefault(cb); + (void) UnlockLog(); + } + return ret; +} + +status_t ClearLogCallbacks() +{ + status_t ret = B_ERROR; + if (LockLog() == B_NO_ERROR) + { + _logCallbacks.Clear(); + (void) UnlockLog(); + } + return ret; +} + +status_t RemoveLogCallback(const LogCallbackRef & cb) +{ + status_t ret = B_ERROR; + if (LockLog() == B_NO_ERROR) + { + ret = _logCallbacks.Remove(cb); + (void) UnlockLog(); + } + return ret; +} + +#endif + +#ifdef WIN32 +static const uint64 _windowsDiffTime = ((uint64)116444736)*NANOS_PER_SECOND; // add (1970-1601) to convert to Windows time base +#endif + +status_t GetHumanReadableTimeValues(uint64 timeUS, HumanReadableTimeValues & v, uint32 timeType) +{ + TCHECKPOINT; + + if (timeUS == MUSCLE_TIME_NEVER) return B_ERROR; + + int microsLeft = (int)(timeUS % MICROS_PER_SECOND); + +#ifdef WIN32 + // Borland's localtime() function is buggy, so we'll use the Win32 API instead. + uint64 winTime = (timeUS*10) + _windowsDiffTime; // Convert to (100ns units) + + FILETIME fileTime; + fileTime.dwHighDateTime = (DWORD) ((winTime>>32) & 0xFFFFFFFF); + fileTime.dwLowDateTime = (DWORD) ((winTime>> 0) & 0xFFFFFFFF); + + SYSTEMTIME st; + if (FileTimeToSystemTime(&fileTime, &st)) + { + if (timeType == MUSCLE_TIMEZONE_UTC) + { + TIME_ZONE_INFORMATION tzi; + if ((GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_INVALID)||(SystemTimeToTzSpecificLocalTime(&tzi, &st, &st) == false)) return B_ERROR; + } + v = HumanReadableTimeValues(st.wYear, st.wMonth-1, st.wDay-1, st.wDayOfWeek, st.wHour, st.wMinute, st.wSecond, microsLeft); + return B_NO_ERROR; + } +#else + struct tm ltm, gtm; + time_t timeS = (time_t) MicrosToSeconds(timeUS); // timeS is seconds since 1970 + struct tm * ts = (timeType == MUSCLE_TIMEZONE_UTC) ? muscle_localtime_r(&timeS, <m) : muscle_gmtime_r(&timeS, >m); // only convert if it isn't already local + if (ts) + { + v = HumanReadableTimeValues(ts->tm_year+1900, ts->tm_mon, ts->tm_mday-1, ts->tm_wday, ts->tm_hour, ts->tm_min, ts->tm_sec, microsLeft); + return B_NO_ERROR; + } +#endif + + return B_ERROR; +} + +#ifdef WIN32 +static bool MUSCLE_TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION tzi, LPSYSTEMTIME st) +{ +# if defined(__BORLANDC__) || defined(MUSCLE_USING_OLD_MICROSOFT_COMPILER) || defined(__MINGW32__) +# if defined(_MSC_VER) + typedef BOOL (*TzSpecificLocalTimeToSystemTimeProc) (IN LPTIME_ZONE_INFORMATION lpTimeZoneInformation, IN LPSYSTEMTIME lpLocalTime, OUT LPSYSTEMTIME lpUniversalTime); +# else + typedef WINBASEAPI BOOL WINAPI (*TzSpecificLocalTimeToSystemTimeProc) (IN LPTIME_ZONE_INFORMATION lpTimeZoneInformation, IN LPSYSTEMTIME lpLocalTime, OUT LPSYSTEMTIME lpUniversalTime); +# endif + + // Some compilers' headers don't have this call, so we have to do it the hard way + HMODULE lib = LoadLibrary(TEXT("kernel32.dll")); + if (lib == NULL) return false; + + TzSpecificLocalTimeToSystemTimeProc tzProc = (TzSpecificLocalTimeToSystemTimeProc) GetProcAddress(lib, "TzSpecificLocalTimeToSystemTime"); + bool ret = ((tzProc)&&(tzProc(tzi, st, st))); + FreeLibrary(lib); + return ret; +# else + return (TzSpecificLocalTimeToSystemTime(tzi, st, st) != 0); +# endif +} +#endif + +status_t GetTimeStampFromHumanReadableTimeValues(const HumanReadableTimeValues & v, uint64 & retTimeStamp, uint32 timeType) +{ + TCHECKPOINT; + +#ifdef WIN32 + SYSTEMTIME st; memset(&st, 0, sizeof(st)); + st.wYear = v.GetYear(); + st.wMonth = v.GetMonth()+1; + st.wDayOfWeek = v.GetDayOfWeek(); + st.wDay = v.GetDayOfMonth()+1; + st.wHour = v.GetHour(); + st.wMinute = v.GetMinute(); + st.wSecond = v.GetSecond(); + st.wMilliseconds = v.GetMicrosecond()/1000; + + if (timeType == MUSCLE_TIMEZONE_UTC) + { + TIME_ZONE_INFORMATION tzi; + if ((GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_INVALID)||(MUSCLE_TzSpecificLocalTimeToSystemTime(&tzi, &st) == false)) return B_ERROR; + } + + FILETIME fileTime; + if (SystemTimeToFileTime(&st, &fileTime)) + { + retTimeStamp = (((((uint64)fileTime.dwHighDateTime)<<32)|((uint64)fileTime.dwLowDateTime))-_windowsDiffTime)/10; + return B_NO_ERROR; + } + else return B_ERROR; +#else + struct tm ltm; memset(<m, 0, sizeof(ltm)); + ltm.tm_sec = v.GetSecond(); /* seconds after the minute [0-60] */ + ltm.tm_min = v.GetMinute(); /* minutes after the hour [0-59] */ + ltm.tm_hour = v.GetHour(); /* hours since midnight [0-23] */ + ltm.tm_mday = v.GetDayOfMonth()+1; /* day of the month [1-31] */ + ltm.tm_mon = v.GetMonth(); /* months since January [0-11] */ + ltm.tm_year = v.GetYear()-1900; /* years since 1900 */ + ltm.tm_wday = v.GetDayOfWeek(); /* days since Sunday [0-6] */ + ltm.tm_isdst = -1; /* Let mktime() decide whether summer time is in effect */ + + time_t tm = ((uint64)((timeType == MUSCLE_TIMEZONE_UTC) ? mktime(<m) : timegm(<m))); + if (tm == -1) return B_ERROR; + + retTimeStamp = SecondsToMicros(tm); + return B_NO_ERROR; +#endif +} + + +String HumanReadableTimeValues :: ToString() const +{ + return ExpandTokens("%T"); // Yes, this must be here in the .cpp file! +} + +String HumanReadableTimeValues :: ExpandTokens(const String & origString) const +{ + if (origString.IndexOf('%') < 0) return origString; + + String newString = origString; + (void) newString.Replace("%%", "%"); // do this first! + (void) newString.Replace("%T", "%Q %D %Y %h:%m:%s"); + (void) newString.Replace("%t", "%Y/%M/%D %h:%m:%s"); + (void) newString.Replace("%f", "%Y-%M-%D_%hh%mm%s"); + + static const char * _daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + static const char * _monthsOfYear[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; + + (void) newString.Replace("%Y", String("%1").Arg(GetYear())); + (void) newString.Replace("%M", String("%1").Arg(GetMonth()+1, "%02i")); + (void) newString.Replace("%Q", String("%1").Arg(_monthsOfYear[muscleClamp(GetMonth(), 0, (int)(ARRAYITEMS(_monthsOfYear)-1))])); + (void) newString.Replace("%D", String("%1").Arg(GetDayOfMonth()+1, "%02i")); + (void) newString.Replace("%d", String("%1").Arg(GetDayOfMonth()+1, "%02i")); + (void) newString.Replace("%W", String("%1").Arg(GetDayOfWeek()+1, "%02i")); + (void) newString.Replace("%w", String("%1").Arg(GetDayOfWeek()+1, "%02i")); + (void) newString.Replace("%q", String("%1").Arg(_daysOfWeek[muscleClamp(GetDayOfWeek(), 0, (int)(ARRAYITEMS(_daysOfWeek)-1))])); + (void) newString.Replace("%h", String("%1").Arg(GetHour(), "%02i")); + (void) newString.Replace("%m", String("%1").Arg(GetMinute(), "%02i")); + (void) newString.Replace("%s", String("%1").Arg(GetSecond(), "%02i")); + (void) newString.Replace("%x", String("%1").Arg(GetMicrosecond(), "%06i")); + + uint32 r1 = rand(); + uint32 r2 = rand(); + char buf[64]; sprintf(buf, UINT64_FORMAT_SPEC, (((uint64)r1)<<32)|((uint64)r2)); + (void) newString.Replace("%r", buf); + + return newString; +} + +String GetHumanReadableTimeString(uint64 timeUS, uint32 timeType) +{ + TCHECKPOINT; + + if (timeUS == MUSCLE_TIME_NEVER) return ("(never)"); + else + { + HumanReadableTimeValues v; + if (GetHumanReadableTimeValues(timeUS, v, timeType) == B_NO_ERROR) + { + char buf[256]; + sprintf(buf, "%02i/%02i/%02i %02i:%02i:%02i", v.GetYear(), v.GetMonth()+1, v.GetDayOfMonth()+1, v.GetHour(), v.GetMinute(), v.GetSecond()); + return String(buf); + } + return ""; + } +} + +#ifdef WIN32 +extern uint64 __Win32FileTimeToMuscleTime(const FILETIME & ft); // from SetupSystem.cpp +#endif + +uint64 ParseHumanReadableTimeString(const String & s, uint32 timeType) +{ + TCHECKPOINT; + + if (s.IndexOfIgnoreCase("never") >= 0) return MUSCLE_TIME_NEVER; + + StringTokenizer tok(s(), "/: "); + const char * year = tok(); + const char * month = tok(); + const char * day = tok(); + const char * hour = tok(); + const char * minute = tok(); + const char * second = tok(); + +#if defined(WIN32) && defined(WINXP) + SYSTEMTIME st; memset(&st, 0, sizeof(st)); + st.wYear = (WORD) (year ? atoi(year) : 0); + st.wMonth = (WORD) (month ? atoi(month) : 0); + st.wDay = (WORD) (day ? atoi(day) : 0); + st.wHour = (WORD) (hour ? atoi(hour) : 0); + st.wMinute = (WORD) (minute ? atoi(minute) : 0); + st.wSecond = (WORD) (second ? atoi(second) : 0); + + if (timeType == MUSCLE_TIMEZONE_UTC) + { + TIME_ZONE_INFORMATION tzi; + if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) (void) MUSCLE_TzSpecificLocalTimeToSystemTime(&tzi, &st); + } + + FILETIME fileTime; + return (SystemTimeToFileTime(&st, &fileTime)) ? __Win32FileTimeToMuscleTime(fileTime) : 0; +#else + struct tm st; memset(&st, 0, sizeof(st)); + st.tm_sec = second ? atoi(second) : 0; + st.tm_min = minute ? atoi(minute) : 0; + st.tm_hour = hour ? atoi(hour) : 0; + st.tm_mday = day ? atoi(day) : 0; + st.tm_mon = month ? atoi(month)-1 : 0; + st.tm_year = year ? atoi(year)-1900 : 0; + time_t timeS = mktime(&st); + if (timeType == MUSCLE_TIMEZONE_LOCAL) + { + struct tm ltm; + struct tm * t = muscle_gmtime_r(&timeS, <m); + if (t) timeS += (timeS-mktime(t)); + } + return SecondsToMicros(timeS); +#endif +} + +enum { + TIME_UNIT_MICROSECOND, + TIME_UNIT_MILLISECOND, + TIME_UNIT_SECOND, + TIME_UNIT_MINUTE, + TIME_UNIT_HOUR, + TIME_UNIT_DAY, + TIME_UNIT_WEEK, + TIME_UNIT_MONTH, + TIME_UNIT_YEAR, + NUM_TIME_UNITS +}; + +static const uint64 MICROS_PER_DAY = DaysToMicros(1); + +static const uint64 _timeUnits[NUM_TIME_UNITS] = { + 1, // micros -> micros + 1000, // millis -> micros + MICROS_PER_SECOND, // secs -> micros + 60*MICROS_PER_SECOND, // mins -> micros + 60*60*MICROS_PER_SECOND, // hours -> micros + MICROS_PER_DAY, // days -> micros + 7*MICROS_PER_DAY, // weeks -> micros + 30*MICROS_PER_DAY, // months -> micros (well, sort of -- we assume a month is always 30 days, which isn't really true) + 365*MICROS_PER_DAY // years -> micros (well, sort of -- we assume a years is always 365 days, which isn't really true) +}; +static const char * _timeUnitNames[NUM_TIME_UNITS] = { + "microsecond", + "millisecond", + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", +}; + +static bool IsFloatingPointNumber(const char * d) +{ + while(1) + { + if (*d == '.') return true; + else if (isdigit(*d) == false) return false; + else d++; + } +} + +uint64 ParseHumanReadableTimeIntervalString(const String & s) +{ + if ((s.EqualsIgnoreCase("forever"))||(s.EqualsIgnoreCase("never"))||(s.StartsWithIgnoreCase("inf"))) return MUSCLE_TIME_NEVER; + + /** Find first digit */ + const char * d = s(); + while((*d)&&(isdigit(*d) == false)) d++; + if (*d == '\0') return 0; + + /** Find first letter */ + const char * l = s(); + while((*l)&&(isalpha(*l) == false)) l++; + if (*l == '\0') l = "s"; // default to seconds + + uint64 multiplier = _timeUnits[TIME_UNIT_SECOND]; // default units is seconds + String tmp(l); tmp = tmp.ToLowerCase(); + if ((tmp.StartsWith("us"))||(tmp.StartsWith("micro"))) multiplier = _timeUnits[TIME_UNIT_MICROSECOND]; + else if ((tmp.StartsWith("ms"))||(tmp.StartsWith("milli"))) multiplier = _timeUnits[TIME_UNIT_MILLISECOND]; + else if (tmp.StartsWith("mo")) multiplier = _timeUnits[TIME_UNIT_MONTH]; + else if (tmp.StartsWith("s")) multiplier = _timeUnits[TIME_UNIT_SECOND]; + else if (tmp.StartsWith("m")) multiplier = _timeUnits[TIME_UNIT_MINUTE]; + else if (tmp.StartsWith("h")) multiplier = _timeUnits[TIME_UNIT_HOUR]; + else if (tmp.StartsWith("d")) multiplier = _timeUnits[TIME_UNIT_DAY]; + else if (tmp.StartsWith("w")) multiplier = _timeUnits[TIME_UNIT_WEEK]; + else if (tmp.StartsWith("y")) multiplier = _timeUnits[TIME_UNIT_YEAR]; + + const char * afterLetters = l; + while((*afterLetters)&&((*afterLetters==',')||(isalpha(*afterLetters)||(isspace(*afterLetters))))) afterLetters++; + + uint64 ret = IsFloatingPointNumber(d) ? (uint64)(atof(d)*multiplier) : (Atoull(d)*multiplier); + if (*afterLetters) ret += ParseHumanReadableTimeIntervalString(afterLetters); + return ret; +} + +String GetHumanReadableTimeIntervalString(uint64 intervalUS, uint32 maxClauses, uint64 minPrecision, bool * optRetIsAccurate) +{ + if (intervalUS == MUSCLE_TIME_NEVER) return "forever"; + + // Find the largest unit that is still smaller than (micros) + uint32 whichUnit = TIME_UNIT_MICROSECOND; + for (uint32 i=0; i= NUM_TIME_UNITS)||((whichUnit > 0)&&(_timeUnits[whichUnit] > intervalUS))) whichUnit--; + + uint64 numUnits = intervalUS/_timeUnits[whichUnit]; + char buf[256]; sprintf(buf, UINT64_FORMAT_SPEC " %s%s", numUnits, _timeUnitNames[whichUnit], (numUnits==1)?"":"s"); + String ret = buf; + + uint64 leftover = intervalUS%_timeUnits[whichUnit]; + if (leftover > 0) + { + if ((leftover > minPrecision)&&(maxClauses > 1)) ret += GetHumanReadableTimeIntervalString(leftover, maxClauses-1, minPrecision, optRetIsAccurate).Prepend(", "); + else if (optRetIsAccurate) *optRetIsAccurate = false; + } + else if (optRetIsAccurate) *optRetIsAccurate = true; + + return ret; +} + +#ifndef MUSCLE_INLINE_LOGGING + +extern uint32 GetAndClearFailedMemoryRequestSize(); + +void WarnOutOfMemory(const char * file, int line) +{ + // Yes, this technique is open to race conditions and other lossage. + // But it will work in the one-error-only case, which is good enough + // for now. + LogTime(MUSCLE_LOG_CRITICALERROR, "ERROR--OUT OF MEMORY! (" INT32_FORMAT_SPEC " bytes at %s:%i)\n", GetAndClearFailedMemoryRequestSize(), file, line); +} + +#endif + +}; // end namespace muscle diff --git a/syslog/SysLog.h b/syslog/SysLog.h new file mode 100644 index 00000000..0f7c9594 --- /dev/null +++ b/syslog/SysLog.h @@ -0,0 +1,571 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.TXT file for details. */ + +#ifndef MuscleSysLog_h +#define MuscleSysLog_h + +#include "support/MuscleSupport.h" + +#ifdef MUSCLE_MINIMALIST_LOGGING +# include +#endif + +namespace muscle { + +class String; +class LogCallbackArgs; + +/** log level constants to use with SetLogLevel(), GetLogLevel() */ +enum +{ + MUSCLE_LOG_NONE = 0, // nothing ever gets logged at this level (default) + MUSCLE_LOG_CRITICALERROR, // only things that should never ever happen + MUSCLE_LOG_ERROR, // things that shouldn't usually happen + MUSCLE_LOG_WARNING, // things that are suspicious + MUSCLE_LOG_INFO, // things that the user might like to know + MUSCLE_LOG_DEBUG, // things the programmer is debugging with + MUSCLE_LOG_TRACE, // exhaustively detailed output + NUM_MUSCLE_LOGLEVELS +}; + +// Define this constant in your Makefile (i.e. -DMUSCLE_DISABLE_LOGGING) to turn all the +// Log commands into no-ops. +#ifdef MUSCLE_DISABLE_LOGGING +# define MUSCLE_INLINE_LOGGING + +// No-op implementation of Log() +static inline status_t Log(int, const char * , ...) {return B_NO_ERROR;} + +// No-op implementation of LogTime() +static inline status_t LogTime(int, const char *, ...) {return B_NO_ERROR;} + +// No-op implementation of WarnOutOfMemory() +static inline void WarnOutOfMemory(const char *, int) {/* empty */} + +// No-op implementation of LogFlush() +static inline status_t LogFlush() {return B_NO_ERROR;} + +// No-op implementation of LogStackTrace() +static inline status_t LogStackTrace(int level = MUSCLE_LOG_INFO, uint32 maxLevel=64) {(void) level; (void) maxLevel; return B_NO_ERROR;} + +// No-op implementation of PrintStackTrace() +static inline status_t PrintStackTrace(FILE * fpOut = NULL, uint32 maxLevel=64) {(void) fpOut; (void) maxLevel; return B_NO_ERROR;} + +// No-op implementation of GetStackTracke(), just return B_NO_ERROR +static inline status_t GetStackTrace(String & retStr, uint32 maxDepth = 64) {(void) retStr; (void) maxDepth; return B_NO_ERROR;} + +// No-op version of GetLogLevelName(), just returns a dummy string +static inline const char * GetLogLevelName(int /*logLevel*/) {return "";} + +// No-op version of GetLogLevelKeyword(), just returns a dummy string +static inline const char * GetLogLevelKeyword(int /*logLevel*/) {return "";} +#else + +// Define this constant in your Makefile (i.e. -DMUSCLE_MINIMALIST_LOGGING) if you don't want to have +// to link in SysLog.cpp and Hashtable.cpp and all the other stuff that is required for "real" logging. +# ifdef MUSCLE_MINIMALIST_LOGGING +# define MUSCLE_INLINE_LOGGING + +// Minimalist version of Log(), just sends the output to stdout. +static inline status_t Log(int, const char * fmt, ...) {va_list va; va_start(va, fmt); vprintf(fmt, va); va_end(va); return B_NO_ERROR;} + +// Minimalist version of LogTime(), just sends a tiny header and the output to stdout. +static inline status_t LogTime(int logLevel, const char * fmt, ...) {printf("%i: ", logLevel); va_list va; va_start(va, fmt); vprintf(fmt, va); va_end(va); return B_NO_ERROR;} + +// Minimalist version of WarnOutOfMemory() +static inline void WarnOutOfMemory(const char * file, int line) {printf("ERROR--OUT OF MEMORY! (%s:%i)\n", file, line);} + +// Minimumist version of LogFlush(), just flushes stdout +static inline status_t LogFlush() {fflush(stdout); return B_NO_ERROR;} + +// Minimalist version of LogStackTrace(), just prints a dummy string +static inline status_t LogStackTrace(int level = MUSCLE_LOG_INFO, uint32 maxDepth = 64) {(void) level; (void) maxDepth; printf("\n"); return B_NO_ERROR;} + +// Minimalist version of PrintStackTrace(), just prints a dummy string +static inline status_t PrintStackTrace(FILE * optFile = NULL, uint32 maxDepth = 64) {(void) maxDepth; fprintf(optFile?optFile:stdout, "\n"); return B_NO_ERROR;} + +// Minimalist version of GetStackTracke(), just returns B_NO_ERROR +static inline status_t GetStackTrace(String & /*retStr*/, uint32 maxDepth = 64) {(void) maxDepth; return B_NO_ERROR;} + +// Minimalist version of GetLogLevelName(), just returns a dummy string +static inline const char * GetLogLevelName(int /*logLevel*/) {return "";} + +// Minimalist version of GetLogLevelKeyword(), just returns a dummy string +static inline const char * GetLogLevelKeyword(int /*logLevel*/) {return "";} +# endif +#endif + +#ifdef MUSCLE_INLINE_LOGGING +inline int ParseLogLevelKeyword(const char *) {return MUSCLE_LOG_NONE;} +inline int GetFileLogLevel() {return MUSCLE_LOG_NONE;} +// Note: GetFileLogName() is not defined here for the inline-logging case, because it causes chicken-and-egg header problems +//inline String GetFileLogName() {return "";} +inline uint32 GetFileLogMaximumSize() {return MUSCLE_NO_LIMIT;} +inline uint32 GetMaxNumLogFiles() {return MUSCLE_NO_LIMIT;} +inline bool GetFileLogCompressionEnabled() {return false;} +inline int GetConsoleLogLevel() {return MUSCLE_LOG_NONE;} +inline int GetMaxLogLevel() {return MUSCLE_LOG_NONE;} +inline status_t SetFileLogLevel(int) {return B_NO_ERROR;} +inline status_t SetFileLogName(const String &) {return B_NO_ERROR;} +inline status_t SetFileLogMaximumSize(uint32) {return B_NO_ERROR;} +inline status_t SetOldLogFilesPattern(const String &) {return B_NO_ERROR;} +inline status_t SetMaxNumLogFiles(uint32) {return B_NO_ERROR;} +inline status_t SetFileLogCompressionEnabled(bool) {return B_NO_ERROR;} +inline status_t SetConsoleLogLevel(int) {return B_NO_ERROR;} +inline void CloseCurrentLogFile() {/* empty */} +#else + +/** Returns the MUSCLE_LOG_* equivalent of the given keyword string + * @param keyword a string such as "debug", "log", or "info". + * @return A MUSCLE_LOG_* value + */ +int ParseLogLevelKeyword(const char * keyword); + +/** Returns the current log level for logging to a file. + * @return a MUSCLE_LOG_* value. + */ +int GetFileLogLevel(); + +/** Returns the user-specified name for the file to log to. + * (note that this may be different from the log file name actually used, + * since the logging mechanism will choose a name when the log is first opened + * if no manually specified name was chosen) + * @return a file name or file path representing where the user would like the + * log file to be written. + */ +String GetFileLogName(); + +/** Returns the maximum size (in bytes) that we will allow the log file(s) to grow to. + * Default is MUSCLE_NO_LIMIT, i.e. unlimited file size. + */ +uint32 GetFileLogMaximumSize(); + +/** Returns the maximum number of log files that should be written out before + * old log files start to be deleted. Defaults to MUSCLE_NO_LIMIT, i.e. + * never delete any log files. + */ +uint32 GetMaxNumLogFiles(); + +/** Returns true if log files are to be gzip-compressed when they are closed; + * or false if they should be left in raw text form. Default value is false. + */ +bool GetFileLogCompressionEnabled(); + +/** Returns the current log level for logging to stdout. + * @return a MUSCLE_LOG_* value. + */ +int GetConsoleLogLevel(); + +/** Returns the max of GetFileLogLevel() and GetConsoleLogLevel() + * @return a MUSCLE_LOG_* value + */ +int GetMaxLogLevel(); + +/** Sets the log filter level for logging to a file. + * Any calls to Log*() that specify a log level greater than (loglevel) + * will be suppressed. Default level is MUSCLE_LOG_NONE (i.e. no file logging is done) + * @param loglevel The MUSCLE_LOG_* value to use in determining which log messages to save to disk. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetFileLogLevel(int loglevel); + +/** Forces the file logger to close any log file that it currently has open. */ +void CloseCurrentLogFile(); + +/** Sets a user-specified name/path for the log file. This name will + * be used whenever a log file is to be opened, instead of the default log file name. + * @param logName The string to use, or "" if you'd prefer a log file name be automatically generated. + * Note that this string can contain any of the special tokens described by the + * HumanReadableTimeValues::ExpandTokens() method, and these values will be expanded + * out when the log file is opened. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetFileLogName(const String & logName); + +/** Sets a user-specified maximum size for the log file. Once a log file has reached this size, + * it will be closed and a new log file opened (note that the new log file's name will be the same + * as the old log file, overwriting it, unless you specify a date/time token via SetFileLogName() + * that will expand out differently). + * Default state is no limit on log file size. (aka MUSCLE_NO_LIMIT) + * @param maxSizeBytes The maximum allowable log file size, or MUSCLE_NO_LIMIT to allow any size log file. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetFileLogMaximumSize(uint32 maxSizeBytes); + +/** Sets the path-pattern of files that the logger is allowed to assume are old log files, and therefore + * is allowed to delete. + * @param pattern The pattern to match against (e.g. "/var/log/mylogfiles-*.txt"). The matching will + * be done immediately/synchronously inside this call. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetOldLogFilesPattern(const String & pattern); + +/** Sets a user-specified maximum number of log files that should be written out before + * the oldest log files start to be deleted. This can help keep the filesystem + * space taken up by log files limited to a finite amount. + * Default state is no limit on the number of log files. (aka MUSCLE_NO_LIMIT) + * @param maxNumLogFiles The maximum allowable number of log files, or MUSCLE_NO_LIMIT to allow any number of log files. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetMaxNumLogFiles(uint32 maxNumLogFiles); + +/** Set this to true if you want log files to be compressed when they are closed (and given a .gz extension). + * or false if they should be left in raw text form. + * @param enable True to enable log file compression; false otherwise. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetFileLogCompressionEnabled(bool enable); + +/** Sets the log filter level for logging to stdout. + * Any calls to Log*() that specify a log level greater than (loglevel) + * will be suppressed. Default level is MUSCLE_LOG_INFO. + * @param loglevel The MUSCLE_LOG_* value to use in determining which log messages to print to stdout. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t SetConsoleLogLevel(int loglevel); + +/** Same semantics as printf, only outputs to the log file/console instead + * @param logLevel a MUSCLE_LOG_* value indicating the "severity" of this message. + * @param fmt A printf-style format string (e.g. "hello %s\n"). Note that \n is NOT added for you. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t Log(int logLevel, const char * fmt, ...); + +/** Calls LogTime() with a critical "OUT OF MEMORY" Message. + * Note that you typically wouldn't call this function directly; + * rather you should call the WARN_OUT_OF_MEMORY macro and it will + * call WarnOutOfMemory() for you, with the correct arguments. + * @param file Name of the source file where the memory failure occurred. + * @param line Line number where the memory failure occurred. + */ +void WarnOutOfMemory(const char * file, int line); + +#ifdef MUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME +# if defined(_MSC_VER) +# define LogTime(logLevel, ...) _LogTime(__FILE__, __FUNCTION__, __LINE__, logLevel, __VA_ARGS__) +# elif defined(__GNUC__) +# define LogTime(logLevel, args...) _LogTime(__FILE__, __FUNCTION__, __LINE__, logLevel, args) +# else +# define LogTime(logLevel, args...) _LogTime(__FILE__, "", __LINE__, logLevel, args) +# endif +status_t _LogTime(const char * sourceFile, const char * optSourceFunction, int line, int logLevel, const char * fmt, ...); +#else + +/** Formatted. Automagically prepends a timestamp and status string to your string. + * e.g. LogTime(MUSCLE_LOG_INFO, "Hello %s!", "world") would generate "[I 12/18 12:11:49] Hello world!" + * @param logLevel a MUSCLE_LOG_* value indicating the "severity" of this message. + * @param fmt A printf-style format string (e.g. "hello %s\n"). Note that \n is NOT added for you. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t LogTime(int logLevel, const char * fmt, ...); + +#endif + +/** Ensures that all previously logged output is actually sent. That is, it simply + * calls fflush() on any streams that we are logging to. + * @returns B_NO_ERROR on success, or B_ERROR if the log lock couldn't be locked for some reason. + */ +status_t LogFlush(); + +/** Attempts to lock the Mutex that is used to serialize LogCallback calls. + * Typically you won't need to call this function, as it is called for you + * before any LogCallback calls are made. + * @returns B_NO_ERROR on success or B_ERROR on failure. + * @note Be sure to call UnlockLog() when you are done! + */ +status_t LockLog(); + +/** Unlocks the Mutex that is used to serialize LogCallback calls. + * Typically you won't need to call this function, as it is called for you + * after any LogCallback calls are made. The only time you need to call it + * is after you've made a call to LockLog() and are now done with your critical + * section. + * @returns B_NO_ERROR on success or B_ERROR on failure. + */ +status_t UnlockLog(); + +/** This is similar to LogStackTrace(), except that the stack trace is printed directly + * to stdout (or another file you specify) instead of via calls to Log() and LogTime(). + * This call is handy when you need to print a stack trace in situations where the log + * isn't available. + * @param optFile If non-NULL, the text will be printed to this file. If left as NULL, stdout will be used as a default. + * @param maxDepth The maximum number of levels of stack trace that we should print out. Defaults to + * 64. The absolute maximum is 256; if you specify a value higher than that, you will still get 256. + * @note This function is currently only implemented under Linux and MacOS/X Leopard; for other OS's, this function is a no-op. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t PrintStackTrace(FILE * optFile = NULL, uint32 maxDepth = 64); + +/** Logs out a stack trace, if possible. Returns B_ERROR if not. + * @note Currently only works under Linux and MacOS/X Leopard, and then only if -rdynamic is specified as a compile flag. + * @param logLevel a MUSCLE_LOG_* value indicating the "severity" of this message. + * @param maxDepth The maximum number of levels of stack trace that we should print out. Defaults to + * 64. The absolute maximum is 256; if you specify a value higher than that, you will still get 256. + * @returns B_NO_ERROR on success, or B_ERROR if a stack trace couldn't be logged because the platform doesn't support it. + */ +status_t LogStackTrace(int logLevel = MUSCLE_LOG_INFO, uint32 maxDepth = 64); + +/** Similar to LogStackTrace(), except that the current stack trace is returned as a String + * instead of being printed out anywhere. + * @param retStr On success, the stack trace is written to this String object. + * @param maxDepth The maximum number of levels of stack trace that we should print out. Defaults to + * 64. The absolute maximum is 256; if you specify a value higher than that, you will still get 256. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + * @note This function is currently only implemented under Linux and MacOS/X Leopard; for other OS's, this function is a no-op. + */ +status_t GetStackTrace(String & retStr, uint32 maxDepth = 64); + +/** Returns a human-readable string for the given log level. + * @param logLevel A MUSCLE_LOG_* value + * @return A pretty human-readable description string such as "Informational" or "Warnings and Errors Only" + */ +const char * GetLogLevelName(int logLevel); + +/** Returns a brief human-readable string for the given log level. + * @param logLevel A MUSCLE_LOG_* value + * @return A brief human-readable description string such as "info" or "warn" + */ +const char * GetLogLevelKeyword(int logLevel); + +/** Writes a standard text string of the format "[L mm/dd hh:mm:ss]" into (buf). + * @param buf Char buffer to write into. Should be at least 64 chars long. + * @param lca A LogCallbackArgs object containing the time stamp and severity that are appropriate to print. + */ +void GetStandardLogLinePreamble(char * buf, const LogCallbackArgs & lca); + +/** Given a source location (e.g. as provided by the information in a LogCallbackArgs object), + * returns a corresponding uint32 that represents a hash of that location. + * The source code location can be later looked up by feeding this hash value + * as a command line argument into the muscle/tests/findsourcecodelocations program. + */ +uint32 GenerateSourceCodeLocationKey(const char * fileName, uint32 lineNumber); + +/** Given a source-code location key (as returned by GenerateSourceCodeLocationKey()), + * returns the standard human-readable representation of that value. (e.g. "7QF2") + */ +String SourceCodeLocationKeyToString(uint32 key); + +/** Given a standard human-readable representation of a source-code-location + * key (e.g. "7EF2"), returns the uint16 key value. This is the inverse + * function of SourceCodeLocationKeyToString(). + */ +uint32 SourceCodeLocationKeyFromString(const String & s); + +#endif + +/** This class represents all the fields necessary to present a human with a human-readable time/date stamp. Objects of this class are typically populated by the GetHumanReadableTimeValues() function, below. */ +class HumanReadableTimeValues +{ +public: + /** Default constructor */ + HumanReadableTimeValues() : _year(0), _month(0), _dayOfMonth(0), _dayOfWeek(0), _hour(0), _minute(0), _second(0), _microsecond(0) {/* empty */} + + /** Explicit constructor + * @param year The year value (e.g. 2005) + * @param month The month value (January=0, February=1, etc) + * @param dayOfMonth The day within the month (ranges from 0 to 30, inclusive) + * @param dayOfWeek The day within the week (Sunday=0, Monday=1, etc) + * @param hour The hour within the day (ranges from 0 to 23, inclusive) + * @param minute The minute within the hour (ranges from 0 to 59, inclusive) + * @param second The second within the minute (ranges from 0 to 59, inclusive) + * @param microsecond The microsecond within the second (ranges from 0 to 999999, inclusive) + */ + HumanReadableTimeValues(int year, int month, int dayOfMonth, int dayOfWeek, int hour, int minute, int second, int microsecond) : _year(year), _month(month), _dayOfMonth(dayOfMonth), _dayOfWeek(dayOfWeek), _hour(hour), _minute(minute), _second(second), _microsecond(microsecond) {/* empty */} + + /** Returns the year value (e.g. 2005) */ + int GetYear() const {return _year;} + + /** Returns the month value (January=0, February=1, March=2, ..., December=11). */ + int GetMonth() const {return _month;} + + /** Returns the day-of-month value (which ranges between 0 and 30, inclusive). */ + int GetDayOfMonth() const {return _dayOfMonth;} + + /** Returns the day-of-week value (Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6). */ + int GetDayOfWeek() const {return _dayOfWeek;} + + /** Returns the hour value (which ranges between 0 and 23, inclusive). */ + int GetHour() const {return _hour;} + + /** Returns the minute value (which ranges between 0 and 59, inclusive). */ + int GetMinute() const {return _minute;} + + /** Returns the second value (which ranges between 0 and 59, inclusive). */ + int GetSecond() const {return _second;} + + /** Returns the microsecond value (which ranges between 0 and 999999, inclusive). */ + int GetMicrosecond() const {return _microsecond;} + + /** Sets the year value (e.g. 2005) */ + void SetYear(int year) {_year = year;} + + /** Sets the month value (January=0, February=1, March=2, ..., December=11). */ + void SetMonth(int month) {_month = month;} + + /** Sets the day-of-month value (which ranges between 0 and 30, inclusive). */ + void SetDayOfMonth(int dayOfMonth) {_dayOfMonth = dayOfMonth;} + + /** Sets the day-of-week value (Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6). */ + void SetDayOfWeek(int dayOfWeek) {_dayOfWeek = dayOfWeek;} + + /** Sets the hour value (which ranges between 0 and 23, inclusive). */ + void SetHour(int hour) {_hour = hour;} + + /** Sets the minute value (which ranges between 0 and 59, inclusive). */ + void SetMinute(int minute) {_minute = minute;} + + /** Sets the second value (which ranges between 0 and 59, inclusive). */ + void SetSecond(int second) {_second = second;} + + /** Sets the microsecond value (which ranges between 0 and 999999, inclusive). */ + void SetMicrosecond(int microsecond) {_microsecond = microsecond;} + + /** Equality operator. */ + bool operator == (const HumanReadableTimeValues & rhs) const + { + return ((_year == rhs._year)&& + (_month == rhs._month)&& + (_dayOfMonth == rhs._dayOfMonth)&& + (_dayOfWeek == rhs._dayOfWeek)&& + (_hour == rhs._hour)&& + (_minute == rhs._minute)&& + (_second == rhs._second)&& + (_microsecond == rhs._microsecond)); + } + + /** Inequality operator */ + bool operator != (const HumanReadableTimeValues & rhs) const {return !(*this==rhs);} + + /** This method will expand the following tokens in the specified String out to the following values: + * %Y -> Current year (e.g. "2005") + * %M -> Current month (e.g. "01" for January, up to "12" for December) + * %Q -> Current month as a string (e.g. "January", "February", "March", etc) + * %D -> Current day of the month (e.g. "01" through "31") + * %d -> Current day of the month (e.g. "01" through "31") (synonym for %D) + * %W -> Current day of the week (e.g. "1" through "7") + * %w -> Current day of the week (e.g. "1" through "7") (synonym for %W) + * %q -> Current day of the week as a string (e.g. "Sunday", "Monday", "Tuesday", etc) + * %h -> Current hour (military style: e.g. "00" through "23") + * %m -> Current minute (e.g. "00" through "59") + * %s -> Current second (e.g. "00" through "59") + * %x -> Current microsecond (e.g. "000000" through "999999", inclusive) + * %r -> A random number between 0 and (2^64-1) (for spicing up the uniqueness of a filename) + * %T -> A human-readable time/date stamp, for convenience (e.g. "January 01 2005 23:59:59") + * %t -> A numeric time/date stamp, for convenience (e.g. "2005/01/01 15:23:59") + * %f -> A filename-friendly numeric time/date stamp, for convenience (e.g. "2005-01-01_15h23m59") + * %% -> A single percent sign. + * @param s The string to expand the tokens of + * @returns The same string, except with any and all of the above tokens expanded as described. + */ + String ExpandTokens(const String & s) const; + + /** Returns a human-readable string showing the contents of this HumanReadableTimeValues object */ + String ToString() const; + +private: + int _year; + int _month; + int _dayOfMonth; + int _dayOfWeek; + int _hour; + int _minute; + int _second; + int _microsecond; +}; + +/** When passing a uint64 as a time value, these tags help indicate what sort of time value it is. */ +enum { + MUSCLE_TIMEZONE_UTC = 0, // Universal Co-ordinated Time (formerly Greenwhich Mean Time) + MUSCLE_TIMEZONE_LOCAL // Host machine's local time (depends on local time zone settings) +}; + +/** Given a uint64 representing a time in microseconds since 1970, + * (e.g. as returned by GetCurrentTime64()), returns the same value + * as a set of more human-friendly units. + * + * @param timeUS a time in microseconds since 1970. Note that the interpretation of this value depends on + * the value passed in to the (timeType) argument. + * @param retValues On success, this object will be filled out with the various human-readable time/date value fields + * that human beings like to read. See the HumanReadableTimeValues class documentation for details. + * @param timeType If set to MUSCLE_TIMEZONE_UTC (the default) then (timeUS) will be interpreted as being in UTC, + * and will be converted to the local time zone as part of the conversion process. If set to + * MUSCLE_TIMEZONE_LOCAL, on the other hand, then (timeUS) will be assumed to be already + * in the local time zone, and no time zone conversion will be done. + * Note that the values returned are ALWAYS in reference to local time + * zone -- the (timeType) argument governs how (timeUS) should be interpreted. + * (timeType) does NOT control the meaning of the return values. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t GetHumanReadableTimeValues(uint64 timeUS, HumanReadableTimeValues & retValues, uint32 timeType = MUSCLE_TIMEZONE_UTC); + +/** This function is the inverse operation of GetHumanReadableTimeValues(). Given a HumanReadableTimeValues object, + * this function returns the corresponding microseconds-since-1970 value. + * @param values The HumanReadableTimeValues object to examine + * @param retTimeUS On success, the corresponding uint64 is written here (in microseconds-since-1970) + * @param timeType If set to MUSCLE_TIMEZONE_UTC (the default) then (values) will be interpreted as being in UTC, + * and (retTimeUS) be converted to the local time zone as part of the conversion process. If set to + * MUSCLE_TIMEZONE_LOCAL, on the other hand, then no time zone conversion will be done. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t GetTimeStampFromHumanReadableTimeValues(const HumanReadableTimeValues & values, uint64 & retTimeUS, uint32 timeType = MUSCLE_TIMEZONE_UTC); + +/** Given a uint64 representing a time in microseconds since 1970, + * (e.g. as returned by GetCurrentTime64()), returns an equivalent + * human-readable time/date string. The format of the returned + * time string is "YYYY/MM/DD HH:MM:SS". + * @param timeUS a time in microseconds since 1970. Note that the interpretation of this value depends on + * the value passed in to the (timeType) argument. + * @param timeType If set to MUSCLE_TIMEZONE_UTC (the default) then (timeUS) will be interpreted as being in UTC, + * and will be converted to the local time zone as part of the conversion process. If set to + * MUSCLE_TIMEZONE_LOCAL, on the other hand, then (timeUS) will be assumed to be already + * in the local time zone, and no time zone conversion will be done. + * @returns The equivalent ASCII string, or "" on failure. + */ +String GetHumanReadableTimeString(uint64 timeUS, uint32 timeType = MUSCLE_TIMEZONE_UTC); + +/** Does the inverse operation of GetHumanReadableTimeString(): + * Given a time string of the format "YYYY/MM/DD HH:MM:SS", + * returns the equivalent time value in microseconds since 1970. + * @param str An ASCII string representing a time. + * @param timeType If set to MUSCLE_TIMEZONE_UTC (the default) then the returned value will be UTC. + * If set to MUSCLE_TIMEZONE_LOCAL, on the other hand, then the returned value will + * be expressed as a time of the local time zone. + * @returns The equivalent time value, or zero on failure. + */ +uint64 ParseHumanReadableTimeString(const String & str, uint32 timeType = MUSCLE_TIMEZONE_UTC); + +/** Given a string that represents a time interval, returns the equivalent value in microsends. + * A time interval should be expressed as a non-negative integer, optionally followed by + * any of the following suffixes: + * us = microseconds + * ms = milliseconds + * s = seconds + * m = minutes + * h = hours + * d = days + * w = weeks + * As a special case, the string "forever" will parse to MUSCLE_TIME_NEVER. + * If no suffix is supplied, the units are presumed to be in seconds. + * @param str The string to parse + * @returns a time interval value, in microseconds. + */ +uint64 ParseHumanReadableTimeIntervalString(const String & str); + +/** Given a time interval specified in microseconds, returns a human-readable + * string representing that time, e.g. "3 weeks, 2 days, 1 hour, 25 minutes, 2 seconds, 350 microseconds" + * @param micros The number of microseconds to describe + * @param maxClauses The maximum number of clauses to allow in the returned string. For example, passing this in + * as 1 might return "3 weeks", while passing this in as two might return "3 weeks, 2 days". + * Default value is MUSCLE_NO_LIMIT, indicating that no maximum should be enforced. + * @param minPrecisionMicros The maximum number of microseconds the routine is allowed to ignore + * when generating its string. For example, if this value was passed in + * as 1000000, the returned string would describe the interval down to + * the nearest second. Defaults to zero for complete accuracy. + * @param optRetIsAccurate If non-NULL, this value will be set to true if the returned string represents + * (micros) down to the nearest microsecond, or false if the string is an approximation. + * @returns a human-readable time interval description string. + */ +String GetHumanReadableTimeIntervalString(uint64 micros, uint32 maxClauses = MUSCLE_NO_LIMIT, uint64 minPrecisionMicros = 0, bool * optRetIsAccurate = NULL); + +}; // end namespace muscle + +#endif diff --git a/system/AcceptSocketsThread.cpp b/system/AcceptSocketsThread.cpp new file mode 100644 index 00000000..3856407a --- /dev/null +++ b/system/AcceptSocketsThread.cpp @@ -0,0 +1,87 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/AcceptSocketsThread.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +namespace muscle { + +AcceptSocketsThread :: AcceptSocketsThread() +{ + // empty +} + +AcceptSocketsThread :: AcceptSocketsThread(uint16 port, const ip_address & optInterfaceIP) +{ + (void) SetPort(port, optInterfaceIP); +} + +AcceptSocketsThread :: ~AcceptSocketsThread() +{ + // empty +} + +status_t AcceptSocketsThread :: SetPort(uint16 port, const ip_address & optInterfaceIP) +{ + if (IsInternalThreadRunning() == false) + { + _port = 0; + _acceptSocket = CreateAcceptingSocket(port, 20, &port, optInterfaceIP); + if (_acceptSocket()) + { + _port = port; + return B_NO_ERROR; + } + } + return B_ERROR; +} + +status_t AcceptSocketsThread :: StartInternalThread() +{ + if ((IsInternalThreadRunning() == false)&&(_acceptSocket())) + { + _notifySocket = GetInternalThreadWakeupSocket(); + return (_notifySocket.GetFileDescriptor() >= 0) ? Thread::StartInternalThread() : B_ERROR; + } + return B_ERROR; +} + +void AcceptSocketsThread :: InternalThreadEntry() +{ + SocketMultiplexer multiplexer; + bool keepGoing = true; + while(keepGoing) + { + int afd = _acceptSocket.GetFileDescriptor(); + int nfd = _notifySocket.GetFileDescriptor(); + + multiplexer.RegisterSocketForReadReady(afd); + multiplexer.RegisterSocketForReadReady(nfd); + if (multiplexer.WaitForEvents() < 0) break; + if (multiplexer.IsSocketReadyForRead(nfd)) + { + MessageRef msgRef; + int32 numLeft; + while((numLeft = WaitForNextMessageFromOwner(msgRef, 0)) >= 0) + { + if (MessageReceivedFromOwner(msgRef, numLeft) != B_NO_ERROR) + { + keepGoing = false; + break; + } + } + } + if (multiplexer.IsSocketReadyForRead(afd)) + { + ConstSocketRef newSocket = Accept(_acceptSocket); + if (newSocket()) + { + MessageRef msg(GetMessageFromPool(AST_EVENT_NEW_SOCKET_ACCEPTED)); + msg()->AddTag(AST_NAME_SOCKET, CastAwayConstFromRef(newSocket.GetRefCountableRef())); + (void) SendMessageToOwner(msg); + } + } + } +} + +}; // end namespace muscle diff --git a/system/AcceptSocketsThread.h b/system/AcceptSocketsThread.h new file mode 100644 index 00000000..05b6fb46 --- /dev/null +++ b/system/AcceptSocketsThread.h @@ -0,0 +1,67 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAcceptSocketsThread_h +#define MuscleAcceptSocketsThread_h + +#include "system/Thread.h" +#include "util/NetworkUtilityFunctions.h" + +namespace muscle { + +/** Event message codes returned by this thread */ +enum { + AST_EVENT_NEW_SOCKET_ACCEPTED = 1634956336, // 'ast0' - sent when we accept and send back a new socket + AST_LAST_EVENT +}; + +#define AST_NAME_SOCKET "socket" // field name where we store our ConstSocketRef in our reply Messages. + +/** A thread that waits for TCP connections on a given port, and when it gets one, + * it sends the socket to its owner via a ConstSocketRef. + */ +class AcceptSocketsThread : public Thread, private CountedObject +{ +public: + /** Default constructor. You'll need to call SetPort() before calling StartInternalThread(). */ + AcceptSocketsThread(); + + /** Constructor. + * @param port Port to listen on, or 0 if we should select our own port. + * If the latter, you can call GetPort() to find out which port was selected. + * @param optFrom If specified, the IP address to accept connections from. If left as zero, + * then connections will be accepted from any IP address. + */ + AcceptSocketsThread(uint16 port, const ip_address & optFrom = invalidIP); + + /** Destructor. Closes the accept socket and frees the port */ + virtual ~AcceptSocketsThread(); + + /** Overridden to grab the notify socket */ + virtual status_t StartInternalThread(); + + /** Returns the port we are (or will be) listening on, or zero if we aren't listening at all. */ + uint16 GetPort() const {return _port;} + + /** Tries to allocate a socket to listen on the given port. Will close any previously existing + * socket first. Does not work if the internal thread is already running. + * @param port Which port to allocate a socket to listen on, or zero if you wish for the system to choose. + * @param optInterfaceIP if specified, this should be the IP address of a local network interface + * to listen for incoming connections on. If left unspecified (or set to invalidIP) + * then we will accept connections on all network interfaces. + * @returns B_NO_ERROR on success, or B_ERROR on failure (port couldn't be allocated, or internal + * thread was already running) + */ + status_t SetPort(uint16 port, const ip_address & optInterfaceIP = invalidIP); + +protected: + virtual void InternalThreadEntry(); + +private: + uint16 _port; + ConstSocketRef _notifySocket; + ConstSocketRef _acceptSocket; +}; + +}; + +#endif diff --git a/system/AtomicCounter.h b/system/AtomicCounter.h new file mode 100644 index 00000000..465bce2f --- /dev/null +++ b/system/AtomicCounter.h @@ -0,0 +1,200 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleAtomicCounter_h +#define MuscleAtomicCounter_h + +#include "support/MuscleSupport.h" + +#ifndef MUSCLE_SINGLE_THREAD_ONLY +# if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) + // empty +# elif defined(__ATHEOS__) +# include +# elif defined(__BEOS__) || defined(__HAIKU__) +# include +# elif defined(WIN32) + // empty +# elif defined(__APPLE__) +# include +# elif defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) || defined(MUSCLE_USE_X86_INLINE_ASSEMBLY) + // empty +# elif defined(MUSCLE_USE_PTHREADS) || defined(ANDROID) +# define MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS +# endif +#endif + +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) +# include "system/Mutex.h" +# ifndef MUSCLE_MUTEX_POOL_SIZE +# define MUSCLE_MUTEX_POOL_SIZE 256 +# endif +#endif + +namespace muscle { + +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) +extern Mutex * _muscleAtomicMutexes; +static inline int32 DoMutexAtomicIncrement(volatile int32 * count, int32 delta) +{ + int32 ret; + if (_muscleAtomicMutexes) + { + MutexGuard mg(_muscleAtomicMutexes[(((uint32)((uintptr)count))/sizeof(int32))%MUSCLE_MUTEX_POOL_SIZE]); // double-cast for AMD64 + ret = *count = (*count + delta); + } + else + { + // if _muscleAtomicMutexes isn't allocated, then we're in process-setup or process-shutdown, so there are no multiple threads at the moment, so we can just do this + ret = *count = (*count + delta); + } + return ret; +} +#endif + +/** This is a teensy little class that works as a cross-platform atomic counter variable. + * It's been ifdef'd all to hell, so that it tries to always use the most efficient API + * possible based on the host CPU and OS. If compiled with -DMUSCLE_SINGLE_THREAD_ONLY, + * it degenerates to a regular old counter variable, which is very lightweight and portable, + * but of course will only work properly in single-threaded environments. + */ +class AtomicCounter +{ +public: + /** Default constructor. The count value is initialized to zero. */ + AtomicCounter() : _count(0) + { + // empty + } + + /** Destructor */ + ~AtomicCounter() + { + // empty + } + + /** Atomically increments our counter by one. + * Returns true iff the count's new value is 1; returns false + * if the count's new value is any other value. + */ + inline bool AtomicIncrement() + { +#if defined(MUSCLE_SINGLE_THREAD_ONLY) + return (++_count == 1); +#elif defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) + return (DoMutexAtomicIncrement(&_count, 1) == 1); +#elif defined(WIN32) + return (InterlockedIncrement(&_count) == 1); +#elif defined(__APPLE__) + return (OSAtomicIncrement32Barrier(&_count) == 1); +#elif defined(__ATHEOS__) || defined(__BEOS__) || defined(__HAIKU__) + return (atomic_add(&_count,1) == 0); // atomic_add() returns the previous value +#elif defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) + volatile int * p = &_count; + int tmp; // tmp will be set to the value after the increment + asm volatile( + "1: lwarx %0,0,%1\n" + " addic %0,%0,1\n" + " stwcx. %0,0,%1\n" + " bne- 1b" + : "=&r" (tmp) + : "r" (p) + : "cc", "memory"); + return (tmp == 1); +#elif defined(MUSCLE_USE_X86_INLINE_ASSEMBLY) + int value = 1; // the increment-by value + asm volatile( + "lock; xaddl %%eax, %2;" + :"=a" (value) // Output + : "a" (value), "m" (_count) // Input + :"memory"); + return (value==0); // at this point value contains the counter's pre-increment value +#else +# error "No atomic increment supplied for this OS! Add it here in AtomicCount.h, or put -DMUSCLE_SINGLE_THREAD_ONLY in your Makefile if you will not be using multithreading." +#endif + } + + /** Atomically decrements our counter by one. + * @returns true iff the new value of our count is 0; + * returns false if it is any other value + */ + inline bool AtomicDecrement() + { +#if defined(MUSCLE_SINGLE_THREAD_ONLY) + return (--_count == 0); +#elif defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) + return (DoMutexAtomicIncrement(&_count, -1) == 0); +#elif defined(WIN32) + return (InterlockedDecrement(&_count) == 0); +#elif defined(__APPLE__) + return (OSAtomicDecrement32Barrier(&_count) == 0); +#elif defined(__ATHEOS__) + return (atomic_add(&_count,-1)==1); +#elif defined(__BEOS__) || defined(__HAIKU__) + return (atomic_add(&_count,-1)==1); +#elif defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) + volatile int * p = &_count; + int tmp; // tmp will be set to the value after the decrement + asm volatile( + "1: lwarx %0,0,%1\n" + " addic %0,%0,-1\n" // addic allows r0, addi doesn't + " stwcx. %0,0,%1\n" + " bne- 1b" + : "=&r" (tmp) + : "r" (p) + : "cc", "memory"); + return(tmp == 0); +#elif defined(MUSCLE_USE_X86_INLINE_ASSEMBLY) + bool isZero; + volatile int * p = &_count; + asm volatile( + "lock; decl (%1)\n" + "sete %0" + : "=q" (isZero) + : "q" (p) + : "cc", "memory" + ); + return isZero; +#else +# error "No atomic decrement supplied for this OS! Add your own here in AtomicCounter.h, or put -DMUSCLE_SINGLE_THREAD_ONLY in your Makefile if you will not be using multithreading." +#endif + } + + /** Returns the current value of this counter. + * Be careful when using this function in multithreaded + * environments, it can easily lead to race conditions + * if you don't know what you are doing! + */ + int32 GetCount() const {return (int32) _count;} + + /** Sets the current value of this counter. + * Be careful when using this function in multithreaded + * environments, it can easily lead to race conditions + * if you don't know what you are doing! + */ + void SetCount(int32 c) {_count = c;} + +private: +#if defined(MUSCLE_SINGLE_THREAD_ONLY) + int32 _count; +#elif defined(__ATHEOS__) + atomic_t _count; +#elif defined(WIN32) + long _count; +#elif defined(__APPLE__) + volatile int32_t _count; +#elif defined(__BEOS__) || defined(__HAIKU__) +# if defined(B_BEOS_VERSION_5) + vint32 _count; +# else + int32 _count; +# endif +#elif defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) || defined(MUSCLE_USE_X86_INLINE_ASSEMBLY) + volatile int _count; +#else + volatile int32 _count; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/system/DetectNetworkConfigChangesSession.cpp b/system/DetectNetworkConfigChangesSession.cpp new file mode 100644 index 00000000..cbf1a973 --- /dev/null +++ b/system/DetectNetworkConfigChangesSession.cpp @@ -0,0 +1,521 @@ +#include "iogateway/SignalMessageIOGateway.h" + +#define FORWARD_DECLARE_SIGNAL_INTERFACES_CHANGED +#include "system/DetectNetworkConfigChangesSession.h" +#undef FORWARD_DECLARE_SIGNAL_INTERFACES_CHANGED + +#ifdef __APPLE__ +# include +#elif WIN32 +# if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) && !defined(MUSCLE_AVOID_NETIOAPI) + // If we're compiling pre-Vista, the NetIOApi isn't available +# define MUSCLE_AVOID_NETIOAPI +# endif +# ifndef MUSCLE_AVOID_NETIOAPI +# include +# include "util/NetworkUtilityFunctions.h" // for GetNetworkInterfaceInfos() +# endif +# include +# define MY_INVALID_HANDLE_VALUE ((::HANDLE)(-1)) // bloody hell... +#endif + +#ifdef __linux__ +# include +# include +# include +# include +#endif + +namespace muscle { + +#if defined(__APPLE__) || defined(WIN32) +static void SignalInterfacesChanged(DetectNetworkConfigChangesSession * s, const Hashtable & optInterfaceNames) +{ + MessageRef msg; // demand-allocated + if (optInterfaceNames.HasItems()) + { + msg = GetMessageFromPool(); + if (msg()) for (HashtableIterator iter(optInterfaceNames); iter.HasData(); iter++) msg()->AddString("if", iter.GetKey()); + } + +#ifdef WIN32 + // Needed because Windows notification callbacks get called from various threads + MutexGuard mg(s->_sendMessageToOwnerMutex); +#endif + s->SendMessageToOwner(msg); +} +#endif + +DetectNetworkConfigChangesSession :: DetectNetworkConfigChangesSession() : +#ifndef __linux__ + _threadKeepGoing(false), +#endif +#ifdef __APPLE__ + _threadRunLoop(NULL), // paranoia +#elif WIN32 + _wakeupSignal(MY_INVALID_HANDLE_VALUE), +#endif + _explicitDelayMicros(MUSCLE_TIME_NEVER), + _callbackTime(MUSCLE_TIME_NEVER), + _enabled(true), + _changeAllPending(false) +{ + // empty +} + +void DetectNetworkConfigChangesSession :: ScheduleSendReport() +{ + // We won't actually send the report for a certain number of + // seconds (OS-specific); that way any additional changes the OS + // is making to the network config will have time to be reported + // and we (hopefully) won't end up sending multiple reports in a row. +#ifdef WIN32 + const int hysteresisDelaySeconds = 5; // Windows needs 5, it is lame +#else + const int hysteresisDelaySeconds = 3; // MacOS/X needs about 3 seconds +#endif + _callbackTime = GetRunTime64() + ((_explicitDelayMicros == MUSCLE_TIME_NEVER) ? SecondsToMicros(hysteresisDelaySeconds) : _explicitDelayMicros); + InvalidatePulseTime(); +} + +void DetectNetworkConfigChangesSession :: NetworkInterfacesChanged(const Hashtable &) +{ + // default implementation is a no-op. +} + +void DetectNetworkConfigChangesSession :: Pulse(const PulseArgs & pa) +{ + if (pa.GetCallbackTime() >= _callbackTime) + { + _callbackTime = MUSCLE_TIME_NEVER; + if (_enabled) NetworkInterfacesChanged(_changeAllPending ? Hashtable() : _pendingChangedInterfaceNames); + _pendingChangedInterfaceNames.Clear(); + _changeAllPending = false; + } + AbstractReflectSession::Pulse(pa); +} + +ConstSocketRef DetectNetworkConfigChangesSession :: CreateDefaultSocket() +{ +#ifdef __linux__ + struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR; + + ConstSocketRef ret = GetConstSocketRefFromPool(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + return ((ret())&&(bind(ret()->GetFileDescriptor(), (struct sockaddr*)&sa, sizeof(sa)) == 0)&&(SetSocketBlockingEnabled(ret, false) == B_NO_ERROR)) ? ret : ConstSocketRef(); +#else + return GetOwnerWakeupSocket(); +#endif +} + +void DetectNetworkConfigChangesSession :: MessageReceivedFromGateway(const MessageRef & /*msg*/, void * /*ptr*/) +{ +#ifndef __linux__ + bool sendReport = false; + MessageRef ref; + while(GetNextReplyFromInternalThread(ref) >= 0) + { + sendReport = true; // we only need to send one report, even for multiple Messages + if ((ref())&&(_changeAllPending == false)) + { + if (ref()->HasName("if", B_STRING_TYPE)) + { + const String * ifName; + for (int32 i=0; ref()->FindString("if", i, &ifName) == B_NO_ERROR; i++) _pendingChangedInterfaceNames.PutWithDefault(*ifName); + } + else _changeAllPending = true; // no interfaces specified means "it could be anything" + } + } + if (sendReport) ScheduleSendReport(); +#endif +} + +#ifdef __linux__ + +int32 DetectNetworkConfigChangesSession :: DoInput(AbstractGatewayMessageReceiver & /*r*/, uint32 /*maxBytes*/) +{ + int fd = GetSessionReadSelectSocket().GetFileDescriptor(); + if (fd < 0) return -1; + + bool sendReport = false; + char buf[4096]; + struct iovec iov = {buf, sizeof(buf)}; + struct sockaddr_nl sa; + struct msghdr msg = {(void *)&sa, sizeof(sa), &iov, 1, NULL, 0, 0 }; + int len = recvmsg(fd, &msg, 0); + if (len >= 0) // FogBugz #9620 + { + for (struct nlmsghdr *nh = (struct nlmsghdr *)buf; ((sendReport == false)&&(NLMSG_OK(nh, (unsigned int)len))); nh=NLMSG_NEXT(nh, len)) + { + /* The end of multipart message. */ + if (nh->nlmsg_type == NLMSG_DONE) break; + else + { + switch(nh->nlmsg_type) + { + case RTM_NEWLINK: case RTM_DELLINK: case RTM_NEWADDR: case RTM_DELADDR: + { + struct ifinfomsg * iface = (struct ifinfomsg *) NLMSG_DATA(nh); + int len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface)); + for (struct rtattr * a = IFLA_RTA(iface); RTA_OK(a, len); a = RTA_NEXT(a, len)) + if (a->rta_type == IFLA_IFNAME) + _pendingChangedInterfaceNames.PutWithDefault((const char *) RTA_DATA(a)); + sendReport = true; + } + break; + + default: + // do nothing + break; + } + } + } + } + if (sendReport) ScheduleSendReport(); + return len; +} + +#else + +status_t DetectNetworkConfigChangesSession :: AttachedToServer() +{ + _threadKeepGoing = true; +# ifdef __APPLE__ + _threadRunLoop = NULL; +# endif +# ifdef WIN32 + _wakeupSignal = CreateEvent(0, false, false, 0); + if (_wakeupSignal == MY_INVALID_HANDLE_VALUE) return B_ERROR; +# endif + return (AbstractReflectSession::AttachedToServer() == B_NO_ERROR) ? StartInternalThread() : B_ERROR; +} + +void DetectNetworkConfigChangesSession :: EndSession() +{ + ShutdownInternalThread(); // do this ASAP, otherwise we get the occasional crash on shutdown :( + AbstractReflectSession::EndSession(); +} + +void DetectNetworkConfigChangesSession :: AboutToDetachFromServer() +{ + ShutdownInternalThread(); +#ifdef WIN32 + if (_wakeupSignal != MY_INVALID_HANDLE_VALUE) + { + CloseHandle(_wakeupSignal); + _wakeupSignal = MY_INVALID_HANDLE_VALUE; + } +#endif + AbstractReflectSession::AboutToDetachFromServer(); +} + +AbstractMessageIOGatewayRef DetectNetworkConfigChangesSession :: CreateGateway() +{ + AbstractMessageIOGateway * gw = newnothrow SignalMessageIOGateway; + if (gw == NULL) WARN_OUT_OF_MEMORY; + return AbstractMessageIOGatewayRef(gw); +} + +void DetectNetworkConfigChangesSession :: SignalInternalThread() +{ + _threadKeepGoing = false; + Thread::SignalInternalThread(); +# ifdef __APPLE__ + if (_threadRunLoop) CFRunLoopStop(_threadRunLoop); +# elif WIN32 + SetEvent(_wakeupSignal); +# endif +} + +# ifdef __APPLE__ + +// MacOS/X Code taken from http://developer.apple.com/technotes/tn/tn1145.html +static OSStatus MoreSCErrorBoolean(Boolean success) +{ + OSStatus err = noErr; + if (!success) + { + int scErr = SCError(); + if (scErr == kSCStatusOK) scErr = kSCStatusFailed; + err = scErr; + } + return err; +} + +static OSStatus MoreSCError(const void *value) {return MoreSCErrorBoolean(value != NULL);} +static OSStatus CFQError(CFTypeRef cf) {return (cf == NULL) ? -1 : noErr;} +static void CFQRelease(CFTypeRef cf) {if (cf != NULL) CFRelease(cf);} + +static void StoreRecordFunc(const void * key, const void * value, void * context) +{ + const CFStringRef keyStr = (const CFStringRef) key; + const CFDictionaryRef propList = (const CFDictionaryRef) value; + if ((keyStr)&&(propList)) + { + String k = CFStringGetCStringPtr(keyStr, kCFStringEncodingMacRoman); + if (k.StartsWith("State:/Network/Interface/")) + { + String interfaceName = k.Substring(25).Substring(0, "/"); + (void) ((Hashtable *)(context))->Put(k, interfaceName); + } + else + { + const CFStringRef interfaceNameRef = (const CFStringRef) CFDictionaryGetValue((CFDictionaryRef) propList, CFSTR("InterfaceName")); + if (interfaceNameRef) + { + String v = CFStringGetCStringPtr(interfaceNameRef, kCFStringEncodingMacRoman); + (void) ((Hashtable *)(context))->Put(k, v); + } + } + } +} + +// Create a SCF dynamic store reference and a corresponding CFRunLoop source. If you add the +// run loop source to your run loop then the supplied callback function will be called when local IP +// address list changes. +static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback, void *contextPtr, SCDynamicStoreRef * storeRef, CFRunLoopSourceRef *sourceRef, Hashtable & keyToInterfaceName) +{ + OSStatus err; + SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL}; + SCDynamicStoreRef ref = NULL; + CFStringRef patterns[3] = {NULL, NULL, NULL}; + CFArrayRef patternList = NULL; + CFRunLoopSourceRef rls = NULL; + + assert(callback != NULL); + assert( storeRef != NULL); + assert(*storeRef == NULL); + assert( sourceRef != NULL); + assert(*sourceRef == NULL); + + // Create a connection to the dynamic store, then create + // a search pattern that finds all entities. + context.info = contextPtr; + ref = SCDynamicStoreCreate(NULL, CFSTR("AddIPAddressListChangeCallbackSCF"), callback, &context); + err = MoreSCError(ref); + if (err == noErr) + { + // This pattern is "State:/Network/Service/[^/]+/IPv4". + patterns[0] = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); // FogBugz #6075 + err = MoreSCError(patterns[0]); + if (err == noErr) + { + // This pattern is "State:/Network/Service/[^/]+/IPv6". + patterns[1] = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); // FogBugz #6075 + err = MoreSCError(patterns[1]); + if (err == noErr) + { + // This pattern is "State:/Network/Interface/[^/]+/Link" + patterns[2] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetLink); // FogBugz #10048 + err = MoreSCError(patterns[2]); + } + } + } + + // Tell SCF that we want to watch changes in keys that match that pattern list, then create our run loop source. + if (err == noErr) + { + patternList = CFArrayCreate(NULL, (const void **) patterns, 3, &kCFTypeArrayCallBacks); + err = CFQError(patternList); + } + + // Query the current values matching our patterns, so we know what interfaces are currently operative + if (err == noErr) + { + CFDictionaryRef curVals = SCDynamicStoreCopyMultiple(ref, NULL, patternList); + if (curVals) + { + CFDictionaryApplyFunction(curVals, StoreRecordFunc, &keyToInterfaceName); + CFRelease(curVals); + } + } + + if (err == noErr) err = MoreSCErrorBoolean(SCDynamicStoreSetNotificationKeys(ref, NULL, patternList)); + if (err == noErr) + { + rls = SCDynamicStoreCreateRunLoopSource(NULL, ref, 0); + err = MoreSCError(rls); + } + + // Clean up. + CFQRelease(patterns[0]); + CFQRelease(patterns[1]); + CFQRelease(patterns[2]); + CFQRelease(patternList); + if (err != noErr) + { + CFQRelease(ref); + ref = NULL; + } + *storeRef = ref; + *sourceRef = rls; + + assert( (err == noErr) == (*storeRef != NULL) ); + assert( (err == noErr) == (*sourceRef != NULL) ); + + return err; +} + +static void IPConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void * info) +{ + DetectNetworkConfigChangesSession * s = (DetectNetworkConfigChangesSession *) info; + Hashtable changedInterfaceNames; + + int c = CFArrayGetCount(changedKeys); + for (int i=0; i & scKeyToInterfaceName = GetKeyToInterfaceNameTable(s); + if (interfaceName.HasChars()) (void) scKeyToInterfaceName.Put(keyStr, interfaceName); + else interfaceName = scKeyToInterfaceName.RemoveWithDefault(keyStr); + if (interfaceName.HasChars()) (void) changedInterfaceNames.PutWithDefault(interfaceName); + } + } + + SignalInterfacesChanged(s, changedInterfaceNames); +} + +Hashtable & GetKeyToInterfaceNameTable(DetectNetworkConfigChangesSession * s) +{ + return s->_scKeyToInterfaceName; +} + +# endif // __APPLE__ + +#if defined(WIN32) && !defined(MUSCLE_AVOID_NETIOAPI) + +static void SignalInterfacesChangedAux(void * context, uint32 changedIdx) +{ + Hashtable iNames; + Queue q; + if (GetNetworkInterfaceInfos(q) == B_NO_ERROR) + { + for (uint32 i=0; iInterfaceIndex); +} + +VOID __stdcall InterfaceCallbackDemo(IN PVOID context, IN PMIB_IPINTERFACE_ROW interfaceRow, IN MIB_NOTIFICATION_TYPE /*NotificationType*/) +{ + if (interfaceRow != NULL) SignalInterfacesChangedAux(context, interfaceRow->InterfaceIndex); +} + +#endif + +void DetectNetworkConfigChangesSession :: InternalThreadEntry() +{ +# ifdef __APPLE__ + _threadRunLoop = CFRunLoopGetCurrent(); + + SCDynamicStoreRef storeRef = NULL; + CFRunLoopSourceRef sourceRef = NULL; + if (CreateIPAddressListChangeCallbackSCF(IPConfigChangedCallback, this, &storeRef, &sourceRef, _scKeyToInterfaceName) == noErr) + { + CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode); + while(_threadKeepGoing) + { + CFRunLoopRun(); + while(1) + { + MessageRef msgRef; + int32 numLeft = WaitForNextMessageFromOwner(msgRef, 0); + if (numLeft >= 0) + { + if (MessageReceivedFromOwner(msgRef, numLeft) != B_NO_ERROR) _threadKeepGoing = false; + } + else break; + } + } + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode); + CFRelease(storeRef); + CFRelease(sourceRef); + } +# elif WIN32 + +# ifndef MUSCLE_AVOID_NETIOAPI + HANDLE handle1 = MY_INVALID_HANDLE_VALUE; + HANDLE handle2 = MY_INVALID_HANDLE_VALUE; + (void) NotifyUnicastIpAddressChange(AF_UNSPEC, &AddressCallbackDemo, this, FALSE, &handle1); + (void) NotifyIpInterfaceChange( AF_UNSPEC, &InterfaceCallbackDemo, this, FALSE, &handle2); +#endif + + OVERLAPPED olap; memset(&olap, 0, sizeof(olap)); + olap.hEvent = CreateEvent(NULL, false, false, NULL); + if (olap.hEvent != NULL) + { + while(_threadKeepGoing) + { + ::HANDLE junk; + int nacRet = NotifyAddrChange(&junk, &olap); + if ((nacRet == NO_ERROR)||(WSAGetLastError() == WSA_IO_PENDING)) + { + ::HANDLE events[] = {olap.hEvent, _wakeupSignal}; + switch(WaitForMultipleObjects(ARRAYITEMS(events), events, false, INFINITE)) + { + case WAIT_OBJECT_0: + { + // Serialized since the NotifyUnicast*Change() callbacks + // get called from a different thread + MutexGuard mg(_sendMessageToOwnerMutex); + SendMessageToOwner(MessageRef()); + } + break; + + default: + (void) CancelIPChangeNotify(&olap); + _threadKeepGoing = false; + break; + } + } + else + { + LogTime(MUSCLE_LOG_ERROR, "DetectNetworkConfigChangesSession: NotifyAddrChange() failed, code %i (%i)\n", nacRet, WSAGetLastError()); + break; + } + } + CloseHandle(olap.hEvent); + } + else LogTime(MUSCLE_LOG_ERROR, "DetectNetworkConfigChangesSession: CreateEvent() failed\n"); + +# ifndef MUSCLE_AVOID_NETIOAPI + if (handle2 != MY_INVALID_HANDLE_VALUE) CancelMibChangeNotify2(handle2); + if (handle1 != MY_INVALID_HANDLE_VALUE) CancelMibChangeNotify2(handle1); +# endif + +# else +# error "NetworkInterfacesSession: OS not supported!" +# endif +} + +#endif // !__linux__ + +}; // end namespace muscle + + diff --git a/system/DetectNetworkConfigChangesSession.h b/system/DetectNetworkConfigChangesSession.h new file mode 100644 index 00000000..6d3e55f3 --- /dev/null +++ b/system/DetectNetworkConfigChangesSession.h @@ -0,0 +1,135 @@ +#ifndef DetectNetworkConfigChangesSession_h +#define DetectNetworkConfigChangesSession_h + +#include "reflector/AbstractReflectSession.h" + +#ifndef __linux__ +# include "system/Thread.h" // For Linux we can just listen directly on an AF_NETLINK socket, so no thread is needed +#endif + +#ifdef __APPLE__ +# include +#endif + +namespace muscle { + +#if defined(FORWARD_DECLARE_SIGNAL_INTERFACES_CHANGED) && (defined(__APPLE__) || defined(WIN32)) +class DetectNetworkConfigChangesSession; +static void SignalInterfacesChanged(DetectNetworkConfigChangesSession * s, const Hashtable & optInterfaceNames); +#endif + +/** This class watches the set of available network interfaces and calls its + * NetworkInterfacesChanged() virtual method when a network-configuration change + * has been detected. The default implementation of NetworkInterfacesChanged() is a no-op, + * so you will want to subclass this class and implement your own version of + * NetworkInterfacesChanged() that does something useful (like posting a log message, + * or tearing down and recreating any sockets that relied on the old networking config). + * + * Note that this functionality is currently implemented for Linux, Windows, and MacOS/X only. + * Note also that the Windows and MacOS/X implementations currently make use of the MUSCLE + * Thread class, and therefore won't compile if -DMUSCLE_SINGLE_THREAD_ONLY is set. + * + * @see tests/testnetconfigdetect.cpp for an example usage of this class. + */ +class DetectNetworkConfigChangesSession : public AbstractReflectSession, private CountedObject +#ifndef __linux__ + , private Thread +#endif +{ +public: + DetectNetworkConfigChangesSession(); + +#ifndef __linux__ + virtual status_t AttachedToServer(); + virtual void AboutToDetachFromServer(); + virtual void EndSession(); + virtual AbstractMessageIOGatewayRef CreateGateway(); +#endif + + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + + virtual ConstSocketRef CreateDefaultSocket(); + virtual const char * GetTypeName() const {return "DetectNetworkConfigChanges";} + + virtual uint64 GetPulseTime(const PulseArgs & args) {return muscleMin(_callbackTime, AbstractReflectSession::GetPulseTime(args));} + virtual void Pulse(const PulseArgs & args); + +#ifdef __linux__ + virtual int32 DoInput(AbstractGatewayMessageReceiver & r, uint32 maxBytes); +#endif + + /** This method can be called to disable or enable this session. + * A disabled session will not call NetworkInterfacesChanged(), even if a network interface change is detected. + * The default state of this session is enabled. + * @param e True to enable calling of NetworkInterfacesChanged() when appropriate, or false to disable it. + */ + void SetEnabled(bool e) {_enabled = e;} + + /** Returns true iff the calling of NetworkInterfaceChanged() is enabled. Default value is true. */ + bool IsEnabled() const {return _enabled;} + + /** Specified the amount of time the session should delay after receiving an indication of + * a network-config change from the OS, before calling NetworkInterfacesChanged(). + * By default this class will wait an OS-specific number of seconds (5 seconds under Windows, + * 3 seconds under Mac) before making the call, to make it more likely that the network + * interfaces are in a usable state at the moment NetworkInterfacesChanged() is called. + * However, you can specify a different delay-period here if you want to. + * @param micros Number of microseconds of delay between when a config change is detected + * and when NetworkInterfacesChanged() should be called. Set to MUSCLE_TIME_NEVER + * if you want to return to the default (OS-specific) behavior. + */ + void SetExplicitDelayMicros(uint64 micros) {_explicitDelayMicros = micros;} + + /** Returns the current delay time, or MUSCLE_TIME_NEVER if we are using the default behavior. */ + uint64 GetExplicitDelayMicros() const {return _explicitDelayMicros;} + +protected: +#ifndef __linux__ + /** Overridden to do the signalling the Carbon way */ + virtual void SignalInternalThread(); +#endif + + /** Called when a change in the local interfaces set is detected. + * Default implementation calls FindAppropriateNetworkInterfaceIndices() + * to update the process's interface list. Subclass can augment + * that behavior to include update various other objects that + * need to be notified of the change. + * @param optInterfaceNames optional table containing the names of the interfaces that + * have changed (e.g. "en0", "en1", etc). If this table is empty, + * that indicates that any or all of the network interfaces may have + * changed. Note that changed-interface enumeration is currently only + * implemented under MacOS/X, so under other operating systems this + * argument will currently always be an empty table. + */ + virtual void NetworkInterfacesChanged(const Hashtable & optInterfaceNames); + +private: + void ScheduleSendReport(); + +#ifndef __linux__ + virtual void InternalThreadEntry(); + + volatile bool _threadKeepGoing; +# ifdef __APPLE__ + friend void SignalInterfacesChanged(DetectNetworkConfigChangesSession * s, const Hashtable & optInterfaceNames); + friend Hashtable & GetKeyToInterfaceNameTable(DetectNetworkConfigChangesSession * s); + CFRunLoopRef _threadRunLoop; + Hashtable _scKeyToInterfaceName; +# elif _WIN32 + friend void SignalInterfacesChanged(DetectNetworkConfigChangesSession * s, const Hashtable & optInterfaceNames); + ::HANDLE _wakeupSignal; + Mutex _sendMessageToOwnerMutex; +# endif +#endif // __linux__ + + uint64 _explicitDelayMicros; + uint64 _callbackTime; + bool _enabled; + + Hashtable _pendingChangedInterfaceNames; // currently used under MacOS/X only + bool _changeAllPending; +}; + +}; // end namespace muscle + +#endif diff --git a/system/GlobalMemoryAllocator.cpp b/system/GlobalMemoryAllocator.cpp new file mode 100644 index 00000000..bea3d30f --- /dev/null +++ b/system/GlobalMemoryAllocator.cpp @@ -0,0 +1,373 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef NEW_H_NOT_AVAILABLE +# include +# include +#endif + +#include "system/GlobalMemoryAllocator.h" +#include "system/SetupSystem.h" // for GetGlobalMuscleLock() + +// Metrowerk's compiler crashes at run-time if this memory-tracking +// code is enabled. I haven't been able to figure out why (everything +// looks okay, but after a few dozen malloc()/free() calls, free() +// crashes!) so I'm just going to disable this code for PowerPC/Metrowerks +// machines. Sorry! --jaf 12/01/00 +#ifndef __osf__ +# ifdef __MWERKS__ +# ifdef MUSCLE_ENABLE_MEMORY_TRACKING +# undef MUSCLE_ENABLE_MEMORY_TRACKING +# undef MUSCLE_ENABLE_MEMORY_PARANOIA +# endif +# endif +#endif + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + +// VC++ doesn't know from exceptions :^P +# ifdef WIN32 +# ifndef MUSCLE_NO_EXCEPTIONS +# define MUSCLE_NO_EXCEPTIONS +# endif +# endif + +// No exceptions? Then make all of these keywords go away +# ifdef MUSCLE_NO_EXCEPTIONS +# define BAD_ALLOC +# define THROW +# define LPAREN +# define RPAREN +# else +# define BAD_ALLOC bad_alloc +# define THROW throw +# define LPAREN ( +# define RPAREN ) +# endif + +namespace muscle { + +extern void SetFailedMemoryRequestSize(uint32 numBytes); // FogBugz #7547 + +# if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + +// Functions for converting user-visible pointers (etc) to our internal implementation and back +# define MEMORY_PARANOIA_ALLOCATED_GARBAGE_VALUE (0x55) +# define MEMORY_PARANOIA_DEALLOCATED_GARBAGE_VALUE (0x66) + +static inline uint32 CONVERT_USER_TO_INTERNAL_SIZE(uint32 uNumBytes) {return (uNumBytes+(sizeof(size_t)+(2*MUSCLE_ENABLE_MEMORY_PARANOIA*sizeof(size_t *))));} +static inline uint32 CONVERT_INTERNAL_TO_USER_SIZE(uint32 iNumBytes) {return (iNumBytes-(sizeof(size_t)+(2*MUSCLE_ENABLE_MEMORY_PARANOIA*sizeof(size_t *))));} +static inline size_t * CONVERT_USER_TO_INTERNAL_POINTER(void * uptr) {return (((size_t*)uptr)-(1+MUSCLE_ENABLE_MEMORY_PARANOIA));} +static inline void * CONVERT_INTERNAL_TO_USER_POINTER(size_t * iptr) {return ((void *)(iptr+1+MUSCLE_ENABLE_MEMORY_PARANOIA));} +static inline size_t ** CONVERT_INTERNAL_TO_FRONT_GUARD(size_t * iptr) {return ((size_t **)(((size_t*)iptr)+1));} +static inline size_t ** CONVERT_INTERNAL_TO_REAR_GUARD(size_t * iptr) {return ((size_t **)((((char *)iptr)+(*iptr))-(MUSCLE_ENABLE_MEMORY_PARANOIA*sizeof(size_t *))));} + +status_t MemoryParanoiaCheckBuffer(void * userPtr, bool crashIfInvalid) +{ + if (userPtr) + { + size_t * internalPtr = CONVERT_USER_TO_INTERNAL_POINTER(userPtr); + size_t ** frontRead = CONVERT_INTERNAL_TO_FRONT_GUARD(internalPtr); + size_t ** rearRead = CONVERT_INTERNAL_TO_REAR_GUARD(internalPtr); + size_t userBufLen = CONVERT_INTERNAL_TO_USER_SIZE(*internalPtr); + + bool foundCorruption = false; + for (int i=0; i oldSize) memset(((char *)CONVERT_INTERNAL_TO_USER_POINTER(internalPtr))+oldSize, MEMORY_PARANOIA_ALLOCATED_GARBAGE_VALUE, newSize-oldSize); +} + +# else + +// Without memory paranoia, here are the conversion functions used for plain old memory usage tracking +static inline uint32 CONVERT_USER_TO_INTERNAL_SIZE(uint32 uNumBytes) {return (uNumBytes+sizeof(size_t));} +static inline uint32 CONVERT_INTERNAL_TO_USER_SIZE(uint32 iNumBytes) {return (iNumBytes-sizeof(size_t));} +static inline size_t * CONVERT_USER_TO_INTERNAL_POINTER(void * uptr) {return (((size_t*)uptr)-1);} +static inline void * CONVERT_INTERNAL_TO_USER_POINTER(size_t * iptr) {return ((void *)(iptr+1));} + +# endif + +static size_t _currentlyAllocatedBytes = 0; // Running tally of how many bytes our process has allocated + +size_t GetNumAllocatedBytes() {return _currentlyAllocatedBytes;} + +void * muscleAlloc(size_t userSize, bool retryOnFailure) +{ + using namespace muscle; + + size_t internalSize = CONVERT_USER_TO_INTERNAL_SIZE(userSize); + + void * userPtr = NULL; + MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + Mutex * glock = ma ? GetGlobalMuscleLock() : NULL; + if ((glock)&&(glock->Lock() != B_NO_ERROR)) + { + printf("Error, muscleAlloc() could not lock the global muscle lock!\n"); + SetFailedMemoryRequestSize(userSize); // FogBugz #7547 + return NULL; // serialize access to (ma) + } +#endif + + if ((ma == NULL)||(ma->AboutToAllocate(_currentlyAllocatedBytes, internalSize) == B_NO_ERROR)) + { + size_t * internalPtr = (size_t *) malloc(internalSize); + if (internalPtr) + { + *internalPtr = internalSize; // our little header tag so that muscleFree() will know how big the allocation was + _currentlyAllocatedBytes += internalSize; + +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + MemoryParanoiaPrepareBuffer(internalPtr, 0); +#endif + userPtr = CONVERT_INTERNAL_TO_USER_POINTER(internalPtr); +//printf("+"UINT32_FORMAT_SPEC" = "UINT32_FORMAT_SPEC" userPtr=%p\n", (uint32)internalSize, (uint32)_currentlyAllocatedBytes, userPtr); + } + else if (ma) ma->AboutToFree(_currentlyAllocatedBytes+internalSize, internalSize); // FogBugz #4494: roll back the call to AboutToAllocate()! + } + + if ((ma)&&(userPtr == NULL)) + { + // I call printf() instead of LogTime() to avoid any chance of an infinite recursion + printf("muscleAlloc: allocation failure (tried to allocate " UINT32_FORMAT_SPEC " internal bytes / " UINT32_FORMAT_SPEC " user bytes)\n", (uint32)internalSize, (uint32)userSize); + fflush(stdout); // make sure this message gets out! + + ma->AllocationFailed(_currentlyAllocatedBytes, internalSize); // see if ma can free spare buffers up for us + + // Maybe the AllocationFailed() method was able to free up some memory; so we'll try it one more time + // That way we might be able to recover without interrupting the operation that was in progress. + if (retryOnFailure) + { + userPtr = muscleAlloc(userSize, false); + if (userPtr == NULL) ma->SetAllocationHasFailed(true); + } + } + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + if (glock) (void) glock->Unlock(); +#endif + + if (userPtr == NULL) SetFailedMemoryRequestSize(userSize); // FogBugz #7547 + return userPtr; +} + +void * muscleRealloc(void * oldUserPtr, size_t newUserSize, bool retryOnFailure) +{ + using namespace muscle; + +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + TCHECKPOINT; + (void) MemoryParanoiaCheckBuffer(oldUserPtr); +#endif + + if (oldUserPtr == NULL) return muscleAlloc(newUserSize, retryOnFailure); + else if (newUserSize == 0) + { + muscleFree(oldUserPtr); + return NULL; + } + + size_t newInternalSize = CONVERT_USER_TO_INTERNAL_SIZE(newUserSize); + size_t * oldInternalPtr = CONVERT_USER_TO_INTERNAL_POINTER(oldUserPtr); + size_t oldInternalSize = *oldInternalPtr; + if (newInternalSize == oldInternalSize) return oldUserPtr; // same size as before? Then we are already done! + + void * newUserPtr = NULL; + MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + Mutex * glock = ma ? GetGlobalMuscleLock() : NULL; + if ((glock)&&(glock->Lock() != B_NO_ERROR)) + { + printf("Error, muscleRealloc() could not lock the global muscle lock!\n"); + SetFailedMemoryRequestSize(newUserSize); // FogBugz #7547 + return NULL; // serialize access to (ma) + } +#endif + + size_t oldUserSize = CONVERT_INTERNAL_TO_USER_SIZE(oldInternalSize); + if (newInternalSize > oldInternalSize) + { + size_t growBy = newInternalSize-oldInternalSize; + if ((ma == NULL)||(ma->AboutToAllocate(_currentlyAllocatedBytes, growBy) == B_NO_ERROR)) + { + size_t * newInternalPtr = (size_t *) realloc(oldInternalPtr, newInternalSize); + if (newInternalPtr) + { + _currentlyAllocatedBytes += growBy; // only reflect the newly-allocated bytes + *newInternalPtr = newInternalSize; // our little header tag so that muscleFree() will know how big the allocation was + newUserPtr = CONVERT_INTERNAL_TO_USER_POINTER(newInternalPtr); +//printf("r+"UINT32_FORMAT_SPEC"(->"UINT32_FORMAT_SPEC") = "UINT32_FORMAT_SPEC" oldUserPtr=%p newUserPtr=%p\n", (uint32)growBy, (uint32)newInternalSize, (uint32)_currentlyAllocatedBytes, oldUserPtr, newUserPtr); + +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + MemoryParanoiaPrepareBuffer(newInternalPtr, oldUserSize); +#endif + } + else if (ma) ma->AboutToFree(_currentlyAllocatedBytes+growBy, growBy); // FogBugz #4494: roll back the call to AboutToAllocate()! + } + + if ((ma)&&(newUserPtr == NULL)) + { + // I call printf() instead of LogTime() to avoid any chance of an infinite recursion + printf("muscleRealloc: reallocation failure (tried to grow " UINT32_FORMAT_SPEC "->" UINT32_FORMAT_SPEC " internal bytes / " UINT32_FORMAT_SPEC "->" UINT32_FORMAT_SPEC " user bytes))\n", (uint32)oldInternalSize, (uint32)newInternalSize, (uint32)oldUserSize, (uint32)newUserSize); + fflush(stdout); // make sure this message gets out! + + ma->AllocationFailed(_currentlyAllocatedBytes, growBy); // see if ma can free spare buffers up for us + + // Maybe the AllocationFailed() method was able to free up some memory; so we'll try it one more time + // That way we might be able to recover without interrupting the operation that was in progress. + if (retryOnFailure) + { + newUserPtr = muscleRealloc(oldUserPtr, newUserSize, false); + if (newUserPtr == NULL) ma->SetAllocationHasFailed(true); + } + } + } + else + { + size_t shrinkBy = oldInternalSize-newInternalSize; + if (ma) ma->AboutToFree(_currentlyAllocatedBytes, shrinkBy); + size_t * newInternalPtr = (size_t *) realloc(oldInternalPtr, newInternalSize); + if (newInternalPtr) + { + *newInternalPtr = newInternalSize; // our little header tag so that muscleFree() will know how big the allocation is now + _currentlyAllocatedBytes -= shrinkBy; +//printf("r-"UINT32_FORMAT_SPEC"(->"UINT32_FORMAT_SPEC") = "UINT32_FORMAT_SPEC" oldUserPtr=%p newUserPtr=%p\n", (uint32)shrinkBy, (uint32)newInternalSize, (uint32)_currentlyAllocatedBytes, oldUserPtr, newUserPtr); + +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + MemoryParanoiaPrepareBuffer(newInternalPtr, MUSCLE_NO_LIMIT); +#endif + newUserPtr = CONVERT_INTERNAL_TO_USER_POINTER(newInternalPtr); + } + else + { + newUserPtr = oldUserPtr; // I guess the best thing to do is just send back the old pointer? Not sure what to do here. + printf("muscleRealloc: reallocation failure (tried to shrink " UINT32_FORMAT_SPEC "->" UINT32_FORMAT_SPEC " internal bytes / " UINT32_FORMAT_SPEC "->" UINT32_FORMAT_SPEC " user bytes))\n", (uint32)oldInternalSize, (uint32)newInternalSize, (uint32)oldUserSize, (uint32)newUserSize); + fflush(stdout); // make sure this message gets out! + } + } + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + if (glock) (void) glock->Unlock(); +#endif + + if (newUserPtr == NULL) SetFailedMemoryRequestSize(newUserSize); // FogBugz #7547 + return newUserPtr; +} + +void muscleFree(void * userPtr) +{ + using namespace muscle; + if (userPtr) + { +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + TCHECKPOINT; + MemoryParanoiaCheckBuffer(userPtr); +#endif + + MemoryAllocator * ma = GetCPlusPlusGlobalMemoryAllocator()(); + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + Mutex * glock = ma ? GetGlobalMuscleLock() : NULL; + if ((glock)&&(glock->Lock() != B_NO_ERROR)) + { + printf("Error, muscleFree() could not lock the global muscle lock!!!\n"); + return; // serialize access to (ma) + } +#endif + + size_t * internalPtr = CONVERT_USER_TO_INTERNAL_POINTER(userPtr); + _currentlyAllocatedBytes -= *internalPtr; + + if (ma) ma->AboutToFree(_currentlyAllocatedBytes, *internalPtr); +//printf("-"UINT32_FORMAT_SPEC" = "UINT32_FORMAT_SPEC" userPtr=%p\n", (uint32)*internalPtr, (uint32)_currentlyAllocatedBytes, userPtr); + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + if (glock) (void) glock->Unlock(); +#endif + +#if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 + memset(internalPtr, MEMORY_PARANOIA_DEALLOCATED_GARBAGE_VALUE, *internalPtr); // make it obvious this memory was freed by munging it +#endif + + free(internalPtr); + } +} + +}; // end namespace muscle + +void * operator new(size_t s) THROW LPAREN BAD_ALLOC RPAREN +{ + using namespace muscle; + void * ret = muscleAlloc(s); + if (ret == NULL) {THROW BAD_ALLOC LPAREN RPAREN;} + return ret; +} + +void * operator new[](size_t s) THROW LPAREN BAD_ALLOC RPAREN +{ + using namespace muscle; + void * ret = muscleAlloc(s); + if (ret == NULL) {THROW BAD_ALLOC LPAREN RPAREN;} + return ret; +} + +// Borland, VC++, and OSF don't like separate throw/no-throw operators, it seems +# ifndef WIN32 +# ifndef __osf__ +void * operator new( size_t s, nothrow_t const &) THROW LPAREN RPAREN {using namespace muscle; return muscleAlloc(s);} +void * operator new[](size_t s, nothrow_t const &) THROW LPAREN RPAREN {using namespace muscle; return muscleAlloc(s);} +# endif +# endif + +void operator delete( void * p) THROW LPAREN RPAREN {using namespace muscle; muscleFree(p);} +void operator delete[](void * p) THROW LPAREN RPAREN {using namespace muscle; muscleFree(p);} + +#else +# if MUSCLE_ENABLE_MEMORY_PARANOIA > 0 +# error "If you want to enable MUSCLE_ENABLE_MEMORY_PARANOIA, you must define MUSCLE_ENABLE_MEMORY_TRACKING also!" +# endif +status_t MemoryParanoiaCheckBuffer(void *, bool) {return B_NO_ERROR;} +#endif diff --git a/system/GlobalMemoryAllocator.h b/system/GlobalMemoryAllocator.h new file mode 100644 index 00000000..0ff3b2d3 --- /dev/null +++ b/system/GlobalMemoryAllocator.h @@ -0,0 +1,91 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleGlobalMemoryAllocator_h +#define MuscleGlobalMemoryAllocator_h + +#include "util/MemoryAllocator.h" + +namespace muscle { + +// You can't use these functions unless memory tracking is enabled! +// So if you are getting errors, make sure -DMUSCLE_ENABLE_MEMORY_TRACKING +// is specified in your Makefile. +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING + +/** Set the MemoryAllocator object that is to be called by the C++ global new and delete operators. + * @note this function is only available is -DMUSCLE_ENABLE_MEMORY_TRACKING is defined in the Makefile. + * @param maRef Reference to The new MemoryAllocator object to use. May be a NULL reference + * if you just want to remove any current MemoryAllocator. + */ +void SetCPlusPlusGlobalMemoryAllocator(const MemoryAllocatorRef & maRef); + +/** Returns a reference to the current MemoryAllocator object that is being used by the + * C++ global new and delete operators. Will return a NULL reference if no MemoryAllocator is in use. + * @note this function is only available is -DMUSCLE_ENABLE_MEMORY_TRACKING is defined in the Makefile. + */ +const MemoryAllocatorRef & GetCPlusPlusGlobalMemoryAllocator(); + +/** Returns the number of bytes currently dynamically allocated by this process. + * @note this function is only available is -DMUSCLE_ENABLE_MEMORY_TRACKING is defined in the Makefile. + */ +size_t GetNumAllocatedBytes(); + +/** MUSCLE version of the C malloc() call. Unlike the C malloc() call, however + * this function will use the global MemoryAllocator object when allocating memory. + * The only time you should need to call this directly is from C code where + * you want to use the global memory allocators but don't want to replace all + * the calls to malloc() and free() with new and delete. For C++ programs, you + * can just use newnothrow and delete as usual and ignore this function. + * @param numBytes Number of bytes to attempt to allocate + * @param retryOnFailure This argument governs muscleAlloc's behaviour when + * an out-of-memory condition occurs. If true, muscleAlloc() + * will attempt the allocation a second time after calling + * AllocationFailed() on the global memory allocator, in the hope + * that AllocationFailed() was able to free enough memory + * to allow the allocation to succeed. If set false, AllocationFailed() + * will still be called after an out-of-memory error, but muscleAlloc() + * will return NULL. + * @return Pointer to an allocated memory buffer on success, or NULL on failure. + */ +void * muscleAlloc(size_t numBytes, bool retryOnFailure = true); + +/** Companion to muscleAlloc(). Any buffers allocated with muscleAlloc() should + * be freed with muscleFree() when you are done with them, to avoid memory leaks. + * @param buf Buffer that was previously allocated with muscleAlloc() or muscleRealloc(). + * If NULL, then this call will be a no-op. + */ +void muscleFree(void * buf); + +/** MUSCLE version of the C realloc() call. Works just like the C realloc(), + * except that it calls the proper callbacks on the global MemoryAllocator + * object, as appropriate. + * @param ptr Pointer to the buffer to resize, or a NULL pointer if you wish + * to allocate a new buffer. + * @param s Desired new size for the buffer, or 0 if you wish to free the buffer. + * @param retryOnFailure See muscleAlloc() for a description of this argument. + * @returns Pointer to the resized array on success, or NULL on failure or + * if (s) was zero. Note that the returned pointer may be the + * same as (ptr). + */ +void * muscleRealloc(void * ptr, size_t s, bool retryOnFailure = true); + +/** Given a pointer that was allocated with muscleAlloc(), muscleRealloc(), or the new operator, + * returns B_NO_ERROR if the memory paranoia guard values are correct, or B_ERROR if they are + * not. Note that this function is a no-op unless MUSCLE_ENABLE_MEMORY_PARANOIA is defined + * as a positive integer. + * @param p the pointer to check for validity/memory corruption. If NULL, this function will return B_NO_ERROR. + * @param crashIfInvalid If true, this function will crash the app if corruption is detected. Defaults to true. + * @returns B_NO_ERROR if the buffer is valid, B_ERROR if it isn't (or won't return if it crashed the app!) + * If MUSCLE_ENABLE_MEMORY_PARANOIA isn't defined, then this function always returns B_NO_ERROR. + */ +status_t MemoryParanoiaCheckBuffer(void * p, bool crashIfInvalid = true); + +#else +# define muscleAlloc malloc +# define muscleFree(x) {if (x) free(x);} +# define muscleRealloc realloc +#endif + +}; // end namespace muscle + +#endif diff --git a/system/MessageTransceiverThread.cpp b/system/MessageTransceiverThread.cpp new file mode 100644 index 00000000..d44ab2d6 --- /dev/null +++ b/system/MessageTransceiverThread.cpp @@ -0,0 +1,786 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/MessageTransceiverThread.h" +#include "iogateway/SignalMessageIOGateway.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/ReflectServer.h" + +namespace muscle { + +static status_t FindIPAddressInMessage(const Message & msg, const String & fieldName, ip_address & ip) +{ + const String * s = NULL; + if (msg.FindString(fieldName, &s) != B_NO_ERROR) return B_ERROR; + + ip = Inet_AtoN(s->Cstr()); + return B_NO_ERROR; +} + +static status_t AddIPAddressToMessage(Message & msg, const String & fieldName, const ip_address & ip) +{ + return msg.AddString(fieldName, Inet_NtoA(ip)); +} + +MessageTransceiverThread :: MessageTransceiverThread() : _forwardAllIncomingMessagesToSupervisor(true) +{ + // empty +} + +MessageTransceiverThread :: ~MessageTransceiverThread() +{ + MASSERT(IsInternalThreadRunning() == false, "You must call ShutdownInternalThread() on a MessageTransceiverThread before deleting it!"); + if (_server()) _server()->Cleanup(); +} + +status_t MessageTransceiverThread :: EnsureServerAllocated() +{ + if (_server() == NULL) + { + ReflectServerRef server = CreateReflectServer(); + if (server()) + { + const ConstSocketRef & sock = GetInternalThreadWakeupSocket(); + if (sock()) + { + ThreadSupervisorSessionRef controlSession = CreateSupervisorSession(); + if (controlSession()) + { + controlSession()->_mtt = this; + controlSession()->SetDefaultDistributionPath(GetDefaultDistributionPath()); + if (server()->AddNewSession(AbstractReflectSessionRef(controlSession.GetRefCountableRef(), true), sock) == B_NO_ERROR) + { + _server = server; +#ifdef MUSCLE_ENABLE_SSL + if (_privateKey()) server()->SetSSLPrivateKey(_privateKey); + if (_publicKey()) server()->SetSSLPublicKeyCertificate(_publicKey); +#endif + return B_NO_ERROR; + } + } + CloseSockets(); // close the other socket too + } + server()->Cleanup(); + } + return B_ERROR; + } + return B_NO_ERROR; +} + +ReflectServerRef MessageTransceiverThread :: CreateReflectServer() +{ + ReflectServer * rs = newnothrow ReflectServer; + if (rs) rs->SetDoLogging(false); // so that adding/removing client-side sessions won't show up in the log + else WARN_OUT_OF_MEMORY; + return ReflectServerRef(rs); +} + +status_t MessageTransceiverThread :: StartInternalThread() +{ + return ((IsInternalThreadRunning() == false)&&(EnsureServerAllocated() == B_NO_ERROR)) ? Thread::StartInternalThread() : B_ERROR; +} + +status_t MessageTransceiverThread :: SendMessageToSessions(const MessageRef & userMsg, const char * optPath) +{ + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_SEND_USER_MESSAGE)); + return ((msgRef())&&(msgRef()->AddMessage(MTT_NAME_MESSAGE, userMsg) == B_NO_ERROR)&&((optPath==NULL)||(msgRef()->AddString(MTT_NAME_PATH, optPath) == B_NO_ERROR))) ? SendMessageToInternalThread(msgRef) : B_ERROR; +} + +void ThreadWorkerSession :: SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(bool defaultValue) +{ + if (_forwardAllIncomingMessagesToSupervisor.HasValueBeenSet() == false) SetForwardAllIncomingMessagesToSupervisor(defaultValue); +} + +status_t MessageTransceiverThread :: AddNewSession(const ConstSocketRef & sock, const ThreadWorkerSessionRef & sessionRef) +{ + if (EnsureServerAllocated() == B_NO_ERROR) + { + ThreadWorkerSessionRef sRef = sessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(); + if (sRef()) sRef()->SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(_forwardAllIncomingMessagesToSupervisor); + return (sRef()) ? (IsInternalThreadRunning() ? SendAddNewSessionMessage(sRef, sock, NULL, invalidIP, 0, false, MUSCLE_TIME_NEVER, MUSCLE_TIME_NEVER) : _server()->AddNewSession(AbstractReflectSessionRef(sRef.GetRefCountableRef(), false), sock)) : B_ERROR; + } + return B_ERROR; +} + +status_t MessageTransceiverThread :: AddNewConnectSession(const ip_address & targetIPAddress, uint16 port, const ThreadWorkerSessionRef & sessionRef, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + if (EnsureServerAllocated() == B_NO_ERROR) + { + ThreadWorkerSessionRef sRef = sessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(); + if (sRef()) sRef()->SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(_forwardAllIncomingMessagesToSupervisor); + return (sRef()) ? (IsInternalThreadRunning() ? SendAddNewSessionMessage(sRef, ConstSocketRef(), NULL, targetIPAddress, port, false, autoReconnectDelay, maxAsyncConnectPeriod) : _server()->AddNewConnectSession(AbstractReflectSessionRef(sRef.GetRefCountableRef(), false), targetIPAddress, port, autoReconnectDelay, maxAsyncConnectPeriod)) : B_ERROR; + } + return B_ERROR; +} + +status_t MessageTransceiverThread :: AddNewConnectSession(const String & targetHostName, uint16 port, const ThreadWorkerSessionRef & sessionRef, bool expandLocalhost, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + if (EnsureServerAllocated() == B_NO_ERROR) + { + ThreadWorkerSessionRef sRef = sessionRef; + if (sRef() == NULL) sRef = CreateDefaultWorkerSession(); + if (sRef()) + { + sRef()->SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(_forwardAllIncomingMessagesToSupervisor); + if (IsInternalThreadRunning()) return SendAddNewSessionMessage(sRef, ConstSocketRef(), targetHostName(), 0, port, expandLocalhost, autoReconnectDelay, maxAsyncConnectPeriod); + else + { + ip_address ip = GetHostByName(targetHostName(), expandLocalhost); + return (ip != invalidIP) ? _server()->AddNewConnectSession(AbstractReflectSessionRef(sRef.GetRefCountableRef(), true), ip, port, autoReconnectDelay, maxAsyncConnectPeriod) : B_ERROR; + } + } + } + return B_ERROR; +} + +status_t MessageTransceiverThread :: SendAddNewSessionMessage(const ThreadWorkerSessionRef & sessionRef, const ConstSocketRef & sock, const char * hostName, const ip_address & hostIP, uint16 port, bool expandLocalhost, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_ADD_NEW_SESSION)); + + return ((sessionRef())&&(msgRef())&& + ((hostIP == invalidIP)||(AddIPAddressToMessage(*msgRef(), MTT_NAME_IP_ADDRESS, hostIP) == B_NO_ERROR))&& + (msgRef()->AddTag( MTT_NAME_SESSION, sessionRef.GetRefCountableRef()) == B_NO_ERROR) && + (msgRef()->CAddString(MTT_NAME_HOSTNAME, hostName) == B_NO_ERROR) && + (msgRef()->CAddInt16( MTT_NAME_PORT, port) == B_NO_ERROR) && + (msgRef()->CAddBool( MTT_NAME_EXPANDLOCALHOST, expandLocalhost) == B_NO_ERROR) && + (msgRef()->CAddTag( MTT_NAME_SOCKET, CastAwayConstFromRef(sock.GetRefCountableRef())) == B_NO_ERROR) && + (msgRef()->CAddInt64( MTT_NAME_AUTORECONNECTDELAY, autoReconnectDelay, MUSCLE_TIME_NEVER) == B_NO_ERROR) && + (msgRef()->CAddInt64( MTT_NAME_MAXASYNCCONNPERIOD, maxAsyncConnectPeriod, MUSCLE_TIME_NEVER) == B_NO_ERROR)) ? SendMessageToInternalThread(msgRef) : B_ERROR; +} + +status_t MessageTransceiverThread :: PutAcceptFactory(uint16 port, const ThreadWorkerSessionFactoryRef & factoryRef, const ip_address & optInterfaceIP, uint16 * optRetPort) +{ + if (EnsureServerAllocated() == B_NO_ERROR) + { + ThreadWorkerSessionFactoryRef fRef = factoryRef; + if (fRef() == NULL) fRef = CreateDefaultSessionFactory(); + if (fRef()) + { + fRef()->SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(_forwardAllIncomingMessagesToSupervisor); + if (IsInternalThreadRunning()) + { + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_PUT_ACCEPT_FACTORY)); + if ((msgRef())&&(msgRef()->AddInt16(MTT_NAME_PORT, port) == B_NO_ERROR)&&(msgRef()->AddTag(MTT_NAME_FACTORY, fRef.GetRefCountableRef()) == B_NO_ERROR)&&(AddIPAddressToMessage(*msgRef(), MTT_NAME_IP_ADDRESS, optInterfaceIP) == B_NO_ERROR)&&(SendMessageToInternalThread(msgRef) == B_NO_ERROR)) return B_NO_ERROR; + } + else if (_server()->PutAcceptFactory(port, ReflectSessionFactoryRef(fRef.GetRefCountableRef(), true), optInterfaceIP, optRetPort) == B_NO_ERROR) return B_NO_ERROR; + } + } + return B_ERROR; +} + +status_t MessageTransceiverThread :: RemoveAcceptFactory(uint16 port, const ip_address & optInterfaceIP) +{ + if (_server()) + { + if (IsInternalThreadRunning()) + { + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_REMOVE_ACCEPT_FACTORY)); + return ((msgRef())&&(msgRef()->AddInt16(MTT_NAME_PORT, port) == B_NO_ERROR)&&(AddIPAddressToMessage(*msgRef(), MTT_NAME_IP_ADDRESS, optInterfaceIP) == B_NO_ERROR)) ? SendMessageToInternalThread(msgRef) : B_ERROR; + } + else return _server()->RemoveAcceptFactory(port, optInterfaceIP); + } + else return B_NO_ERROR; // if there's no server, there's no port +} + +#ifdef MUSCLE_ENABLE_SSL + +status_t MessageTransceiverThread :: SetSSLPrivateKey(const ConstByteBufferRef & privateKey) +{ + _privateKey = privateKey; + + if (IsInternalThreadRunning()) + { + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_SET_SSL_PRIVATE_KEY)); + if ((msgRef() == NULL)||((_privateKey())&&(msgRef()->AddFlat(MTT_NAME_DATA, CastAwayConstFromRef(privateKey)) != B_NO_ERROR))||(SendMessageToInternalThread(msgRef) != B_NO_ERROR)) return B_ERROR; + } + return B_NO_ERROR; +} + +status_t MessageTransceiverThread :: SetSSLPublicKeyCertificate(const ConstByteBufferRef & publicKey) +{ + _publicKey = publicKey; + + if (IsInternalThreadRunning()) + { + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_SET_SSL_PUBLIC_KEY)); + if ((msgRef() == NULL)||((_publicKey())&&(msgRef()->AddFlat(MTT_NAME_DATA, CastAwayConstFromRef(_publicKey)) != B_NO_ERROR))||(SendMessageToInternalThread(msgRef) != B_NO_ERROR)) return B_ERROR; + } + return B_NO_ERROR; +} + +#endif + +status_t MessageTransceiverThread :: SetDefaultDistributionPath(const String & path) +{ + if (_defaultDistributionPath != path) + { + if (IsInternalThreadRunning()) + { + MessageRef msgRef(GetMessageFromPool(MTT_COMMAND_SET_DEFAULT_PATH)); + if ((msgRef() == NULL)||(msgRef()->AddString(MTT_NAME_PATH, path) != B_NO_ERROR)||(SendMessageToInternalThread(msgRef) != B_NO_ERROR)) return B_ERROR; + } + _defaultDistributionPath = path; + } + return B_NO_ERROR; +} + +int32 MessageTransceiverThread :: GetNextEventFromInternalThread(uint32 & code, MessageRef * optRetRef, String * optFromSession, uint32 * optFromFactoryID, IPAddressAndPort * optLocation) +{ + // First, default values for everyone + if (optRetRef) optRetRef->Reset(); + if (optFromSession) optFromSession->Clear(); + if (optFromFactoryID) *optFromFactoryID = 0; + + MessageRef msgRef; + int32 ret = GetNextReplyFromInternalThread(msgRef); + if (ret >= 0) + { + if (msgRef()) + { + code = msgRef()->what; + if ((optRetRef)&&(msgRef()->FindMessage(MTT_NAME_MESSAGE, *optRetRef) != B_NO_ERROR)) *optRetRef = msgRef; + if (optFromSession) (void) msgRef()->FindString(MTT_NAME_FROMSESSION, *optFromSession); + if (optFromFactoryID) (void) msgRef()->FindInt32(MTT_NAME_FACTORY_ID, optFromFactoryID); + if (optLocation) + { + const String * s; + if (msgRef()->FindString(MTT_NAME_LOCATION, &s) == B_NO_ERROR) optLocation->SetFromString(*s, 0, false); + } + } + else ret = -1; // NULL event message should never happen, but just in case + } + return ret; +} + +status_t MessageTransceiverThread :: RequestOutputQueuesDrainedNotification(const MessageRef & notifyRef, const char * optDistPath, DrainTag * optDrainTag) +{ + // Send a command to the supervisor letting him know we are waiting for him to assure + // us that all the matching worker sessions have dequeued their messages. Preallocate + // as much as possible so we won't have to worry about out-of-memory later on, when + // it's too late to handle it properly. + MessageRef commandRef = GetMessageFromPool(MTT_COMMAND_NOTIFY_ON_OUTPUT_DRAIN); + MessageRef replyRef = GetMessageFromPool(MTT_EVENT_OUTPUT_QUEUES_DRAINED); + if ((commandRef())&&(replyRef())&&((notifyRef() == NULL)||(replyRef()->AddMessage(MTT_NAME_MESSAGE, notifyRef) == B_NO_ERROR))) + { + DrainTagRef drainTagRef(optDrainTag ? optDrainTag : newnothrow DrainTag); + if (drainTagRef()) drainTagRef()->SetReplyMessage(replyRef); + RefCountableRef genericRef = drainTagRef.GetRefCountableRef(); + if ((drainTagRef())&& + ((optDistPath == NULL)||(commandRef()->AddString(MTT_NAME_PATH, optDistPath) == B_NO_ERROR))&& + (commandRef()->AddTag(MTT_NAME_DRAIN_TAG, genericRef) == B_NO_ERROR)&& + (SendMessageToInternalThread(commandRef) == B_NO_ERROR)) return B_NO_ERROR; + + // User keeps ownership of his custom DrainTag on error, so we don't delete it. + if ((drainTagRef())&&(drainTagRef() == optDrainTag)) + { + drainTagRef()->SetReplyMessage(MessageRef()); + drainTagRef.Neutralize(); + genericRef.Neutralize(); + } + } + return B_ERROR; +} + +status_t MessageTransceiverThread :: SetNewInputPolicy(const AbstractSessionIOPolicyRef & pref, const char * optDistPath) +{ + return SetNewPolicyAux(MTT_COMMAND_SET_INPUT_POLICY, pref, optDistPath); +} + +status_t MessageTransceiverThread :: SetNewOutputPolicy(const AbstractSessionIOPolicyRef & pref, const char * optDistPath) +{ + return SetNewPolicyAux(MTT_COMMAND_SET_OUTPUT_POLICY, pref, optDistPath); +} + +status_t MessageTransceiverThread :: SetNewPolicyAux(uint32 what, const AbstractSessionIOPolicyRef & pref, const char * optDistPath) +{ + MessageRef commandRef = GetMessageFromPool(what); + return ((commandRef())&& + ((optDistPath == NULL)||(commandRef()->AddString(MTT_NAME_PATH, optDistPath) == B_NO_ERROR))&& + ((pref() == NULL)||(commandRef()->AddTag(MTT_NAME_POLICY_TAG, pref.GetRefCountableRef()) == B_NO_ERROR))) + ? SendMessageToInternalThread(commandRef) : B_ERROR; +} + +status_t MessageTransceiverThread :: SetOutgoingMessageEncoding(int32 encoding, const char * optDistPath) +{ + MessageRef commandRef = GetMessageFromPool(MTT_COMMAND_SET_OUTGOING_ENCODING); + return ((commandRef())&& + ((optDistPath == NULL)||(commandRef()->AddString(MTT_NAME_PATH, optDistPath) == B_NO_ERROR))&& + (commandRef()->AddInt32(MTT_NAME_ENCODING, encoding) == B_NO_ERROR)) + ? SendMessageToInternalThread(commandRef) : B_ERROR; +} + +status_t MessageTransceiverThread :: RemoveSessions(const char * optDistPath) +{ + MessageRef commandRef = GetMessageFromPool(MTT_COMMAND_REMOVE_SESSIONS); + return ((commandRef())&&((optDistPath == NULL)||(commandRef()->AddString(MTT_NAME_PATH, optDistPath) == B_NO_ERROR))) ? SendMessageToInternalThread(commandRef) : B_ERROR; +} + +void MessageTransceiverThread :: Reset() +{ + ShutdownInternalThread(); + if (_server()) + { + _server()->Cleanup(); + _server.Reset(); + } + + // Clear both message queues of any leftover messages. + MessageRef junk; + while(WaitForNextMessageFromOwner(junk, 0) >= 0) {/* empty */} + while(GetNextReplyFromInternalThread(junk) >= 0) {/* empty */} +} + +ThreadSupervisorSessionRef MessageTransceiverThread :: CreateSupervisorSession() +{ + ThreadSupervisorSession * ret = newnothrow ThreadSupervisorSession(); + if (ret == NULL) WARN_OUT_OF_MEMORY; + return ThreadSupervisorSessionRef(ret); +} + +ThreadWorkerSessionRef MessageTransceiverThread :: CreateDefaultWorkerSession() +{ + ThreadWorkerSession * ret = newnothrow ThreadWorkerSession(); + if (ret == NULL) WARN_OUT_OF_MEMORY; + return ThreadWorkerSessionRef(ret); +} + +ThreadWorkerSessionFactoryRef MessageTransceiverThread :: CreateDefaultSessionFactory() +{ + ThreadWorkerSessionFactory * ret = newnothrow ThreadWorkerSessionFactory(); + if (ret == NULL) WARN_OUT_OF_MEMORY; + return ThreadWorkerSessionFactoryRef(ret); +} + +ThreadWorkerSessionFactory :: ThreadWorkerSessionFactory() : _forwardAllIncomingMessagesToSupervisor(true) +{ + // empty +} + +status_t ThreadWorkerSessionFactory :: AttachedToServer() +{ + return (StorageReflectSessionFactory::AttachedToServer() == B_NO_ERROR) ? SendMessageToSupervisorSession(GetMessageFromPool(MTT_EVENT_FACTORY_ATTACHED)) : B_ERROR; +} + +void ThreadWorkerSessionFactory :: AboutToDetachFromServer() +{ + (void) SendMessageToSupervisorSession(GetMessageFromPool(MTT_EVENT_FACTORY_DETACHED)); + StorageReflectSessionFactory::AboutToDetachFromServer(); +} + +status_t ThreadWorkerSessionFactory :: SendMessageToSupervisorSession(const MessageRef & msg, void * userData) +{ + // I'm not bothering to cache the supervisor pointer for this class, because this method is called + // so rarely and I can't be bothered to add anti-dangling-pointer logic for so little gain --jaf + ThreadSupervisorSession * supervisorSession = FindFirstSessionOfType(); + if (supervisorSession) + { + supervisorSession->MessageReceivedFromFactory(*this, msg, userData); + return B_NO_ERROR; + } + else return B_ERROR; +} + +void ThreadWorkerSessionFactory :: SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(bool defaultValue) +{ + if (_forwardAllIncomingMessagesToSupervisor.HasValueBeenSet() == false) SetForwardAllIncomingMessagesToSupervisor(defaultValue); +} + +ThreadWorkerSessionRef ThreadWorkerSessionFactory :: CreateThreadWorkerSession(const String &, const IPAddressAndPort &) +{ + ThreadWorkerSession * ret = newnothrow ThreadWorkerSession(); + if (ret == NULL) WARN_OUT_OF_MEMORY; + return ThreadWorkerSessionRef(ret); +} + +AbstractReflectSessionRef ThreadWorkerSessionFactory :: CreateSession(const String & clientHostIP, const IPAddressAndPort & iap) +{ + ThreadWorkerSessionRef tws = CreateThreadWorkerSession(clientHostIP, iap); + if ((tws())&&(SetMaxIncomingMessageSizeFor(tws()) == B_NO_ERROR)) + { + tws()->SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(_forwardAllIncomingMessagesToSupervisor); + tws()->_acceptedIAP = iap; // gotta send the MTT_EVENT_SESSION_ACCEPTED Message from within AttachedToServer() + return AbstractReflectSessionRef(tws.GetRefCountableRef(), true); + } + return AbstractReflectSessionRef(); +} + +void MessageTransceiverThread :: InternalThreadEntry() +{ + if (_server()) + { + (void) _server()->ServerProcessLoop(); + _server()->Cleanup(); + } + SendMessageToOwner(GetMessageFromPool(MTT_EVENT_SERVER_EXITED)); +} + +ThreadWorkerSession :: ThreadWorkerSession() : _forwardAllIncomingMessagesToSupervisor(true), _supervisorSession(NULL) +{ + // empty +} + +ThreadWorkerSession :: ~ThreadWorkerSession() +{ + // empty +} + +void ThreadWorkerSession :: AsyncConnectCompleted() +{ + StorageReflectSession::AsyncConnectCompleted(); + + MessageRef msg = GetMessageFromPool(MTT_EVENT_SESSION_CONNECTED); + if ((msg())&&(msg()->AddString(MTT_NAME_LOCATION, IPAddressAndPort(GetAsyncConnectIP(), GetAsyncConnectPort()).ToString()) == B_NO_ERROR)) (void) SendMessageToSupervisorSession(msg); +} + +status_t ThreadWorkerSession :: AttachedToServer() +{ + if (StorageReflectSession::AttachedToServer() == B_NO_ERROR) + { + if (_acceptedIAP.IsValid()) + { + MessageRef msg = GetMessageFromPool(MTT_EVENT_SESSION_ACCEPTED); + if ((msg() == NULL)||(msg()->AddString(MTT_NAME_LOCATION, _acceptedIAP.ToString()) != B_NO_ERROR)||(SendMessageToSupervisorSession(msg) != B_NO_ERROR)) return B_ERROR; + } + return SendMessageToSupervisorSession(GetMessageFromPool(MTT_EVENT_SESSION_ATTACHED)); + } + else return B_ERROR; +} + +status_t ThreadWorkerSession :: SendMessageToSupervisorSession(const MessageRef & msg, void * userData) +{ + if (_supervisorSession == NULL) _supervisorSession = FindFirstSessionOfType(); + if (_supervisorSession) + { + _supervisorSession->MessageReceivedFromSession(*this, msg, userData); + return B_NO_ERROR; + } + else return B_ERROR; +} + +bool ThreadWorkerSession :: ClientConnectionClosed() +{ + (void) SendMessageToSupervisorSession(GetMessageFromPool(MTT_EVENT_SESSION_DISCONNECTED)); + _drainedNotifiers.Clear(); + return StorageReflectSession::ClientConnectionClosed(); +} + +void ThreadWorkerSession :: AboutToDetachFromServer() +{ + (void) SendMessageToSupervisorSession(GetMessageFromPool(MTT_EVENT_SESSION_DETACHED)); + _drainedNotifiers.Clear(); + _supervisorSession = NULL; // paranoia + StorageReflectSession::AboutToDetachFromServer(); +} + +int32 ThreadWorkerSession :: DoOutput(uint32 maxBytes) +{ + int32 ret = StorageReflectSession::DoOutput(maxBytes); + if (_drainedNotifiers.HasItems()) + { + AbstractMessageIOGateway * gw = GetGateway()(); + if ((gw == NULL)||(gw->HasBytesToOutput() == false)) _drainedNotifiers.Clear(); + } + return ret; +} + +void ThreadWorkerSession :: MessageReceivedFromGateway(const MessageRef & msg, void * userData) +{ + if (_forwardAllIncomingMessagesToSupervisor) + { + // Wrap it up so the supervisor knows its for him, and send it out + MessageRef wrapper = GetMessageFromPool(MTT_EVENT_INCOMING_MESSAGE); + if ((wrapper())&&(wrapper()->AddMessage(MTT_NAME_MESSAGE, msg) == B_NO_ERROR)) (void) SendMessageToSupervisorSession(wrapper, userData); + } + else StorageReflectSession::MessageReceivedFromGateway(msg, userData); +} + +void ThreadWorkerSession :: MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void * userData) +{ + const Message * msg = msgRef(); + if (msg) + { + if ((msg->what >= MTT_COMMAND_SEND_USER_MESSAGE)&&(msg->what <= MTT_LAST_COMMAND)) + { + switch(msg->what) + { + case MTT_COMMAND_NOTIFY_ON_OUTPUT_DRAIN: + { + RefCountableRef genericRef; + if (msg->FindTag(MTT_NAME_DRAIN_TAG, genericRef) == B_NO_ERROR) + { + DrainTagRef drainTagRef(genericRef, true); + if (drainTagRef()) + { + // Add our session ID so that the supervisor session will know we received the drain tag + Message * rmsg = drainTagRef()->GetReplyMessage()(); + if (rmsg) rmsg->AddString(MTT_NAME_FROMSESSION, GetSessionRootPath()); + + // If we have any messages pending, we'll save this message reference until our + // outgoing message queue becomes empty. That way the DrainTag item held by the + // referenced message won't be deleted until the appropriate time, and hence + // the supervisor won't be notified until all the specified queues have drained. + AbstractMessageIOGateway * gw = GetGateway()(); + if ((gw)&&(gw->HasBytesToOutput())) _drainedNotifiers.AddTail(drainTagRef); + } + } + } + break; + + case MTT_COMMAND_SEND_USER_MESSAGE: + { + MessageRef userMsg; + if (msg->FindMessage(MTT_NAME_MESSAGE, userMsg) == B_NO_ERROR) AddOutgoingMessage(userMsg); + } + break; + + case MTT_COMMAND_SET_INPUT_POLICY: + case MTT_COMMAND_SET_OUTPUT_POLICY: + { + RefCountableRef tagRef; + (void) msg->FindTag(MTT_NAME_POLICY_TAG, tagRef); + AbstractSessionIOPolicyRef pref(tagRef, true); + if (msg->what == MTT_COMMAND_SET_INPUT_POLICY) SetInputPolicy(pref); + else SetOutputPolicy(pref); + } + break; + + case MTT_COMMAND_SET_OUTGOING_ENCODING: + { + int32 enc; + if (msg->FindInt32(MTT_NAME_ENCODING, enc) == B_NO_ERROR) + { + MessageIOGateway * gw = dynamic_cast(GetGateway()()); + if (gw) gw->SetOutgoingEncoding(enc); + } + } + break; + + case MTT_COMMAND_REMOVE_SESSIONS: + EndSession(); + break; + } + } + else if ((msg->what >= MTT_EVENT_INCOMING_MESSAGE)&&(msg->what <= MTT_LAST_EVENT)) + { + // ignore these; we don't care about silly MTT_EVENTS, those are for the supervisor and the user + } + else StorageReflectSession::MessageReceivedFromSession(from, msgRef, userData); + } +} + +ThreadSupervisorSession :: ThreadSupervisorSession() +{ + // empty +} + +ThreadSupervisorSession :: ~ThreadSupervisorSession() +{ + // empty +} + +void ThreadSupervisorSession :: AboutToDetachFromServer() +{ + // Neutralize all outstanding DrainTrags so that they won't try to call DrainTagIsBeingDeleted() on me after I'm gone. + for (HashtableIterator tagIter(_drainTags); tagIter.HasData(); tagIter++) tagIter.GetKey()->SetNotify(NULL); + + // Nerf any ThreadWorkerSessions' cached pointers to us so they won't dangle + Queue workers; + if (FindSessionsOfType(workers) == B_NO_ERROR) + { + for (uint32 i=0; i(workers[i]()); + if (ws->_supervisorSession == this) ws->_supervisorSession = NULL; + } + } + + StorageReflectSession :: AboutToDetachFromServer(); +} + +void ThreadSupervisorSession :: DrainTagIsBeingDeleted(DrainTag * tag) +{ + if (_drainTags.Remove(tag) == B_NO_ERROR) _mtt->SendMessageToOwner(tag->GetReplyMessage()); +} + +AbstractMessageIOGatewayRef ThreadSupervisorSession :: CreateGateway() +{ + AbstractMessageIOGateway * gw = newnothrow SignalMessageIOGateway(); + if (gw == NULL) WARN_OUT_OF_MEMORY; + return AbstractMessageIOGatewayRef(gw); +} + +void ThreadSupervisorSession :: MessageReceivedFromGateway(const MessageRef &, void *) +{ + // The message from the gateway is merely a signal that we should check + // the message queue from the main thread again, to see if there are + // new messages from our owner waiting. So we'll do that here. + MessageRef msgFromOwner; + int32 numLeft; + while((numLeft = _mtt->WaitForNextMessageFromOwner(msgFromOwner, 0)) >= 0) + { + if (msgFromOwner()) MessageReceivedFromOwner(msgFromOwner, (uint32)numLeft); + else + { + EndServer(); // this will cause our thread to exit + break; + } + } +} + +void ThreadSupervisorSession :: MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msgRef, void *) +{ + if (msgRef()) (void) msgRef()->AddString(MTT_NAME_FROMSESSION, from.GetSessionRootPath()); + _mtt->SendMessageToOwner(msgRef); +} + +void ThreadSupervisorSession :: MessageReceivedFromFactory(ReflectSessionFactory & from, const MessageRef & msgRef, void *) +{ + if (msgRef()) msgRef()->AddInt32(MTT_NAME_FACTORY_ID, from.GetFactoryID()); + _mtt->SendMessageToOwner(msgRef); +} + +bool ThreadSupervisorSession :: ClientConnectionClosed() +{ + EndServer(); + return StorageReflectSession::ClientConnectionClosed(); +} + +status_t ThreadSupervisorSession :: AddNewWorkerConnectSession(const ThreadWorkerSessionRef & sessionRef, const ip_address & hostIP, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod) +{ + status_t ret = (hostIP != invalidIP) ? AddNewConnectSession(AbstractReflectSessionRef(sessionRef.GetRefCountableRef(), true), hostIP, port, autoReconnectDelay, maxAsyncConnectPeriod) : B_ERROR; + + // For immediate failure: Since (sessionRef) never attached, we need to send the disconnect message ourself. + if ((ret != B_NO_ERROR)&&(sessionRef())) + { + // We have to synthesize the MTT_NAME_FROMSESSION path ourselves, since the session was never added to the server and thus its path isn't set + MessageRef errorMsg = GetMessageFromPool(MTT_EVENT_SESSION_DISCONNECTED); + if ((errorMsg())&&(errorMsg()->AddString(MTT_NAME_FROMSESSION, String("/%1/%2").Arg(Inet_NtoA(hostIP)).Arg(sessionRef()->GetSessionID())) == B_NO_ERROR)) _mtt->SendMessageToOwner(errorMsg); + } + return ret; +} + +void ThreadSupervisorSession :: SendMessageToWorkers(const MessageRef & distMsg) +{ + String distPath; + SendMessageToMatchingSessions(distMsg, (distMsg()->FindString(MTT_NAME_PATH, distPath) == B_NO_ERROR) ? distPath : _defaultDistributionPath, ConstQueryFilterRef(), false); +} + +status_t ThreadSupervisorSession :: MessageReceivedFromOwner(const MessageRef & msgRef, uint32) +{ + const Message * msg = msgRef(); + if (msg) + { + if (muscleInRange(msg->what, (uint32) MTT_COMMAND_SEND_USER_MESSAGE, (uint32) (MTT_LAST_COMMAND-1))) + { + switch(msg->what) + { + case MTT_COMMAND_ADD_NEW_SESSION: + { + RefCountableRef tagRef; + if (msg->FindTag(MTT_NAME_SESSION, tagRef) == B_NO_ERROR) + { + ThreadWorkerSessionRef sessionRef(tagRef, true); + if (sessionRef()) + { + const char * hostName; + ip_address hostIP; + uint16 port = msg->GetInt16(MTT_NAME_PORT); + uint64 autoReconnectDelay = msg->GetInt64(MTT_NAME_AUTORECONNECTDELAY, MUSCLE_TIME_NEVER); + uint64 maxAsyncConnectPeriod = msg->GetInt64(MTT_NAME_MAXASYNCCONNPERIOD, MUSCLE_TIME_NEVER); + + if (FindIPAddressInMessage(*msg, MTT_NAME_IP_ADDRESS, hostIP) == B_NO_ERROR) (void) AddNewWorkerConnectSession(sessionRef, hostIP, port, autoReconnectDelay, maxAsyncConnectPeriod); + else if (msg->FindString(MTT_NAME_HOSTNAME, &hostName) == B_NO_ERROR) (void) AddNewWorkerConnectSession(sessionRef, GetHostByName(hostName, msg->GetBool(MTT_NAME_EXPANDLOCALHOST)), port, autoReconnectDelay, maxAsyncConnectPeriod); + else (void) AddNewSession(AbstractReflectSessionRef(sessionRef.GetRefCountableRef(), true), ConstSocketRef(msg->GetTag(MTT_NAME_SOCKET), true)); + } + else LogTime(MUSCLE_LOG_ERROR, "MTT_COMMAND_PUT_ACCEPT_FACTORY: Could not get Session!\n"); + } + } + break; + + case MTT_COMMAND_PUT_ACCEPT_FACTORY: + { + RefCountableRef tagRef; + if (msg->FindTag(MTT_NAME_FACTORY, tagRef) == B_NO_ERROR) + { + ThreadWorkerSessionFactoryRef factoryRef(tagRef, true); + if (factoryRef()) + { + uint16 port = 0; (void) msg->FindInt16(MTT_NAME_PORT, port); + ip_address ip = invalidIP; (void) FindIPAddressInMessage(*msg, MTT_NAME_IP_ADDRESS, ip); + (void) PutAcceptFactory(port, ReflectSessionFactoryRef(factoryRef.GetRefCountableRef(), true), ip); + } + else LogTime(MUSCLE_LOG_ERROR, "MTT_COMMAND_PUT_ACCEPT_FACTORY: Could not get ReflectSessionFactory!\n"); + } + } + break; + + case MTT_COMMAND_REMOVE_ACCEPT_FACTORY: + { + uint16 port; + ip_address ip; + if ((msg->FindInt16(MTT_NAME_PORT, port) == B_NO_ERROR)&&(FindIPAddressInMessage(*msg, MTT_NAME_IP_ADDRESS, ip) == B_NO_ERROR)) (void) RemoveAcceptFactory(port, ip); + } + break; + + case MTT_COMMAND_SET_DEFAULT_PATH: + { + String dpath; + (void) msg->FindString(MTT_NAME_PATH, dpath); + SetDefaultDistributionPath(dpath); + } + break; + + case MTT_COMMAND_NOTIFY_ON_OUTPUT_DRAIN: + { + RefCountableRef genericRef; + if (msg->FindTag(MTT_NAME_DRAIN_TAG, genericRef) == B_NO_ERROR) + { + DrainTagRef drainTagRef(genericRef, true); + if ((drainTagRef())&&(_drainTags.PutWithDefault(drainTagRef()) == B_NO_ERROR)) + { + drainTagRef()->SetNotify(this); + SendMessageToWorkers(msgRef); + + // Check the tag to see if anyone got it. If not, we'll add the + // PR_NAME_KEY string to the reply field, to give the user thread + // a hint about which handler the reply should be directed back to. + Message * rmsg = drainTagRef()->GetReplyMessage()(); + if ((rmsg)&&(rmsg->HasName(MTT_NAME_FROMSESSION) == false)) + { + String t; + if (msg->FindString(MTT_NAME_PATH, t) == B_NO_ERROR) (void) rmsg->AddString(MTT_NAME_FROMSESSION, t); + } + } + } + } + break; + +#ifdef MUSCLE_ENABLE_SSL + case MTT_COMMAND_SET_SSL_PRIVATE_KEY: + _mtt->_server()->SetSSLPrivateKey(msg->GetFlat(MTT_NAME_DATA)); + break; + + case MTT_COMMAND_SET_SSL_PUBLIC_KEY: + _mtt->_server()->SetSSLPublicKeyCertificate(msg->GetFlat(MTT_NAME_DATA)); + break; +#endif + + default: + SendMessageToWorkers(msgRef); + break; + } + } + else StorageReflectSession::MessageReceivedFromGateway(msgRef, NULL); + + return B_NO_ERROR; + } + else return B_ERROR; +} + +DrainTag :: ~DrainTag() +{ + if (_notify) _notify->DrainTagIsBeingDeleted(this); +} + +}; // end namespace muscle diff --git a/system/MessageTransceiverThread.h b/system/MessageTransceiverThread.h new file mode 100644 index 00000000..d1ae00ee --- /dev/null +++ b/system/MessageTransceiverThread.h @@ -0,0 +1,678 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleMessageTransceiverThread_h +#define MuscleMessageTransceiverThread_h + +#include "system/Thread.h" +#include "reflector/StorageReflectSession.h" +#include "reflector/ReflectServer.h" +#include "support/TamperEvidentValue.h" + +namespace muscle { + +class ThreadSupervisorSession; +class MessageTransceiverThread; + +// MTT_EVENT_SESSION_CONNECTED events, this field contains a string representation of the IPAddressAndPort object + +/** These are reply codes returned by MessageTransceiverThread::GetNextEventFromInternalThread() + * @see MessageTransceiverThread::GetNextEventFromInternalThread() + */ +enum { + MTT_EVENT_INCOMING_MESSAGE = 1835233840, // A new message from a remote computer is ready to process + MTT_EVENT_SESSION_ACCEPTED, // A new session has been created by one of our factory objects + MTT_EVENT_SESSION_ATTACHED, // A new session has been attached to the local server + MTT_EVENT_SESSION_CONNECTED, // A session on the local server has completed its connection to the remote one + MTT_EVENT_SESSION_DISCONNECTED, // A session on the local server got disconnected from its remote peer + MTT_EVENT_SESSION_DETACHED, // A session on the local server has detached (and been destroyed) + MTT_EVENT_FACTORY_ATTACHED, // A ReflectSessionFactory object has been attached to the server + MTT_EVENT_FACTORY_DETACHED, // A ReflectSessionFactory object has been detached (and been destroyed) + MTT_EVENT_OUTPUT_QUEUES_DRAINED, // Output queues of sessions previously specified in RequestOutputQueuesDrainedNotification() have drained + MTT_EVENT_SERVER_EXITED, // The ReflectServer event loop has terminated + MTT_LAST_EVENT +}; + +/** These are command codes used in the MessageTransceiverThread's internal protocol */ +enum { + MTT_COMMAND_SEND_USER_MESSAGE = 1835230000, // contains a user message to be sent out + MTT_COMMAND_ADD_NEW_SESSION, // contains info on a new session to be created + MTT_COMMAND_PUT_ACCEPT_FACTORY, // request to start accepting session(s) on a given port + MTT_COMMAND_REMOVE_ACCEPT_FACTORY, // remove the acceptor factory on a given port(s) + MTT_COMMAND_SET_DEFAULT_PATH, // change the default distribution path + MTT_COMMAND_NOTIFY_ON_OUTPUT_DRAIN, // request a notification when all currently pending output has been sent + MTT_COMMAND_SET_INPUT_POLICY, // set a new input IO Policy for worker sessions + MTT_COMMAND_SET_OUTPUT_POLICY, // set a new output IO Policy for worker sessions + MTT_COMMAND_REMOVE_SESSIONS, // remove the matching worker sessions + MTT_COMMAND_SET_OUTGOING_ENCODING, // set the MUSCLE_MESSAGE_ENCODING_* setting on worker sessions + MTT_COMMAND_SET_SSL_PRIVATE_KEY, // set the private key used to authenticate accepted incoming TCP connections + MTT_COMMAND_SET_SSL_PUBLIC_KEY, // set the public key used to certify outgoing TCP connections + MTT_LAST_COMMAND +}; + +/** These are field names used in the MessageTransceiverThread's internal protocol */ +#define MTT_NAME_PATH "path" // field containing a session path (e.g. "/*/*") +#define MTT_NAME_DATA "data" // field containing a raw bytes +#define MTT_NAME_MESSAGE "mssg" // field containing a message object +#define MTT_NAME_SOCKET "sock" // field containing a Socket reference +#define MTT_NAME_IP_ADDRESS "addr" // field containing an int32 IP address +#define MTT_NAME_HOSTNAME "host" // field containing an ASCII hostname or IP address +#define MTT_NAME_PORT "port" // field containing an int16 port number +#define MTT_NAME_FACTORY_ID "fcid" // field containing a uint32 factory ID number (new for v3.40) +#define MTT_NAME_SESSION "sess" // field containing an AbstractReflectSession tag +#define MTT_NAME_FROMSESSION "sfrm" // field containing the root path of the session this message is from (e.g. "192.168.1.103/17") +#define MTT_NAME_FACTORY "fact" // field containing a ReflectSessionFactory tag +#define MTT_NAME_DRAIN_TAG "dtag" // field containing a DrainTag reference +#define MTT_NAME_POLICY_TAG "ptag" // field containing an IOPolicy reference +#define MTT_NAME_ENCODING "enco" // field containing the MUSCLE_MESSAGE_ENCODING_* value +#define MTT_NAME_EXPANDLOCALHOST "expl" // boolean field indicating whether localhost IP should be expanded to primary IP +#define MTT_NAME_AUTORECONNECTDELAY "arcd" // int64 indicating how long after disconnect before an auto-reconnect should occur +#define MTT_NAME_MAXASYNCCONNPERIOD "maxa" // int64 indicating how long we should wait for an async TCP connect to be established +#define MTT_NAME_LOCATION "loc" // String field representing an IPAddressAndPort of where the session connected to (or was accepted from) + +/** This little class is used to help us track when workers' output queues are empty. + * When it gets deleted (inside the internal thread), it triggers the supervisor session + * to send back an MTT_EVENT_OUTPUT_QUEUES_DRAINED event. + * It's exposed publically here only because certain (ahem) poorly written programs + * need to subclass it in order to be able to safely block until it has gone away. + */ +class DrainTag : public RefCountable, private CountedObject +{ +public: + /** Constructor */ + DrainTag() : _notify(NULL) {/* empty */} + + /** Destructor. Notifies our supervisor that we are being deleted. */ + virtual ~DrainTag(); + +private: + friend class ThreadSupervisorSession; + friend class ThreadWorkerSession; + friend class MessageTransceiverThread; + + void SetNotify(ThreadSupervisorSession * notify) {_notify = notify;} + MessageRef GetReplyMessage() const {return _replyRef;} + void SetReplyMessage(const MessageRef & ref) {_replyRef = ref;} + + ThreadSupervisorSession * _notify; + MessageRef _replyRef; +}; +DECLARE_REFTYPES(DrainTag); + +/** This is a session that represents a connection to another computer or etc. + * ThreadWorkerSessions are added to a MessageTransceiverThread's held ReflectServer + * object by the ThreadSupervisorSession on demand. + */ +class ThreadWorkerSession : public StorageReflectSession, private CountedObject +{ +public: + /** Constructor */ + ThreadWorkerSession(); + + /** Destructor */ + virtual ~ThreadWorkerSession(); + + /** Overridden to send a message back to the user */ + virtual status_t AttachedToServer(); + + /** Overridden to send a message back to the user */ + virtual void AboutToDetachFromServer(); + + /** Overridden to send a message back to the user */ + virtual bool ClientConnectionClosed(); + + /** Overridden to send a message back to the user to let him know the connection is ready */ + virtual void AsyncConnectCompleted(); + + /** Overridden to wrap incoming messages and pass them along to our supervisor session */ + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + + /** Overriden to handle messages from our supervisor session */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * userData); + + /** Returns a human-readable label for this session type: "ThreadWorker" */ + virtual const char * GetTypeName() const {return "ThreadWorker";} + + /** Overridden to clear our _drainNotifiers Queue when appropriate */ + virtual int32 DoOutput(uint32 maxBytes); + + /** Returns true iff our MessageReceivedFromGateway() method should automatically forward all Messages + * it receives from the remote peer verbatim to the ThreadSupervisorSession for presentation to the owner + * thread, or false if incoming Messages should be handled locally by our StorageReflectSession superclass. + * Defaults state is true. + */ + bool IsForwardAllIncomingMessagesToSupervisor() const {return _forwardAllIncomingMessagesToSupervisor;} + + /** Set whether Messages received from this session's remote peer should be forwarded to the + * ThreadSupervisorSession for presentation to the owner thread, or handled locally. + * Default state is true (all incoming Messages will be forwarded to the supervisor) + * @param forward true to forward all Messages, false otherwise + */ + void SetForwardAllIncomingMessagesToSupervisor(bool forward) {_forwardAllIncomingMessagesToSupervisor = forward;} + +protected: + /** Sends the specified Message to our ThreadSupervisorSession object + * @param msg The Message to send + * @param userData Can be used to pass context information to the supervisor's MessageReceivedFromSession() method, if desired. + * @returns B_NO_ERROR on success, or B_ERROR on failure (supervisor not found?) + */ + status_t SendMessageToSupervisorSession(const MessageRef & msg, void * userData = NULL); + +private: + friend class ThreadWorkerSessionFactory; + friend class ThreadSupervisorSession; + friend class MessageTransceiverThread; + + void SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(bool defaultValue); + + Queue _drainedNotifiers; + IPAddressAndPort _acceptedIAP; // if valid, this is the location where the client is connecting from + + TamperEvidentValue _forwardAllIncomingMessagesToSupervisor; + ThreadSupervisorSession * _supervisorSession; // cached for efficiency +}; +DECLARE_REFTYPES(ThreadWorkerSession); + +/** A factory class that returns new ThreadWorkerSession objects. */ +class ThreadWorkerSessionFactory : public StorageReflectSessionFactory, private CountedObject +{ +public: + /** Default Constructor. */ + ThreadWorkerSessionFactory(); + + /** Overridden to send a message back to the user */ + virtual status_t AttachedToServer(); + + /** Overridden to send a message back to the user */ + virtual void AboutToDetachFromServer(); + + /** Reimplemented to call CreateThreadWorkerSession() to create + * a new session, and on success to send a MTT_EVENT_SESSION_ACCEPTED + * Message back to the main thread. Subclasses that wish to + * to have this factory class return a different type of + * session object should override CreateThreadWorkerSession(const String &, const IPAddressAndPort &); + * instead of this method. + */ + virtual AbstractReflectSessionRef CreateSession(const String & clientAddress, const IPAddressAndPort & factoryInfo); + + /** Default implementation returns a new ThreadWorkerSession object. + * Subclasses may override this method to return a different type of + * object, as long as the returned object is a subclass of ThreadWorkerSession. + */ + virtual ThreadWorkerSessionRef CreateThreadWorkerSession(const String & clientAddress, const IPAddressAndPort & factoryInfo); + + /** Returns true iff our ThreadWorkerSessions' MessageReceivedFromGateway() methods should automatically forward all Messages + * they receive from their remote peer verbatim to the ThreadSupervisorSession for presentation to the owner + * thread, or false if incoming Messages should be handled locally by their StorageReflectSession superclass. + * Defaults state is true. + */ + bool IsForwardAllIncomingMessagesToSupervisor() const {return _forwardAllIncomingMessagesToSupervisor;} + + /** Set whether Messages received from our sessions' remote peers should be forwarded to the + * ThreadSupervisorSession for presentation to the owner thread, or handled locally. + * Default state is true (all incoming Messages will be forwarded to the supervisor) + * @param forward true to forward all Messages, false otherwise + */ + void SetForwardAllIncomingMessagesToSupervisor(bool forward) {_forwardAllIncomingMessagesToSupervisor = forward;} + +protected: + /** Sends the specified Message to our ThreadSupervisorSession object + * @param msg The Message to send + * @param userData Can be used to pass context information to the supervisor's MessageReceivedFromFactory() method, if desired. + * @returns B_NO_ERROR on success, or B_ERROR on failure (supervisor not found?) + */ + status_t SendMessageToSupervisorSession(const MessageRef & msg, void * userData = NULL); + +private: + friend class MessageTransceiverThread; + + void SetForwardAllIncomingMessagesToSupervisorIfNotAlreadySet(bool defaultValue); + + TamperEvidentValue _forwardAllIncomingMessagesToSupervisor; +}; +DECLARE_REFTYPES(ThreadWorkerSessionFactory); + +/** This is the session that acts as the main thread's agent inside the MessageTransceiverThread's + * held ReflectServer object. It accepts commands from the MessageTransceiverThread object, and + * routes messages to and from the ThreadWorkerSessions. + */ +class ThreadSupervisorSession : public StorageReflectSession, private CountedObject +{ +public: + /** Default Constructor */ + ThreadSupervisorSession(); + + /** Destructor */ + virtual ~ThreadSupervisorSession(); + + /** Overridden to neutralize any outstanding DrainTag objects */ + virtual void AboutToDetachFromServer(); + + /** Overridden to create a custom gateway for interacting with the MessageTransceiverThread */ + virtual AbstractMessageIOGatewayRef CreateGateway(); + + /** Overridden to deal with the MessageTransceiverThread. If you are subclassing + * ThreadSupervisorSession, don't override this method; override MessageReceivedFromOwner() instead. + */ + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData); + + /** Overridden to handle messages coming from the ThreadWorkerSessions. */ + virtual void MessageReceivedFromSession(AbstractReflectSession & from, const MessageRef & msg, void * userData); + + /** Overriden to handle messages from factories */ + virtual void MessageReceivedFromFactory(ReflectSessionFactory & from, const MessageRef & msg, void * userData); + + /** Overridden to end the server (and hence, the thread) if our connection to the thread is broken. + * (this shouldn't ever happen, but just in case...) + */ + virtual bool ClientConnectionClosed(); + + /** Sets the default distribution path for this session. This path, if set, determines which ThreadWorkerSessions + * are to receive outgoing data messages when the messages themselves contain no distribution path -- + * only those sessions who match the path will get the user messages. Setting the path to "" means + * to revert to sending all data messages to all ThreadWorkerSessions. + * @param path The new default distribution path. + */ + void SetDefaultDistributionPath(const String & path) {_defaultDistributionPath = path;} + + /** Returns the current default distribution path. */ + const String & GetDefaultDistributionPath() const {return _defaultDistributionPath;} + + /** Returns a human-readable label for this session type: "ThreadSupervisor" */ + virtual const char * GetTypeName() const {return "ThreadSupervisor";} + +protected: + /** Handles control messages received from the main thread. + * @param msg Reference to the message from the owner. + * @param numLeft Number of messages still pending in the message queue + * @returns B_NO_ERROR on success, or B_ERROR if the thread should go away. + */ + virtual status_t MessageReceivedFromOwner(const MessageRef & msg, uint32 numLeft); + + /** Forwards the specified Message to all the ThreadWorkerSessions specified in the + * MTT_NAME_PATH field of the Message, or if there is no such field, to all of the + * worker sessions specified in the default distribution path. + * @param distMsg the Message to forward to ThreadWorkerSessions. + */ + void SendMessageToWorkers(const MessageRef & distMsg); + +private: + friend class MessageTransceiverThread; + friend class DrainTag; + + void DrainTagIsBeingDeleted(DrainTag * tag); + status_t AddNewWorkerConnectSession(const ThreadWorkerSessionRef & sessionRef, const ip_address & hostIP, uint16 port, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod); + + Hashtable _drainTags; + String _defaultDistributionPath; + MessageTransceiverThread * _mtt; +}; +DECLARE_REFTYPES(ThreadSupervisorSession); + +/** + * This is a class that holds a ReflectServer object in an internal thread, and mediates + * between it and the calling code. It is primarily used for integrating MUSCLE networking + * with another messaging system. + * @see AMessageTransceiverThread for use with AtheOS APIs + * @see BMessageTransceiverThread for use with BeOS APIs + * @see QMessageTransceiverThread for use with Qt's APIs + */ +class MessageTransceiverThread : public Thread, private CountedObject +{ +public: + /** Constructor */ + MessageTransceiverThread(); + + /** Destructor. If the internal thread was started, you must make sure it has been + * shut down by calling ShutdownInternalThread() before deleting the MessageTransceiverThread object. + */ + virtual ~MessageTransceiverThread(); + + /** Overridden to do some additional setup, before starting the internal thread. + * @returns B_NO_ERROR on success, B_ERROR on error (out of memory, or thread is already running) + */ + virtual status_t StartInternalThread(); + + /** Asynchronously sends the given message to the specified target session(s) inside the held ReflectServer. + * May be called at any time; if called while the internal thread isn't running, the given + * message will be queued up and delivered when the internal thread is started. + * @param msgRef Reference to the message to send + * @param optDistPath Optional node path to match against to decide which ThreadWorkerSessions to send to. + * If left as NULL, the default distribution path will be used. + * @returns B_NO_ERROR if the message was enqueued successfully, or B_ERROR if out-of-memory + */ + virtual status_t SendMessageToSessions(const MessageRef & msgRef, const char * optDistPath = NULL); + + /** + * Adds a new session that will use the given socket for its I/O. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is immediately passed on through to ReflectServer::AddNewSession(). + * @param socket The TCP socket that the new session will be using, or a NULL ConstSocketRef, if the new session is to have no + * associated TCP connection. This socket becomes property of this object on success. + * @param optSessionRef Optional reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify a session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or else things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t AddNewSession(const ConstSocketRef & socket, const ThreadWorkerSessionRef & optSessionRef); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t AddNewSession(const ConstSocketRef & socket) {return AddNewSession(socket, ThreadWorkerSessionRef());} + + /** Convenience method -- calls the above method with a NULL socket reference. */ + status_t AddNewSession(const ThreadWorkerSessionRef & optSessionRef) {return AddNewSession(ConstSocketRef(), optSessionRef);} + + /** Convenience method -- calls the above method with a NULL socket and NULL session reference. */ + status_t AddNewSession() {return AddNewSession(ConstSocketRef(), ThreadWorkerSessionRef());} + + /** + * Adds a new session that will connect out to the given IP address and port. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is immediately passed on through to ReflectServer::AddNewConnectSession(). + * @param targetIPAddress IP address to connect to + * @param port Port to connect to at that IP address. + * @param optSessionRef optional Reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify a session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (optSessionRef). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t AddNewConnectSession(const ip_address & targetIPAddress, uint16 port, const ThreadWorkerSessionRef & optSessionRef, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t AddNewConnectSession(const ip_address & targetIPAddress, uint16 port, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) {return AddNewConnectSession(targetIPAddress, port, ThreadWorkerSessionRef(), autoReconnectDelay, maxAsyncConnectPeriod);} + + /** + * Adds a new session that will connect out to the given hostname and port. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the session will be added asynchronously + * to the server. If not, the call is passed immediately on through to ReflectServer::AddNewConnectSession(). + * @param targetHostName ASCII hostname or ASCII IP address to connect to. (e.g. "blah.com" or "132.239.50.8") + * @param port Port to connect to at that IP address. + * @param optSessionRef optional Reference for a session to add. If it's a NULL reference, a default ThreadWorkerSession + * will be created and used. If you do specify session here, you will want to use either a + * ThreadWorkerSession, a subclass of ThreadWorkerSession, or at least something that acts + * like one, or things won't work correctly. + * The referenced session becomes sole property of the MessageTransceiverThread on success. + * @param expandLocalhost Passed to GetHostByName(). See GetHostByName() documentation for details. Defaults to false. + * @param autoReconnectDelay If specified, this is the number of microseconds after the + * connection is broken that an automatic reconnect should be + * attempted. If not specified, an automatic reconnect will not + * be attempted, and the session will be removed when the + * connection breaks. Specifying this is equivalent to calling + * SetAutoReconnectDelay() on (optSessionRef). + * @param maxAsyncConnectPeriod If specified, this is the maximum time (in microseconds) that we will + * wait for the asynchronous TCP connection to complete. If this amount of time passes + * and the TCP connection still has not been established, we will force the connection attempt to + * abort. If not specified, the default value (as specified by MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) + * is used; typically this means that it will be left up to the operating system how long to wait + * before timing out the connection attempt. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the add command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t AddNewConnectSession(const String & targetHostName, uint16 port, const ThreadWorkerSessionRef & optSessionRef, bool expandLocalhost = false, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS); + + /** Convenience method -- calls the above method with a NULL session reference. */ + status_t AddNewConnectSession(const String & targetHostName, uint16 port, bool expandLocalhost = false, uint64 autoReconnectDelay = MUSCLE_TIME_NEVER, uint64 maxAsyncConnectPeriod = MUSCLE_MAX_ASYNC_CONNECT_DELAY_MICROSECONDS) {return AddNewConnectSession(targetHostName, port, ThreadWorkerSessionRef(), expandLocalhost, autoReconnectDelay, maxAsyncConnectPeriod);} + + /** Installs a new ReflectSessionFactory onto the ReflectServer (or replaces an existing one) on the given port. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the factory will be added to + * the ReflectServer asynchronously; otherwise the call is passed directly through to ReflectServer::PutAcceptFactory(). + * @param port The port to place the new factory on. + * @param optFactoryRef Optional reference to a factory object to use to instantiate new sessions. + * Note that in order for things to work as expected, (optFactory) should create + * only ThreadWorkerSessions (or sessions which are subclasses thereof) + * If left as NULL (the default), a default ThreadWorkerFactory will be created and used. + * The referenced factory becomes sole property of the MessageTransceiverThread on success. + * @param optInterfaceIP Optional local interface address to listen on. If not specified, or if specified + * as (invalidIP), then connections will be accepted from all local network interfaces. + * @param optRetPort If specified non-NULL, then on success the port that the factory was bound to will + * be placed into this parameter. NOTE: This argument is only useful if you are adding the + * factory synchronously... i.e. if you are calling this method before the MessageTransceiverThread's + * internal thread has been started. If the internal thread is running already, this argument + * will be ignored (because the socket binding will happen asynchronously and therefore the + * port chosen is not known in time to return it here). Defaults to NULL. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the put command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t PutAcceptFactory(uint16 port, const ThreadWorkerSessionFactoryRef & optFactoryRef, const ip_address & optInterfaceIP = invalidIP, uint16 * optRetPort = NULL); + + /** Convenience method -- calls the above method with a NULL factory reference. */ + status_t PutAcceptFactory(uint16 port) {return PutAcceptFactory(port, ThreadWorkerSessionFactoryRef());} + + /** Removes an existing ReflectSessionFactory from the held ReflectServer. + * May be called at any time, but behaves slightly differently depending on whether the internal + * thread is running or not. If the internal thread is running, the factory will be removed from + * the ReflectServer asynchronously; otherwise the call is passed directly through to ReflectServer::RemoveAcceptFactory(). + * @param port The port to remove the factory from, or zero to remove all factories. + * @param optInterfaceIP Interface(s) that the specified callbacks were assigned to in their PutAcceptFactory() call. + * This parameter is ignored when (port) is zero. + * @return B_NO_ERROR on success, or B_ERROR on failure. Note that if the internal thread is currently running, + * then success merely indicates that the remove command was enqueued successfully, not that it was executed (yet). + */ + virtual status_t RemoveAcceptFactory(uint16 port, const ip_address & optInterfaceIP = invalidIP); + + /** Stops the internal thread if it is running, destroys internal the internal ReflectServer object, and more or + * less make this MessageTransceiverThread look like it had just been constructed anew. + * @note This call will not reset the default distribution path, nor any SSL private + * or public key data that was installed via SetSSLPrivateKey() or SetSSLPublicKeyCertificate(). + */ + virtual void Reset(); + + /** + * Sets our ThreadSupervisorSession's default distribution path to (optPath). + * If called while the internal thread is running, the path change will be done asynchronously. + * This path determines which sessions get the messages sent by SendMessageToSession() if no + * path is specified explicitly there. + * Setting the path to "" indicates that you want all outgoing messages to go to all ThreadWorkerSessions. + * The change of target path will only affect the routing of messages enqueued after this call has + * returned, not ones that are currently enqueued for distribution or transmission. + * @param distPath Node path to use by default, or NULL to send to all. + * @return B_NO_ERROR if the set-target-path command was enqueued successfully, or B_ERROR if there was + * an error enqueueing it. Note that the actual change-of-path is done asynchronously. + */ + status_t SetDefaultDistributionPath(const String & distPath); + + /** Returns our current default distribution path, or "" if it is unset. */ + const String & GetDefaultDistributionPath() const {return _defaultDistributionPath;} + + /** + * Call this to get the next event notification message from the internal thread. Typically you will want to call this + * whenever your main thread has been notified that a new event may be pending. You should keep calling this method + * in a loop until it returns MTT_EVENT_NO_MORE_EVENTS; at that point it is okay to go back to waiting for the next + * event notification signal. + * @param retEventCode On success, this uint32 will be set to the event code of the returned event. + * The event code will typically be one of the following constants: + *
    + *
  1. MTT_EVENT_INCOMING_MESSAGE A new message from a remote computer is ready to process
  2. + *
  3. MTT_EVENT_SESSION_ACCEPTED A new session has been created by one of our ReflectSessionFactory objects
  4. + *
  5. MTT_EVENT_SESSION_ATTACHED A new session has been attached to the local server
  6. + *
  7. MTT_EVENT_SESSION_CONNECTED A session on the local server has completed its connection to the remote one
  8. + *
  9. MTT_EVENT_SESSION_DISCONNECTED A session on the local server got disconnected from its remote peer
  10. + *
  11. MTT_EVENT_SESSION_DETACHED A session on the local server has detached (and been destroyed)
  12. + *
  13. MTT_EVENT_FACTORY_ATTACHED A ReflectSessionFactory object has been attached to the server
  14. + *
  15. MTT_EVENT_FACTORY_DETACHED A ReflectSessionFactory object has been detached (and been destroyed)
  16. + *
  17. MTT_EVENT_OUTPUT_QUEUES_DRAINED Output queues of sessions previously specified in RequestOutputQueuesDrainedNotification() have drained
  18. + *
  19. MTT_EVENT_SERVER_EXITED The ReflectServer event loop has terminated
  20. + *
+ * May return some other code if the ThreadSupervisorSession or ThreadWorkerSessions have + * been customized to return other message types. + * @param retEventCode On successful return, the MTT_EVENT_* code for this event will be written here. + * @param optRetMsgRef If non-NULL, on success the MessageRef this argument points to is written into so that + * it references a Message associated with the event. This is mainly used with the + * MTT_EVENT_INCOMING_MESSAGE event code. + * @param optFromSession If non-NULL, the string that this argument points to will be have the root node path of + * the source AbstractReflectSession written into it (e.g. "/192.168.1.105/17"). + * @param optFromFactoryID If non-NULL, the uint32 that this arguments points to will have the factory ID of the + * source ReflectSessionFactory object written into it. + * @param optLocation If non-NULL, the IPAddressAndPort value that this points to will be filled with the IP address + * and port that the event is associated with. Note that currently only MTT_EVENT_SESSION_CONNECTED + * and MTT_EVENT_SESSION_ACCEPTED events have an associated IPAddressAndPort value. + * @returns The number of events left in the event queue (after our having removed one) on success, or -1 on failure. + */ + int32 GetNextEventFromInternalThread(uint32 & retEventCode, MessageRef * optRetMsgRef = NULL, String * optFromSession = NULL, uint32 * optFromFactoryID = NULL, IPAddressAndPort * optLocation = NULL); + + /** + * Requests that the MessageTranceiverThread object send us a MTT_EVENT_OUTPUT_QUEUES_DRAINED event + * when all the specified outgoing message queues have become empty. Which output queues are specified + * is handled the same way as SendMessageToSessions() does it -- if you specify a path here, sessions + * that match that path will be used, otherwise the default distribution path will be used. + * @param notificationMsg MessageRef to return with the MTT_EVENT_OUTPUT_QUEUES_DRAINED event. May be a NULL ref. + * @param optDistPath If non-NULL, only sessions matching this path will be watched for drainage. + * @param optDrainTag If non-NULL, this DrainTag will be used to track drainage, instead of a + * default one. Don't supply a value for this argument unless you think you + * know what you are doing. ;^) On success, (optDrainTag) becomes property + * of this MessageTransceiverThread. + * @returns B_NO_ERROR on success (in which case an MTT_EVENT_OUTPUT_QUEUES_DRAINED event will be + * forthcoming) or B_ERROR on error (out of memory). + */ + status_t RequestOutputQueuesDrainedNotification(const MessageRef & notificationMsg, const char * optDistPath = NULL, DrainTag * optDrainTag = NULL); + + /** + * Tells the specified worker session(s) to install a new input IOPolicy. + * @param pref Reference to the new IOPolicy object. Since IOPolicies are generally + * not thread safe, the referenced IOPolicy should not be used after it has + * been successfully passed in via this call. May be a NULL ref to remove + * the existing input policy. + * @param optDistPath If non-NULL, only sessions matching this path will be affected. + * A NULL path (the default) means affect all worker sessions. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetNewInputPolicy(const AbstractSessionIOPolicyRef & pref, const char * optDistPath = NULL); + + /** + * Tells the specified worker session(s) to install a new output IOPolicy. + * @param pref Reference to the new IOPolicy object. Since IOPolicies are generally + * not thread safe, the referenced IOPolicy should not be used after it has + * been successfully passed in via this call. May be a NULL ref to remove + * the existing output policy. + * @param optDistPath If non-NULL, only sessions matching this path will be affected. + * A NULL path (the default) means affect all worker sessions. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetNewOutputPolicy(const AbstractSessionIOPolicyRef & pref, const char * optDistPath = NULL); + + /** + * Tells the specified worker session(s) to switch to a different message encoding + * for the Messages they are sending to the network. Note that this only works if + * the workers are using the usual MessageIOGateways for their I/O. + * Note that ZLIB encoding is only enabled if your program is compiled with the + * -DMUSCLE_ENABLE_ZLIB_ENCODING compiler flag set. + * @param encoding one of the MUSCLE_MESSAGE_ENCODING_* constant declared in MessageIOGateway.h + * @param optDistPath If non-NULL, only sessions matching this path will be affected. + * A NULL path (the default) means affect all worker sessions. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetOutgoingMessageEncoding(int32 encoding, const char * optDistPath = NULL); + + /** + * Tells the specified worker session(s) to go away. + * @param optDistPath If non-NULL, only sessions matching this path will be affected. + * A NULL path (the default) means all worker sessions will be destroyed. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t RemoveSessions(const char * optDistPath = NULL); + + /** Returns the value that this MessageTransceiverThread will pass to the + * SetForwardAllIncomingMessagesToSupervisor() method of ThreadWorkerSessions it has just created. + * @see ThreadWorkerSession::SetForwardAllIncomingMessagesToSupervisor() + */ + bool IsForwardAllIncomingMessagesToSupervisor() const {return _forwardAllIncomingMessagesToSupervisor;} + + /** Sets the value that this MessageTransceiverThread will pass to the + * SetForwardAllIncomingMessagesToSupervisor() method of ThreadWorkerSessions it has just created. + * @param forward true iff ThreadWorkerSessions should pass all incoming Messages on to the + * ThreadSupervisorSession verbatim (aka "client mode"), or false if the ThreadWorkerSessions + * should handle incoming Messages themselves (aka "server mode"). + * @see ThreadWorkerSession::SetForwardAllIncomingMessagesToSupervisor() + * Default state is true. + */ + void SetForwardAllIncomingMessagesToSupervisor(bool forward) {_forwardAllIncomingMessagesToSupervisor = forward;} + +#ifdef MUSCLE_ENABLE_SSL + /** Sets the SSL private key data that should be used to authenticate and encrypt + * accepted incoming TCP connections. Default state is a NULL reference (i.e. no SSL + * encryption will be used for incoming connecitons). + * @param privateKey Reference to the contents of a .pem file containing both + * a PRIVATE KEY section and a CERTIFICATE section, or a NULL reference + * if you want to make SSL disabled again. + * @note this method is only available if MUSCLE_ENABLE_OPENSSL is defined. + */ + status_t SetSSLPrivateKey(const ConstByteBufferRef & privateKey); + + /** Sets the SSL public key data that should be used to authenticate and encrypt + * outgoing TCP connections. Default state is a NULL reference (i.e. no SSL + * encryption will be used for outgoing connections). + * @param publicKey Reference to the contents of a .pem file containing a CERTIFICATE + * section, or a NULL reference if you want to make SSL disabled again. + * @note this method is only available if MUSCLE_ENABLE_OPENSSL is defined. + */ + status_t SetSSLPublicKeyCertificate(const ConstByteBufferRef & publicKey); +#endif + +protected: + /** Overridden to begin execution of the ReflectServer's event loop. */ + virtual void InternalThreadEntry(); + + /** Creates and returns a new ThreadSupervisorSession to use + * to do the internal-thread-side handling of the messages this API sends. + * May be overridden if you wish to use a customized subclass instead. + * @returns a new ThreadSupervisorSession on success, or NULL on failure. + */ + virtual ThreadSupervisorSessionRef CreateSupervisorSession(); + + /** Creates and returns a ThreadWorkerSession object. Called when a new + * session is requested (e.g. in AddNewSession(), but no session is specified + * by the call. This method may be overridden to customize the type of session used. + */ + virtual ThreadWorkerSessionRef CreateDefaultWorkerSession(); + + /** Creates and returns a ThreadWorkerSessionFactory object. Called when a new + * factory is requested (e.g. in PutAcceptFactory(), but none is specified. + * This method may be overridden to customize the type of factory used. + */ + virtual ThreadWorkerSessionFactoryRef CreateDefaultSessionFactory(); + + /** Creates a new ReflectServer object and returns a reference to it. */ + virtual ReflectServerRef CreateReflectServer(); + +private: + friend class ThreadSupervisorSession; + status_t EnsureServerAllocated(); + void UpdateWorkerSessionForwardingLogic(ThreadWorkerSessionRef & sRef) const; + void UpdateWorkerSessionFactoryForwardingLogic(ThreadWorkerSessionFactoryRef & fRef) const; + status_t SendAddNewSessionMessage(const ThreadWorkerSessionRef & sessionRef, const ConstSocketRef & socket, const char * hostName, const ip_address & hostIP, uint16 port, bool expandLocalhost, uint64 autoReconnectDelay, uint64 maxAsyncConnectPeriod); + status_t SetNewPolicyAux(uint32 what, const AbstractSessionIOPolicyRef & pref, const char * optDistPath); + + ReflectServerRef _server; + String _defaultDistributionPath; +#ifdef MUSCLE_ENABLE_SSL + ConstByteBufferRef _privateKey; + ConstByteBufferRef _publicKey; +#endif + bool _forwardAllIncomingMessagesToSupervisor; +}; + +}; // end namespace muscle + +#endif + diff --git a/system/Mutex.h b/system/Mutex.h new file mode 100644 index 00000000..0ed775bf --- /dev/null +++ b/system/Mutex.h @@ -0,0 +1,279 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleMutex_h +#define MuscleMutex_h + +#include "support/MuscleSupport.h" // needed for WIN32 defines, etc + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + +#if defined(QT_CORE_LIB) // is Qt4 available? +# include // to bring in the proper value of QT_VERSION +#endif + +#if defined(QT_THREAD_SUPPORT) || (QT_VERSION >= 0x040000) +# define MUSCLE_QT_HAS_THREADS 1 +#endif + +# if defined(WIN32) +# if defined(MUSCLE_QT_HAS_THREADS) && defined(MUSCLE_PREFER_QT_OVER_WIN32) + /* empty - we don't have to do anything for this case. */ +# else +# ifndef MUSCLE_PREFER_WIN32_OVER_QT +# define MUSCLE_PREFER_WIN32_OVER_QT +# endif +# endif +# endif +# if defined(MUSCLE_USE_PTHREADS) +# include +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) +# // empty +# elif defined(MUSCLE_QT_HAS_THREADS) +# if (QT_VERSION >= 0x040000) +# include +# else +# include +# endif +# elif defined(__BEOS__) || defined(__HAIKU__) +# include +# elif defined(__ATHEOS__) +# include +# else +# error "Mutex: threading support not implemented for this platform. You'll need to add code to the MUSCLE Mutex class for your platform, or add -DMUSCLE_SINGLE_THREAD_ONLY to your build line if your program is single-threaded or for some other reason does not need to worry about locking" +# endif +#endif + +namespace muscle { + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER +# define Lock() DeadlockFinderLockWrapper (__FILE__, __LINE__) +# define Unlock() DeadlockFinderUnlockWrapper(__FILE__, __LINE__) +extern void DeadlockFinder_LogEvent(bool isLock, const void * mutexPtr, const char * fileName, int fileLine); +extern bool _enableDeadlockFinderPrints; +#define LOG_DEADLOCK_FINDER_EVENT(val) \ + if ((_enableDeadlockFinderPrints)&&(_inDeadlockCallbackCount == 0)) \ + { \ + _inDeadlockCallbackCount++; \ + DeadlockFinder_LogEvent(val, this, fileName, fileLine); \ + _inDeadlockCallbackCount--; \ + } +#endif + +// If false, then we must not assume that we are running in single-threaded mode. +// This variable should be set by the ThreadSetupSystem constructor ONLY! +extern bool _muscleSingleThreadOnly; + +/** This class is a platform-independent API for a recursive mutual exclusion semaphore (a.k.a mutex). + * Typically used to serialize the execution of critical sections in a multithreaded API + * (e.g. the MUSCLE ObjectPool or Thread classes) + * When compiling with the MUSCLE_SINGLE_THREAD_ONLY preprocessor flag defined, this class becomes a no-op. + */ +class Mutex +{ +public: + /** Constructor */ + Mutex() +#ifndef MUSCLE_SINGLE_THREAD_ONLY + : _isEnabled(_muscleSingleThreadOnly == false) +# if defined(MUSCLE_USE_PTHREADS) + // empty +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + // empty +# elif defined(MUSCLE_QT_HAS_THREADS) +# if (QT_VERSION >= 0x040000) + , _locker(QMutex::Recursive) +# else + , _locker(true) +# endif +# elif defined(__ATHEOS__) + , _locker(NULL) +# endif +#endif + { +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + _inDeadlockCallbackCount = 0; +#endif + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + if (_isEnabled) + { +# if defined(MUSCLE_USE_PTHREADS) + pthread_mutexattr_t mutexattr; + pthread_mutexattr_init(&mutexattr); // Note: If this code doesn't compile, then + pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); // you may need to add -D_GNU_SOURCE to your + pthread_mutex_init(&_locker, &mutexattr); // Linux Makefile to enable it properly. +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + InitializeCriticalSection(&_locker); +# endif + } +#endif + } + + /** Destructor. If a Mutex is destroyed while another thread is blocking in its Lock() method, + * the results are undefined. + */ + ~Mutex() {Cleanup();} + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + status_t DeadlockFinderLockWrapper(const char * fileName, int fileLine) const +#else + /** Attempts to lock the lock. + * Any thread that tries to Lock() this object while it is already locked by another thread + * will block until the other thread unlocks the lock. The lock is recursive, however; + * if a given thread calls Lock() twice in a row it won't deadlock itself (although it will + * need to call Unlock() twice in a row in order to truly unlock the lock) + * @returns B_NO_ERROR on success, or B_ERROR if the lock could not be locked for some reason. + */ + status_t Lock() const +#endif + { +#ifdef MUSCLE_SINGLE_THREAD_ONLY + return B_NO_ERROR; +#else + if (_isEnabled == false) return B_NO_ERROR; + +# if defined(MUSCLE_USE_PTHREADS) + status_t ret = (pthread_mutex_lock(&_locker) == 0) ? B_NO_ERROR : B_ERROR; +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + EnterCriticalSection(&_locker); + status_t ret = B_NO_ERROR; +# elif defined(MUSCLE_QT_HAS_THREADS) + _locker.lock(); + status_t ret = B_NO_ERROR; +# elif defined(__BEOS__) || defined(__HAIKU__) + status_t ret = _locker.Lock() ? B_NO_ERROR : B_ERROR; +# elif defined(__ATHEOS__) + status_t ret = _locker.Lock() ? B_ERROR : B_NO_ERROR; // Is this correct? Kurt's documentation sucks +# endif + +# ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + // We gotta do the logging after we are locked, otherwise our counter can suffer from race conditions + if (ret == B_NO_ERROR) LOG_DEADLOCK_FINDER_EVENT(true); +# endif + + return ret; +#endif + } + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + status_t DeadlockFinderUnlockWrapper(const char * fileName, int fileLine) const +#else + /** Unlocks the lock. Once this is done, any other thread that is blocked in the Lock() + * method will gain ownership of the lock and return. + * @returns B_NO_ERROR on success, or B_ERROR on failure (perhaps you tried to unlock a lock + * that wasn't locked? This method should never fail in typical usage) + */ + status_t Unlock() const +#endif + { +#ifdef MUSCLE_SINGLE_THREAD_ONLY + return B_NO_ERROR; +#else + if (_isEnabled == false) return B_NO_ERROR; + +# ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + // We gotta do the logging while we are still are locked, otherwise our counter can suffer from race conditions + LOG_DEADLOCK_FINDER_EVENT(false); +# endif + +# if defined(MUSCLE_USE_PTHREADS) + return (pthread_mutex_unlock(&_locker) == 0) ? B_NO_ERROR : B_ERROR; +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + LeaveCriticalSection(&_locker); + return B_NO_ERROR; +# elif defined(MUSCLE_QT_HAS_THREADS) + _locker.unlock(); + return B_NO_ERROR; +# elif defined(__BEOS__) || defined(__HAIKU__) + _locker.Unlock(); + return B_NO_ERROR; +# elif defined(__ATHEOS__) + return _locker.Unlock() ? B_ERROR : B_NO_ERROR; // Is this correct? Kurt's documentation sucks +# endif +#endif + } + + /** Turns this Mutex into a no-op object. Irreversible! */ + void Neuter() {Cleanup();} + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + void AvoidFindDeadlockCallbacks() {_inDeadlockCallbackCount++;} +#endif + +private: + void Cleanup() + { +#ifndef MUSCLE_SINGLE_THREAD_ONLY + if (_isEnabled) + { +# if defined(MUSCLE_USE_PTHREADS) + pthread_mutex_destroy(&_locker); +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + DeleteCriticalSection(&_locker); +# elif defined(MUSCLE_QT_HAS_THREADS) + // do nothing +# endif + _isEnabled = false; + } +#endif + } + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + bool _isEnabled; // if false, this Mutex is a no-op +# if defined(MUSCLE_USE_PTHREADS) + mutable pthread_mutex_t _locker; +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + mutable CRITICAL_SECTION _locker; +# elif defined(MUSCLE_QT_HAS_THREADS) + mutable QMutex _locker; +# elif defined(__BEOS__) || defined(__HAIKU__) + mutable BLocker _locker; +# elif defined(__ATHEOS__) + mutable os::Locker _locker; +# endif +#endif + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + mutable uint32 _inDeadlockCallbackCount; +#endif +}; + +/** This convenience class can be used to automatically lock/unlock a Mutex based on the MutexGuard's ctor/dtor */ +class MutexGuard +{ +public: + /** Constructor. Locks the specified Mutex. + * @param m The Mutex to lock. + */ + MutexGuard(Mutex & m) : _mutex(m) + { + if (_mutex.Lock() == B_NO_ERROR) _isMutexLocked = true; + else + { + _isMutexLocked = false; + printf("MutexGuard %p: could not lock mutex %p!\n", this, &_mutex); + } + } + + /** Destructor. Unlocks the Mutex previously specified in the constructor. */ + ~MutexGuard() + { + if ((_isMutexLocked)&&(_mutex.Unlock() != B_NO_ERROR)) printf("MutexGuard %p: could not unlock mutex %p!\n", this, &_mutex); + } + + /** Returns true iff we successfully locked our Mutex. */ + bool IsMutexLocked() const {return _isMutexLocked;} + +private: + MutexGuard(const MutexGuard &); // copy ctor, deliberately inaccessible + + Mutex & _mutex; + bool _isMutexLocked; +}; + +/** A macro to quickly and safely put a MutexGuard on the stack for the given Mutex. */ +#define DECLARE_MUTEXGUARD(mutex) MutexGuard MUSCLE_UNIQUE_NAME(mutex) + +}; // end namespace muscle + +#endif diff --git a/system/SetupSystem.cpp b/system/SetupSystem.cpp new file mode 100644 index 00000000..1a8047d0 --- /dev/null +++ b/system/SetupSystem.cpp @@ -0,0 +1,1815 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/SetupSystem.h" +#include "support/Flattenable.h" +#include "dataio/DataIO.h" +#include "util/ObjectPool.h" +#include "util/MiscUtilityFunctions.h" // for ExitWithoutCleanup() +#include "util/DebugTimer.h" +#include "util/CountedObject.h" +#include "util/String.h" +#include "system/GlobalMemoryAllocator.h" + +#ifdef MUSCLE_ENABLE_SSL +# include +# include +# ifndef WIN32 +# include +# endif +#endif + +#ifdef WIN32 +# include +# include +#else +# if defined(__BEOS__) || defined(__HAIKU__) +# include +# elif defined(__CYGWIN__) +# include +# include +# include +# elif defined(__QNX__) +# include +# include +# elif defined(SUN) || defined(__sparc__) || defined(sun386) +# include +# include +# include +# elif defined(ANDROID) +# include +# include +# else +# include // changed signal.h to sys/signal.h to work with OS/X +# include +# endif +#endif + +#if defined(__BORLANDC__) +# include +# include +#endif + +#if defined(__APPLE__) +# include +#endif + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER +# include "system/AtomicCounter.h" +# include "system/ThreadLocalStorage.h" +#endif + +namespace muscle { + +#ifdef MUSCLE_COUNT_STRING_COPY_OPERATIONS +uint32 _stringOpCounts[NUM_STRING_OPS] = {0}; +#endif + +#ifdef MUSCLE_SINGLE_THREAD_ONLY +bool _muscleSingleThreadOnly = true; +#else +bool _muscleSingleThreadOnly = false; +#endif + +#ifdef MUSCLE_CATCH_SIGNALS_BY_DEFAULT +bool _mainReflectServerCatchSignals = true; +#else +bool _mainReflectServerCatchSignals = false; +#endif + +static Mutex * _muscleLock = NULL; +Mutex * GetGlobalMuscleLock() {return _muscleLock;} + +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) +Mutex * _muscleAtomicMutexes = NULL; // used by DoMutexAtomicIncrement() +#endif + +static uint32 _threadSetupCount = 0; +#ifndef MUSCLE_SINGLE_THREAD_ONLY +static muscle_thread_id _mainThreadID; +#endif + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER +# ifdef MUSCLE_DEFAULT_RUNTIME_DISABLE_DEADLOCK_FINDER +bool _enableDeadlockFinderPrints = false; +# else +bool _enableDeadlockFinderPrints = true; +# endif +#endif + +static uint32 _failedMemoryRequestSize = MUSCLE_NO_LIMIT; // start with an obviously-invalid guard value +uint32 GetAndClearFailedMemoryRequestSize() +{ + uint32 ret = _failedMemoryRequestSize; // yes, it's racy. But I'll live with that for now. + _failedMemoryRequestSize = MUSCLE_NO_LIMIT; + return ret; +} +void SetFailedMemoryRequestSize(uint32 numBytes) {_failedMemoryRequestSize = numBytes;} + +// This was moved here so that it will be present even when +// GlobalMemoryAllocator.cpp isn't linked in. +static MemoryAllocatorRef _globalAllocatorRef; +void SetCPlusPlusGlobalMemoryAllocator(const MemoryAllocatorRef & maRef) {_globalAllocatorRef = maRef;} +const MemoryAllocatorRef & GetCPlusPlusGlobalMemoryAllocator() {return _globalAllocatorRef;} + +static int swap_memcmp(const void * vp1, const void * vp2, uint32 numBytes) +{ + const uint8 * p1 = (const uint8 *) vp1; + const uint8 * p2 = (const uint8 *) vp2; + for (uint32 i=0; iIsFull())) + { + MutexEventBlock * newBlock = (MutexEventBlock *) malloc(sizeof(MutexEventBlock)); // THIS LINE CAN ONLY CALL plain old malloc() and nothing else!!! + if (newBlock) + { + newBlock->Initialize(); + if (_headBlock == NULL) _headBlock = newBlock; + if (_tailBlock) _tailBlock->_nextBlock = newBlock; + _tailBlock = newBlock; + } + else + { + printf("MutexEventLog::AddEvent(): malloc() failed!\n"); // what else to do? Even WARN_OUT_OF_MEMORY isn't safe here + return; + } + } + if (_tailBlock) _tailBlock->AddEvent(isLock, mutexPtr, fileName, fileLine); + } + + void PrintToStream() const + { + MutexEventBlock * meb = _headBlock; + while(meb) + { + meb->PrintToStream(_threadID); + meb = meb->_nextBlock; + } + } + +private: + class MutexEventBlock + { + public: + MutexEventBlock() {/* empty */} + + void Initialize() {_validCount = 0; _nextBlock = NULL;} + bool IsFull() const {return (_validCount == ARRAYITEMS(_events));} + void AddEvent(bool isLock, const void * mutexPtr, const char * fileName, int fileLine) {_events[_validCount++] = MutexEvent(isLock, mutexPtr, fileName, fileLine);} + void PrintToStream(const muscle_thread_id & tid) const {for (uint32 i=0; i<_validCount; i++) _events[i].PrintToStream(tid);} + + private: + friend class MutexEventLog; + + class MutexEvent + { + public: + MutexEvent() {/* empty */} + + MutexEvent(bool isLock, const void * mutexPtr, const char * fileName, uint32 fileLine) : _fileLine(fileLine | (isLock?(1L<<31):0)), _mutexPtr(mutexPtr) + { + const char * lastSlash = strrchr(fileName, '/'); + if (lastSlash) fileName = lastSlash+1; + + strncpy(_fileName, fileName, sizeof(_fileName)); + _fileName[sizeof(_fileName)-1] = '\0'; + } + + void PrintToStream(const muscle_thread_id & threadID) const + { + char buf[20]; + printf("%s: tid=%s m=%p loc=%s:" UINT32_FORMAT_SPEC "\n", (_fileLine&(1L<<31))?"mx_lock":"mx_unlk", threadID.ToString(buf), _mutexPtr, _fileName, (uint32)(_fileLine&~(1L<<31))); + } + + private: + uint32 _fileLine; + const void * _mutexPtr; + char _fileName[48]; + }; + + MutexEventBlock * _nextBlock; + uint32 _validCount; + MutexEvent _events[4096]; + }; + + muscle_thread_id _threadID; + MutexEventBlock * _headBlock; + MutexEventBlock * _tailBlock; +}; + +static ThreadLocalStorage _mutexEventLogs(false); // false argument is necessary otherwise we can't read the threads' logs after they've gone away! +static Mutex _logsTableMutex; +static Queue _logsTable; // read at process-shutdown time (I use a Queue rather than a Hashtable because muscle_thread_id isn't usable as a Hashtable key) + +void DeadlockFinder_LogEvent(bool isLock, const void * mutexPtr, const char * fileName, int fileLine) +{ + MutexEventLog * mel = _mutexEventLogs.GetThreadLocalObject(); + if (mel == NULL) + { + mel = (MutexEventLog *) malloc(sizeof(MutexEventLog)); // MUST CALL malloc() here to avoid inappropriate re-entrancy! + if (mel) + { + mel->Initialize(muscle_thread_id::GetCurrentThreadID()); + _mutexEventLogs.SetThreadLocalObject(mel); + if (_logsTableMutex.Lock() == B_NO_ERROR) + { + _logsTable.AddTail(mel); + _logsTableMutex.Unlock(); + } + } + } + if (mel) mel->AddEvent(isLock, mutexPtr, fileName, fileLine); + else printf("DeadlockFinder_LogEvent: malloc failed!?\n"); // we can't even call WARN_OUT_OF_MEMORY here +} + +static void DeadlockFinder_ProcessEnding() +{ + for (uint32 i=0; i<_logsTable.GetNumItems(); i++) _logsTable[i]->PrintToStream(); + _logsTableMutex.Unlock(); +} + +#endif + +ThreadSetupSystem :: ThreadSetupSystem(bool muscleSingleThreadOnly) +{ + if (++_threadSetupCount == 1) + { +#ifdef MUSCLE_SINGLE_THREAD_ONLY + (void) muscleSingleThreadOnly; // shut the compiler up +#else + _mainThreadID = muscle_thread_id::GetCurrentThreadID(); + _muscleSingleThreadOnly = muscleSingleThreadOnly; + if (_muscleSingleThreadOnly) _lock.Neuter(); // if we're single-thread, then this Mutex can be a no-op! +#endif + _muscleLock = &_lock; + +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) + _muscleAtomicMutexes = newnothrow_array(Mutex, MUSCLE_MUTEX_POOL_SIZE); + MASSERT(_muscleAtomicMutexes, "Could not allocate atomic mutexes!"); +#endif + } +} + +ThreadSetupSystem :: ~ThreadSetupSystem() +{ + if (--_threadSetupCount == 0) + { +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) + delete [] _muscleAtomicMutexes; _muscleAtomicMutexes = NULL; +#endif + _muscleLock = NULL; + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + DeadlockFinder_ProcessEnding(); +#endif + } +} + +static uint32 _networkSetupCount = 0; + +#if defined(MUSCLE_ENABLE_SSL) && !defined(MUSCLE_SINGLE_THREAD_ONLY) + +// OpenSSL thread-safety-callback setup code provided by Tosha at +// http://stackoverflow.com/questions/3417706/openssl-and-multi-threads/12810000#12810000 +# if defined(WIN32) +# define OPENSSL_MUTEX_TYPE HANDLE +# define OPENSSL_MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL) +# define OPENSSL_MUTEX_CLEANUP(x) CloseHandle(x) +# define OPENSSL_MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE) +# define OPENSSL_MUTEX_UNLOCK(x) ReleaseMutex(x) +# define OPENSSL_THREAD_ID GetCurrentThreadId() +# else +# define OPENSSL_MUTEX_TYPE pthread_mutex_t +# define OPENSSL_MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) +# define OPENSSL_MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) +# define OPENSSL_MUTEX_LOCK(x) pthread_mutex_lock(&(x)) +# define OPENSSL_MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) +# define OPENSSL_THREAD_ID pthread_self() +# endif + +/* This array will store all of the mutexes available to OpenSSL. */ +static OPENSSL_MUTEX_TYPE *mutex_buf=NULL; + +static void openssl_locking_function(int mode, int n, const char * file, int line) +{ + (void) file; + (void) line; + if (mode & CRYPTO_LOCK) OPENSSL_MUTEX_LOCK(mutex_buf[n]); + else OPENSSL_MUTEX_UNLOCK(mutex_buf[n]); +} + +static unsigned long openssl_id_function(void) {return ((unsigned long)OPENSSL_THREAD_ID);} + +static int openssl_thread_setup(void) +{ + mutex_buf = (OPENSSL_MUTEX_TYPE *) malloc(CRYPTO_num_locks() * sizeof(OPENSSL_MUTEX_TYPE)); + if (!mutex_buf) return -1; + for (int i=0; i 0)&&(QueryPerformanceCounter(&curTicks))) + { + uint64 checkGetTime = ((uint64)timeGetTime())*1000; + ret = (curTicks.QuadPart*MICROS_PER_SECOND)/_ticksPerSecond; + + // Hack-around for evil Windows/hardware bug in QueryPerformanceCounter(). + // see http://support.microsoft.com/default.aspx?scid=kb;en-us;274323 + static uint64 _lastCheckGetTime = 0; + static uint64 _lastCheckQPCTime = 0; + if (_lastCheckGetTime > 0) + { + uint64 getTimeElapsed = checkGetTime - _lastCheckGetTime; + uint64 qpcTimeElapsed = ret - _lastCheckQPCTime; + if ((muscleMax(getTimeElapsed, qpcTimeElapsed) - muscleMin(getTimeElapsed, qpcTimeElapsed)) > 500000) + { + //LogTime(MUSCLE_LOG_DEBUG, "QueryPerformanceCounter() is buggy, reverting to timeGetTime() method instead!\n"); + _brokenQPCOffset = (_lastCheckQPCTime-_lastCheckGetTime); + ret = (((uint64)timeGetTime())*1000) + _brokenQPCOffset; + } + } + _lastCheckGetTime = checkGetTime; + _lastCheckQPCTime = ret; + } + } +# endif + if (ret == 0) + { + static uint32 _prevVal = 0; + static uint64 _wrapOffset = 0; + + uint32 newVal = (uint32) timeGetTime(); + if (newVal < _prevVal) _wrapOffset += (((uint64)1)<<32); + ret = (_wrapOffset+newVal)*1000; // convert to microseconds + _prevVal = newVal; + } + _rtMutex.Unlock(); + } + return ret; +# elif defined(__APPLE__) + static bool _init = true; + static mach_timebase_info_data_t _timebase; + if (_init) {_init = false; (void) mach_timebase_info(&_timebase);} + return (uint64)((mach_absolute_time() * _timebase.numer) / (1000 * _timebase.denom)); +# else +# if defined(MUSCLE_USE_POWERPC_INLINE_ASSEMBLY) && defined(MUSCLE_POWERPC_TIMEBASE_HZ) + TCHECKPOINT; + while(1) + { + uint32 hi1 = get_tbu(); + uint32 low = get_tbl(); + uint32 hi2 = get_tbu(); + if (hi1 == hi2) + { + // FogBugz #3199 + uint64 cycles = ((((uint64)hi1)<<32)|((uint64)low)); + return ((cycles/MUSCLE_POWERPC_TIMEBASE_HZ)*MICROS_PER_SECOND)+(((cycles%MUSCLE_POWERPC_TIMEBASE_HZ)*(MICROS_PER_SECOND))/MUSCLE_POWERPC_TIMEBASE_HZ); + } + } +# elif defined(MUSCLE_USE_LIBRT) && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec ts; + return (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) ? (SecondsToMicros(ts.tv_sec)+NanosToMicros(ts.tv_nsec)) : 0; +# else + // default implementation: use POSIX API + static clock_t _ticksPerSecond = 0; + if (_ticksPerSecond <= 0) _ticksPerSecond = sysconf(_SC_CLK_TCK); + if (_ticksPerSecond > 0) + { + if (sizeof(clock_t) > 4) + { + // Easy case: with a wide clock_t, we don't need to worry about it wrapping + struct tms junk; clock_t newTicks = (clock_t) times(&junk); + return ((((uint64)newTicks)*MICROS_PER_SECOND)/_ticksPerSecond); + } + else + { + // Oops, clock_t is skinny enough that it might wrap. So we need to watch for that. + static Mutex _rtMutex; + if (_rtMutex.Lock() == B_NO_ERROR) + { + static uint32 _prevVal; + static uint64 _wrapOffset = 0; + + struct tms junk; clock_t newTicks = (clock_t) times(&junk); + uint32 newVal = (uint32) newTicks; + if (newVal < _prevVal) _wrapOffset += (((uint64)1)<<32); + uint64 ret = ((_wrapOffset+newVal)*MICROS_PER_SECOND)/_ticksPerSecond; // convert to microseconds + _prevVal = newTicks; + + _rtMutex.Unlock(); + return ret; + } + } + } + return 0; // Oops? +# endif +# endif +} +#endif + +#if !(defined(__BEOS__) || defined(__HAIKU__)) +status_t Snooze64(uint64 micros) +{ + if (micros == MUSCLE_TIME_NEVER) while(Snooze64(DaysToMicros(1)) == B_NO_ERROR) {/* empty */} + +#if __ATHEOS__ + return (snooze(micros) >= 0) ? B_NO_ERROR : B_ERROR; +#elif WIN32 + Sleep((DWORD)((micros/1000)+(((micros%1000)!=0)?1:0))); + return B_NO_ERROR; +#elif defined(MUSCLE_USE_LIBRT) && defined(_POSIX_MONOTONIC_CLOCK) + const struct timespec ts = {MicrosToSeconds(micros), MicrosToNanos(micros%MICROS_PER_SECOND)}; + return (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL) == 0) ? B_NO_ERROR : B_ERROR; +#else + /** We can use select(), if nothing else */ + struct timeval waitTime; + Convert64ToTimeVal(micros, waitTime); + return (select(0, NULL, NULL, NULL, &waitTime) >= 0) ? B_NO_ERROR : B_ERROR; +#endif +} + + +#ifdef WIN32 +// Broken out so ParseHumanReadableTimeValues() can use it also +uint64 __Win32FileTimeToMuscleTime(const FILETIME & ft) +{ + union { + uint64 ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } theTime; + theTime.ft = ft; + + static const uint64 TIME_DIFF = ((uint64)116444736)*NANOS_PER_SECOND; + struct timeval tv; + tv.tv_usec = (long)((theTime.ns100 / ((uint64)10)) % MICROS_PER_SECOND); + tv.tv_sec = (long)((theTime.ns100 - TIME_DIFF) / (10*MICROS_PER_SECOND)); + return ConvertTimeValTo64(tv); +} +#endif + +#endif /* !__BEOS__ && !__HAIKU__ */ + +/** Defined here since every MUSCLE program will have to include this file anyway... */ +uint64 GetCurrentTime64(uint32 timeType) +{ +#ifdef WIN32 + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + if (timeType == MUSCLE_TIMEZONE_LOCAL) (void) FileTimeToLocalFileTime(&ft, &ft); + return __Win32FileTimeToMuscleTime(ft); +#else +# if defined(__BEOS__) || defined(__HAIKU__) + uint64 ret = real_time_clock_usecs(); +# else + struct timeval tv; + gettimeofday(&tv, NULL); + uint64 ret = ConvertTimeValTo64(tv); +# endif + if (timeType == MUSCLE_TIMEZONE_LOCAL) + { + time_t now = time(NULL); +# if defined(__BEOS__) && !defined(__HAIKU__) + struct tm * tm = gmtime(&now); +# else + struct tm gmtm; + struct tm * tm = gmtime_r(&now, &gmtm); +# endif + if (tm) + { + ret += SecondsToMicros(now-mktime(tm)); + if (tm->tm_isdst>0) ret += HoursToMicros(1); // FogBugz #4498 + } + } + return ret; +#endif +} + +#if MUSCLE_TRACE_CHECKPOINTS > 0 +static volatile uint32 _defaultTraceLocation[MUSCLE_TRACE_CHECKPOINTS]; +volatile uint32 * _muscleTraceValues = _defaultTraceLocation; +uint32 _muscleNextTraceValueIndex = 0; + +void SetTraceValuesLocation(volatile uint32 * location) +{ + _muscleTraceValues = location ? location : _defaultTraceLocation; + _muscleNextTraceValueIndex = 0; + for (uint32 i=0; iLock() != B_NO_ERROR)) m = NULL; + + // Append us to the front of the linked list + if (_firstRecycler) _firstRecycler->_prev = this; + _prev = NULL; + _next = _firstRecycler; + _firstRecycler = this; + + if (m) m->Unlock(); +} + +AbstractObjectRecycler :: ~AbstractObjectRecycler() +{ + Mutex * m = GetGlobalMuscleLock(); + if ((m)&&(m->Lock() != B_NO_ERROR)) m = NULL; + + // Remove us from the linked list + if (_prev) _prev->_next = _next; + if (_next) _next->_prev = _prev; + if (_firstRecycler == this) _firstRecycler = _next; + + if (m) m->Unlock(); +} + +void AbstractObjectRecycler :: GlobalFlushAllCachedObjects() +{ + Mutex * m = GetGlobalMuscleLock(); + if ((m)&&(m->Lock() != B_NO_ERROR)) m = NULL; + + // We restart at the head of the list anytime anything is flushed, + // for safety. When we get to the end of the list, everything has + // been flushed. + AbstractObjectRecycler * r = _firstRecycler; + while(r) r = (r->FlushCachedObjects() > 0) ? _firstRecycler : r->_next; + + if (m) m->Unlock(); +} + +static CompleteSetupSystem * _activeCSS = NULL; +CompleteSetupSystem * CompleteSetupSystem :: GetCurrentCompleteSetupSystem() {return _activeCSS;} + +CompleteSetupSystem :: CompleteSetupSystem(bool muscleSingleThreadOnly) : _threads(muscleSingleThreadOnly), _prevInstance(_activeCSS) +{ + _activeCSS = this; // push us onto the stack +} + +CompleteSetupSystem :: ~CompleteSetupSystem() +{ + // We'll assume that by this point all spawned threads are gone, and therefore mutex-ordering problems detected after this are not real problems. +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + _enableDeadlockFinderPrints = false; +#endif + + GenericCallbackRef r; + while(_cleanupCallbacks.RemoveTail(r) == B_NO_ERROR) (void) r()->Callback(NULL); + + AbstractObjectRecycler::GlobalFlushAllCachedObjects(); + + _activeCSS = _prevInstance; // pop us off the stack +} + +// Implemented here so that every program doesn't have to link +// in MiscUtilityFunctions.cpp just for this function. +void ExitWithoutCleanup(int exitCode) +{ + _exit(exitCode); +} + +uint32 DataIO :: WriteFully(const void * buffer, uint32 size) +{ + const uint8 * b = (const uint8 *)buffer; + const uint8 * firstInvalidByte = b+size; + while(b < firstInvalidByte) + { + int32 bytesWritten = Write(b, (uint32)(firstInvalidByte-b)); + if (bytesWritten <= 0) break; + b += bytesWritten; + } + return (uint32) (b-((const uint8 *)buffer)); +} + +uint32 DataIO :: ReadFully(void * buffer, uint32 size) +{ + uint8 * b = (uint8 *) buffer; + uint8 * firstInvalidByte = b+size; + while(b < firstInvalidByte) + { + int32 bytesRead = Read(b, (uint32) (firstInvalidByte-b)); + if (bytesRead <= 0) break; + b += bytesRead; + } + return (uint32) (b-((const uint8 *)buffer)); +} + +int64 DataIO :: GetLength() +{ + int64 origPos = GetPosition(); + if ((origPos >= 0)&&(Seek(0, IO_SEEK_END) == B_NO_ERROR)) + { + int64 ret = GetPosition(); + if (Seek(origPos, IO_SEEK_SET) == B_NO_ERROR) return ret; + } + return -1; // error! +} + +status_t Flattenable :: FlattenToDataIO(DataIO & outputStream, bool addSizeHeader) const +{ + uint8 smallBuf[256]; + uint8 * bigBuf = NULL; + uint32 fs = FlattenedSize(); + uint32 bufSize = fs+(addSizeHeader?sizeof(uint32):0); + + uint8 * b; + if (bufSize<=ARRAYITEMS(smallBuf)) b = smallBuf; + else + { + b = bigBuf = newnothrow_array(uint8, bufSize); + if (bigBuf == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + + // Populate the buffer + if (addSizeHeader) + { + muscleCopyOut(b, B_HOST_TO_LENDIAN_INT32(fs)); + Flatten(b+sizeof(uint32)); + } + else Flatten(b); + + // And finally, write out the buffer + status_t ret = (outputStream.WriteFully(b, bufSize) == bufSize) ? B_NO_ERROR : B_ERROR; + delete [] bigBuf; + return ret; +} + +status_t Flattenable :: UnflattenFromDataIO(DataIO & inputStream, int32 optReadSize, uint32 optMaxReadSize) +{ + uint32 readSize = (uint32) optReadSize; + if (optReadSize < 0) + { + uint32 leSize; + if (inputStream.ReadFully(&leSize, sizeof(leSize)) != sizeof(leSize)) return B_ERROR; + readSize = (uint32) B_LENDIAN_TO_HOST_INT32(leSize); + if (readSize > optMaxReadSize) return B_ERROR; + } + + uint8 smallBuf[256]; + uint8 * bigBuf = NULL; + uint8 * b; + if (readSize<=ARRAYITEMS(smallBuf)) b = smallBuf; + else + { + b = bigBuf = newnothrow_array(uint8, readSize); + if (bigBuf == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + } + + status_t ret = (inputStream.ReadFully(b, readSize) == readSize) ? Unflatten(b, readSize) : B_ERROR; + delete [] bigBuf; + return ret; +} + +status_t Flattenable :: CopyFromImplementation(const Flattenable & copyFrom) +{ + uint8 smallBuf[256]; + uint8 * bigBuf = NULL; + uint32 flatSize = copyFrom.FlattenedSize(); + if (flatSize > ARRAYITEMS(smallBuf)) + { + bigBuf = newnothrow_array(uint8, flatSize); + if (bigBuf == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } + copyFrom.Flatten(bigBuf ? bigBuf : smallBuf); + status_t ret = Unflatten(bigBuf ? bigBuf : smallBuf, flatSize); + delete [] bigBuf; + return ret; +} + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) +extern void NotifySocketMultiplexersThatSocketIsClosed(int fd); +#endif + +// This function is now a private one, since it should no longer be necessary to call it +// from user code. Instead, attach any socket file descriptors you create to ConstSocketRef +// objects by calling GetConstSocketRefFromPool(fd), and the file descriptors will be automatically +// closed when the last ConstSocketRef that references them is destroyed. +static void CloseSocket(int fd) +{ + if (fd >= 0) + { +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + // We have to do this, otherwise a socket fd value can get re-used before the next call + // to WaitForEvents(), causing the SocketMultiplexers to fail to update their in-kernel state. + NotifySocketMultiplexersThatSocketIsClosed(fd); +#endif + +#if defined(WIN32) || defined(BEOS_OLD_NETSERVER) + ::closesocket(fd); +#else + close(fd); +#endif + } +} + +const ConstSocketRef & GetInvalidSocket() +{ + static const ConstSocketRef _ref(&GetDefaultObjectForType(), false); + return _ref; +} + +ConstSocketRef GetConstSocketRefFromPool(int fd, bool okayToClose, bool returnNULLOnInvalidFD) +{ + static ConstSocketRef::ItemPool _socketPool; + + if ((fd < 0)&&(returnNULLOnInvalidFD)) return ConstSocketRef(); + else + { + Socket * s = _socketPool.ObtainObject(); + ConstSocketRef ret(s); + + if (s) + { + s->SetFileDescriptor(fd, okayToClose); +#ifdef WIN32 + // FogBugz #9911: Make the socket un-inheritable, since that + // is the behavior you want 99% of the time. (Anyone who wants + // to inherit the socket will have to either avoid calling this + // for those sockets, or call SetHandleInformation() again + // afterwards to reinstate the inherit-handle flag) + (void) SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0); +#endif + } + else if (okayToClose) CloseSocket(fd); + + return ret; + } +} + +Socket :: ~Socket() +{ + Clear(); +} + +void Socket :: SetFileDescriptor(int newFD, bool okayToClose) +{ + if (newFD != _fd) + { + if (_okayToClose) CloseSocket(_fd); // CloseSocket(-1) is a no-op, so no need to check fd twice + _fd = newFD; + } + _okayToClose = okayToClose; +} + +static void FlushStringAsciiChars(String & s, int idx, char * ascBuf, char * hexBuf, uint32 count, uint32 numColumns) +{ + while(count 0) FlushAsciiChars(optFile, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } + else fprintf(optFile, "NULL buffer\n"); + } +} + +void PrintHexBytes(const ByteBuffer & bb, const char * optDesc, uint32 numColumns, FILE * optFile) +{ + PrintHexBytes(bb.GetBuffer(), bb.GetNumBytes(), optDesc, numColumns, optFile); +} + +void PrintHexBytes(const ConstByteBufferRef & bbRef, const char * optDesc, uint32 numColumns, FILE * optFile) +{ + PrintHexBytes(bbRef()?bbRef()->GetBuffer():NULL, bbRef()?bbRef()->GetNumBytes():0, optDesc, numColumns, optFile); +} + +void PrintHexBytes(const Queue & buf, const char * optDesc, uint32 numColumns, FILE * optFile) +{ + if (optFile == NULL) optFile = stdout; + + uint32 numBytes = buf.GetNumItems(); + if (numColumns == 0) + { + // A simple, single-line format + if (optDesc) fprintf(optFile, "%s: ", optDesc); + fprintf(optFile, "["); + for (uint32 i=0; i 0) FlushAsciiChars(optFile, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } +} + +void LogHexBytes(int logLevel, const void * vbuf, uint32 numBytes, const char * optDesc, uint32 numColumns) +{ + const uint8 * buf = (const uint8 *) vbuf; + + if (numColumns == 0) + { + // A simple, single-line format + if (optDesc) LogTime(logLevel, "%s: ", optDesc); + Log(logLevel, "["); + if (buf) for (uint32 i=0; i 0) FlushLogAsciiChars(logLevel, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } + else LogTime(logLevel, "NULL buffer\n"); + } +} + +void LogHexBytes(int logLevel, const Queue & buf, const char * optDesc, uint32 numColumns) +{ + uint32 numBytes = buf.GetNumItems(); + if (numColumns == 0) + { + // A simple, single-line format + if (optDesc) LogTime(logLevel, "%s: ", optDesc); + Log(logLevel, "["); + for (uint32 i=0; i 0) FlushLogAsciiChars(logLevel, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } +} + +void LogHexBytes(int logLevel, const ByteBuffer & bb, const char * optDesc, uint32 numColumns) +{ + LogHexBytes(logLevel, bb.GetBuffer(), bb.GetNumBytes(), optDesc, numColumns); +} + +void LogHexBytes(int logLevel, const ConstByteBufferRef & bbRef, const char * optDesc, uint32 numColumns) +{ + LogHexBytes(logLevel, bbRef()?bbRef()->GetBuffer():NULL, bbRef()?bbRef()->GetNumBytes():0, optDesc, numColumns); +} + +String HexBytesToAnnotatedString(const void * vbuf, uint32 numBytes, const char * optDesc, uint32 numColumns) +{ + String ret; + + const uint8 * buf = (const uint8 *) vbuf; + if (numColumns == 0) + { + // A simple, single-line format + if (optDesc) {ret += optDesc; ret += ": ";} + ret += '['; + if (buf) for (uint32 i=0; i 0) FlushStringAsciiChars(ret, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } + else ret += "NULL buffer"; + } + return ret; +} + +String HexBytesToAnnotatedString(const Queue & buf, const char * optDesc, uint32 numColumns) +{ + String ret; + + uint32 numBytes = buf.GetNumItems(); + if (numColumns == 0) + { + // A simple, single-line format + if (optDesc) {ret += optDesc; ret += ": ";} + ret += '['; + for (uint32 i=0; i 0) FlushStringAsciiChars(ret, numBytes-leftovers, ascBuf, hexBuf, leftovers, numColumns); + } + else WARN_OUT_OF_MEMORY; + + delete [] ascBuf; + delete [] hexBuf; + } + return ret; +} + +String HexBytesToAnnotatedString(const ByteBuffer & bb, const char * optDesc, uint32 numColumns) +{ + return HexBytesToAnnotatedString(bb.GetBuffer(), bb.GetNumBytes(), optDesc, numColumns); +} + +String HexBytesToAnnotatedString(const ConstByteBufferRef & bbRef, const char * optDesc, uint32 numColumns) +{ + return HexBytesToAnnotatedString(bbRef()?bbRef()->GetBuffer():NULL, bbRef()?bbRef()->GetNumBytes():0, optDesc, numColumns); +} + +DebugTimer :: DebugTimer(const String & title, uint64 mlt, uint32 startMode, int debugLevel) : _currentMode(startMode+1), _title(title), _minLogTime(mlt), _debugLevel(debugLevel), _enableLog(true) +{ + SetMode(startMode); + _startTime = MUSCLE_DEBUG_TIMER_CLOCK; // re-set it here so that we don't count the Hashtable initialization! +} + +DebugTimer :: ~DebugTimer() +{ + if (_enableLog) + { + // Finish off the current mode + uint64 * curElapsed = _modeToElapsedTime.Get(_currentMode); + if (curElapsed) *curElapsed += MUSCLE_DEBUG_TIMER_CLOCK-_startTime; + + // And print out our stats + for (HashtableIterator iter(_modeToElapsedTime); iter.HasData(); iter++) + { + uint64 nextTime = iter.GetValue(); + if (nextTime >= _minLogTime) + { + if (_debugLevel >= 0) + { + if (nextTime >= 1000) LogTime(_debugLevel, "%s: mode " UINT32_FORMAT_SPEC ": " UINT64_FORMAT_SPEC " milliseconds elapsed\n", _title(), iter.GetKey(), nextTime/1000); + else LogTime(_debugLevel, "%s: mode " UINT32_FORMAT_SPEC ": " UINT64_FORMAT_SPEC " microseconds elapsed\n", _title(), iter.GetKey(), nextTime); + } + else + { + // For cases where we don't want to call LogTime() + if (nextTime >= 1000) printf("%s: mode " UINT32_FORMAT_SPEC ": " UINT64_FORMAT_SPEC " milliseconds elapsed\n", _title(), iter.GetKey(), nextTime/1000); + else printf("%s: mode " UINT32_FORMAT_SPEC ": " UINT64_FORMAT_SPEC " microseconds elapsed\n", _title(), iter.GetKey(), nextTime); + } + } + } + } +} + +/** Gotta define this myself, since atoll() isn't standard. :^( + * Note that this implementation doesn't handle negative numbers! + */ +uint64 Atoull(const char * str) +{ + TCHECKPOINT; + + const char * s = str; + if (muscleInRange(*s, '0', '9') == false) return 0; + + uint64 base = 1; + uint64 ret = 0; + + // Move to the last digit in the number + while(muscleInRange(*s, '0', '9')) s++; + + // Then iterate back to the beginning, tabulating as we go + while((--s >= str)&&(*s >= '0')&&(*s <= '9')) + { + ret += base * ((uint64)(*s-'0')); + base *= (uint64)10; + } + return ret; +} + +int64 Atoll(const char * str) +{ + TCHECKPOINT; + + bool negative = false; + const char * s = str; + while(*s == '-') + { + negative = (negative == false); + s++; + } + int64 ret = (int64) Atoull(s); + return negative ? -ret : ret; +} + +/** Set the timer to record elapsed time to a different mode. */ +void DebugTimer :: SetMode(uint32 newMode) +{ + if (newMode != _currentMode) + { + uint64 * curElapsed = _modeToElapsedTime.Get(_currentMode); + if (curElapsed) *curElapsed += MUSCLE_DEBUG_TIMER_CLOCK-_startTime; + + _currentMode = newMode; + (void) _modeToElapsedTime.GetOrPut(_currentMode, 0); + _startTime = MUSCLE_DEBUG_TIMER_CLOCK; + } +} + +#ifdef MUSCLE_SINGLE_THREAD_ONLY +bool IsCurrentThreadMainThread() {return true;} +#else +bool IsCurrentThreadMainThread() +{ + if (_threadSetupCount > 0) return (_mainThreadID == muscle_thread_id::GetCurrentThreadID()); + else + { + MCRASH("IsCurrentThreadMainThread() cannot be called unless there is a CompleteSetupSystem object on the stack!"); + return false; // to shut the compiler up + } +} +#endif + +uint32 CalculateHashCode(const void * key, uint32 numBytes, uint32 seed) +{ +#define MURMUR2_MIX(h,k,m) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; } + const uint32 m = 0x5bd1e995; + const int32 r = 24; + + const unsigned char * data = (const unsigned char *)key; + uint32 h = seed ^ numBytes; + uint32 align = ((uint32)((uintptr)data)) & 3; + if ((align!=0)&&(numBytes >= 4)) + { + // Pre-load the temp registers + uint32 t = 0, d = 0; + switch(align) + { + case 1: t |= data[2] << 16; + case 2: t |= data[1] << 8; + case 3: t |= data[0]; + } + + t <<= (8 * align); + data += 4-align; + numBytes -= 4-align; + + int32 sl = 8 * (4-align); + int32 sr = 8 * align; + + // Mix + while(numBytes >= 4) + { + d = *(uint32 *)data; + t = (t >> sr) | (d << sl); + + uint32 k = t; + MURMUR2_MIX(h,k,m); + t = d; + + data += 4; + numBytes -= 4; + } + + // Handle leftover data in temp registers + d = 0; + if(numBytes >= align) + { + switch(align) + { + case 3: d |= data[2] << 16; + case 2: d |= data[1] << 8; + case 1: d |= data[0]; + } + + uint32 k = (t >> sr) | (d << sl); + MURMUR2_MIX(h,k,m); + + data += align; + numBytes -= align; + + //---------- + // Handle tail bytes + switch(numBytes) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + } + else + { + switch(numBytes) + { + case 3: d |= data[2] << 16; + case 2: d |= data[1] << 8; + case 1: d |= data[0]; + case 0: h ^= (t >> sr) | (d << sl); + h *= m; + } + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } + else + { + while(numBytes >= 4) + { + uint32 k = *(uint32 *)data; + MURMUR2_MIX(h,k,m); + data += 4; + numBytes -= 4; + } + + //---------- + // Handle tail bytes + + switch(numBytes) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } +} + +uint64 CalculateHashCode64(const void * key, unsigned int numBytes, unsigned int seed) +{ +#ifdef MUSCLE_64_BIT_PLATFORM + const uint64 m = 0xc6a4a7935bd1e995; + const int r = 47; + + uint64 h = seed ^ (numBytes * m); + + const uint64 * data = (const uint64 *)key; + const uint64 * end = data + (numBytes/sizeof(uint64)); + + while(data != end) + { + uint64 k = *data++; + k *= m; + k ^= k >> r; + k *= m; + h ^= k; + h *= m; + } + + const unsigned char * data2 = (const unsigned char*)data; + switch(numBytes & 7) + { + case 7: h ^= uint64(data2[6]) << 48; + case 6: h ^= uint64(data2[5]) << 40; + case 5: h ^= uint64(data2[4]) << 32; + case 4: h ^= uint64(data2[3]) << 24; + case 3: h ^= uint64(data2[2]) << 16; + case 2: h ^= uint64(data2[1]) << 8; + case 1: h ^= uint64(data2[0]); + h *= m; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + return h; +#else + const unsigned int m = 0x5bd1e995; + const int r = 24; + + unsigned int h1 = seed ^ numBytes; + unsigned int h2 = 0; + + const unsigned int * data = (const unsigned int *)key; + + while(numBytes >= sizeof(uint64)) + { + unsigned int k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + numBytes -= sizeof(uint32); + + unsigned int k2 = *data++; + k2 *= m; k2 ^= k2 >> r; k2 *= m; + h2 *= m; h2 ^= k2; + numBytes -= sizeof(uint32); + } + + if (numBytes >= sizeof(uint32)) + { + unsigned int k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + numBytes -= sizeof(uint32); + } + + switch(numBytes) + { + case 3: h2 ^= ((unsigned char*)data)[2] << 16; + case 2: h2 ^= ((unsigned char*)data)[1] << 8; + case 1: h2 ^= ((unsigned char*)data)[0]; + h2 *= m; + }; + + h1 ^= h2 >> 18; h1 *= m; + h2 ^= h1 >> 22; h2 *= m; + h1 ^= h2 >> 17; h1 *= m; + h2 ^= h1 >> 19; h2 *= m; + + uint64 h = h1; + h = (h << 32) | h2; + + return h; +#endif +} + +#ifndef MUSCLE_AVOID_OBJECT_COUNTING + +static ObjectCounterBase * _firstObjectCounter = NULL; +static Mutex _counterListMutex; + +ObjectCounterBase :: ObjectCounterBase() : _prevCounter(NULL) +{ + MutexGuard mg(_counterListMutex); + + // Prepend this object to the head of the global counters-list + _nextCounter = _firstObjectCounter; + if (_firstObjectCounter) _firstObjectCounter->_prevCounter = this; + _firstObjectCounter = this; +} + +ObjectCounterBase :: ~ObjectCounterBase() +{ + if ((_prevCounter)||(_nextCounter)) // paranoia + { + // Remove this object from the global counters-list + MutexGuard mg(_counterListMutex); + if (_firstObjectCounter == this) _firstObjectCounter = _nextCounter; + if (_prevCounter) _prevCounter->_nextCounter = _nextCounter; + if (_nextCounter) _nextCounter->_prevCounter = _prevCounter; + } +} + +#endif + +status_t GetCountedObjectInfo(Hashtable & results) +{ +#ifdef MUSCLE_AVOID_OBJECT_COUNTING + (void) results; + return B_ERROR; +#else + if (_counterListMutex.Lock() == B_NO_ERROR) + { + status_t ret = B_NO_ERROR; + + const ObjectCounterBase * oc = _firstObjectCounter; + while(oc) + { + if (results.Put(oc->GetCounterTypeName(), oc->GetCount()) != B_NO_ERROR) ret = B_ERROR; + oc = oc->GetNextCounter(); + } + + _counterListMutex.Unlock(); + return ret; + } + else return B_ERROR; +#endif +} + +void PrintCountedObjectInfo() +{ +#ifdef MUSCLE_AVOID_OBJECT_COUNTING + printf("Counted Object Info report not available, because MUSCLE was compiled with -DMUSCLE_AVOID_OBJECT_COUNTING\n"); +#else + Hashtable table; + if (GetCountedObjectInfo(table) == B_NO_ERROR) + { + table.SortByKey(); // so they'll be printed in alphabetical order + printf("Counted Object Info report follows: (" UINT32_FORMAT_SPEC " types counted)\n", table.GetNumItems()); + for (HashtableIterator iter(table); iter.HasData(); iter++) printf(" %6" UINT32_FORMAT_SPEC_NOPERCENT " %s\n", iter.GetValue(), iter.GetKey()); + } + else printf("PrintCountedObjectInfo: GetCountedObjectInfo() failed!\n"); +#endif +} + +void SetMainReflectServerCatchSignals(bool enable) +{ + _mainReflectServerCatchSignals = enable; +} + +bool GetMainReflectServerCatchSignals() +{ + return _mainReflectServerCatchSignals; +} + +}; // end namespace muscle diff --git a/system/SetupSystem.h b/system/SetupSystem.h new file mode 100644 index 00000000..dc089b74 --- /dev/null +++ b/system/SetupSystem.h @@ -0,0 +1,307 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSetupSystem_h +#define MuscleSetupSystem_h + +#include "system/AtomicCounter.h" +#include "system/Mutex.h" +#include "util/GenericCallback.h" +#include "util/Queue.h" + +#if !defined(MUSCLE_SINGLE_THREAD_ONLY) && defined(MUSCLE_QT_HAS_THREADS) +# if QT_VERSION >= 0x040000 +# include +# else +# include +# endif +#endif + +namespace muscle { + +class AtomicCounter; + +/** SetupSystem is the base class for an object that sets up + * the environment to handle the sort of things we'll + * be wanting to do. Typically System objects are placed + * on the stack at the beginning of main(). They do + * the setup in their constructor, and tear it down + * again in their destructor. + */ +class SetupSystem +{ +protected: + /** Default constructor, a no-op. + * It's protected because you should never instantiate a SetupSystem object alone; + * it should always be subclassed to. (consider it an abstract base class, except + * without any pure virtuals defined) + */ + SetupSystem() {/* empty */} + +public: + /** Virtual destructor to keep C++ honest */ + virtual ~SetupSystem() {/* empty */} +}; + +/** This SetupSystem handles initializing the environment's threading mechanisms. */ +class ThreadSetupSystem : public SetupSystem +{ +public: + /** Constructor. Records the thread ID of the main + * thread and does some other miscellaneous setup work. + * @param muscleSingleThreadOnly If set to true, the MUSCLE code will assume that + * this process is going to be single-threaded, even if the + * code was not compiled with the -DMUSCLE_SINGLE_THREAD_ONLY flag! + * This can be useful to gain a bit of extra efficiency, if you + * need to compile your code to be multithread-capable but can + * sometimes promise that this particular process will never + * spawn multiple threads. + * If your code is NEVER multi-threaded, then it is even more efficient to define + * -DMUSCLE_SINGLE_THREAD_ONLY in your Makefile, rather than setting this flag to false. + * If -DMUSCLE_SINGLE_THREAD_ONLY is set, then this argument is ignored. + * Note: DON'T SET THIS TO FLAG TRUE UNLESS YOU REALLY KNOW WHAT YOU ARE DOING! + * If you're not sure, leave it set to false. Most people won't need it, + * and leaving it false won't cause any problems. + */ + ThreadSetupSystem(bool muscleSingleThreadOnly = false); + + /** Destructor. If MUSCLE_USE_PTHREADS is defined, + * this will destroy the mutexes that were set up + * in the constructor; otherwise it's a no-op + */ + virtual ~ThreadSetupSystem(); + +private: + friend class AtomicCounter; + +private: + Mutex _lock; // Returned by GetGlobalMuscleLock() +}; + +#if defined(MUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS) +/** Used by AtomicCounter to get (rather inefficient) + * atomic counting via a small number of static mutexes. + * @param count The value to adjust atomically + * @param delta The amount to add/subtract to/from (*count) + * @returns the new state of (*count) + */ +int32 DoMutexAtomicIncrement(int32 * count, int32 delta); +#endif + +/** This SetupSystem handles initializing the environment's TCP stack */ +class NetworkSetupSystem : public SetupSystem +{ +public: + /** Constructor. Under Windows, this calls WSAStartup() + * to initialize the TCP stack. Under all other OS's, + * it calls signal(SIGPIPE, SIG_IGN) so that we won't + * get signalled and killed if a remote client closes + * his connection while we are sending to him. + */ + NetworkSetupSystem(); + + /** Destructor. Under Windows, this calls WSACleanup(); + * it's a no-op for everyone else. + */ + virtual ~NetworkSetupSystem(); +}; + +/** This SetupSystem handles initializing the system's + * math routines as necessary. + */ +class MathSetupSystem : public SetupSystem +{ +public: + /** Constructor. Under Borland C++, this constructor + * disables floating point exceptions so that if they + * occur, they won't crash the program. It's a no-op + * for all other environments. + */ + MathSetupSystem(); + + /** Destructor. A no-op. */ + virtual ~MathSetupSystem(); +}; + +/** This SetupSystem just does some basic sanity checks + * to ensure that the code was compiled in a way that + * has some chance of working (e.g. it makes sure that + * sizeof(uint32)==4, etc) + */ +class SanitySetupSystem : public SetupSystem +{ +public: + /** Constructor. Does some quick sanity checking + * to make sure our data types are what they are supposed + * to be, the endian-ness is correct, etc. + * If any checks fail, this constructor will crash + * the program so that the failure will be immediately + * obvious. + */ + SanitySetupSystem(); + + /** Destructor. A no-op. */ + virtual ~SanitySetupSystem(); +}; + +/** This class is a global setup/tear-down class; + * It contains one member variable of each of the + * other SetupSystem classes, so that when you instantiate + * one of these objects, all the systems MUSCLE uses + * get set up. It's recommended that you put one of + * these guys on the stack right at the beginning of + * main(), to ensure that everything gets initialized + * correctly... or if you for some reason don't want + * to initialize all subsystems, you can still put + * individual smaller SetupSystem objects on the + * stack, instead. Your choice. + */ +class CompleteSetupSystem : public SetupSystem +{ +public: + /** Constructor. No-op, all the other *SetupSystem objects are created at this point. + * @param muscleSingleThreadOnly Passed to the ThreadSetupSystem constructor. + * See the ThreadSetupSystem documentation for details. + * (If you don't know what this flag is, leave it set to false!) + */ + CompleteSetupSystem(bool muscleSingleThreadOnly = false); + + /** Destructor. Calls the Callback() method of any items that were previously added + * to our cleanup-callbacks list (in the opposite order from how they were added), then + * calls AbstractObjectRecycler::GlobalFlushAllCachedObjects() to ensure + * that objects don't get recycled after their object pools have been deleted. + */ + ~CompleteSetupSystem(); + + /** Returns a reference to a list of cleanup callback items that will be called by + * the CompleteSetupSystem destructor. You can add items to this list if you want + * things to happen on program exit. + */ + Queue & GetCleanupCallbacks() {return _cleanupCallbacks;} + + /** As above, but read-only. */ + const Queue & GetCleanupCallbacks() const {return _cleanupCallbacks;} + + /** If there are any CompleteSetupSystems anywhere on the stack, this method will + * return a pointer to the current (most recently created) CompleteSetupSystem object. + * Otherwise, returns NULL. + */ + static CompleteSetupSystem * GetCurrentCompleteSetupSystem(); + +private: + NetworkSetupSystem _network; + ThreadSetupSystem _threads; + MathSetupSystem _math; + SanitySetupSystem _sanity; + Queue _cleanupCallbacks; + CompleteSetupSystem * _prevInstance; // stack (via linked list) so that nested scopes are handled appropriately +}; + +/** Returns a pointer to a process-wide Mutex, or NULL if that Mutex + * hasn't been allocated (by a ThreadSetupSystem or CompleteSetupSystem + * object) yet. + */ +Mutex * GetGlobalMuscleLock(); + +/** Returns true iff the current thread is the process's main thread (i.e. the flow + * of execution that started at main() and placed a SetupSystem object on the stack). + * If MUSCLE_SINGLE_THREAD_ONLY is defined, then this function always returns true. + */ +bool IsCurrentThreadMainThread(); + +#ifndef MUSCLE_SINGLE_THREAD_ONLY + +/** This class represents a unique ID for a thread. It provides an + * implementation-neutral and more user-friendly wrapper around pthread_self() + * and its equivalents. + */ +class muscle_thread_id +{ +public: + /** Default constructor. Returns an muscle_thread_id object that doesn't represent any thread. */ + muscle_thread_id() +# ifndef MUSCLE_USE_PTHREADS + : _id(0) +# endif + { +# ifdef MUSCLE_USE_PTHREADS + memset(&_id, 0, sizeof(_id)); +# endif + } + + /** Returns true iff the two objects represent the same thread ID. */ + bool operator == (const muscle_thread_id & rhs) const + { +# if defined(MUSCLE_USE_PTHREADS) + return pthread_equal(_id, rhs._id); +# else + return (_id == rhs._id); +# endif + } + + /** Returns true iff the two thread objects do not represent that same thread ID. */ + bool operator != (const muscle_thread_id & rhs) const {return !(*this == rhs);} + + /** Returns a muscle_thread_id object representing the calling thread. */ + static muscle_thread_id GetCurrentThreadID() + { + muscle_thread_id ret(false); +# if defined(MUSCLE_USE_PTHREADS) + ret._id = pthread_self(); +# elif defined(WIN32) + ret._id = GetCurrentThreadId(); +# elif defined(MUSCLE_QT_HAS_THREADS) + ret._id = QThread::currentThreadId(); +# elif defined(__BEOS__) || defined(__HAIKU__) || defined(__ATHEOS__) + ret._id = find_thread(NULL); +# else +# error "GetCurrentThreadID(): No implementation found for this OS!" +# endif + return ret; + } + + /** Returns a human-readable string representation of this thread ID. + * Note that the returned buffer has the same lifetime as this object. + * @param buf Must point to a buffer of at least 20 characters that we can write to + * @returns buf + */ + const char * ToString(char * buf) const + { +# if defined(MUSCLE_USE_PTHREADS) + // pthread_t might be a struct, so generate a good-enough ID from its bytes + unsigned long count = 0; + unsigned long base = 1; + unsigned char * s = (unsigned char*)(void*)(&_id); + for (size_t i=0; i +# include +# include +# include +#endif + +namespace muscle { + +#ifndef WIN32 +static const short LARGEST_SEMAPHORE_DELTA = 10000; // I'm assuming there will never be this many processes + +// Unbelievable how messed up the semctl() API is :^P +# if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +# define DECLARE_SEMCTL_ARG(semopts) semun semopts +# elif defined(__APPLE__) +# define DECLARE_SEMCTL_ARG(semopts) semun semopts +# else +# define DECLARE_SEMCTL_ARG(semopts) union semun {int val; struct semid_ds * buf; unsigned short * array; struct seminfo * __buf;} semopts +#endif + +#endif + +SharedMemory :: SharedMemory() : +#ifdef WIN32 + _mutex(NULL), _file(INVALID_HANDLE_VALUE), _map(NULL), +#else + _key(IPC_PRIVATE), _areaID(-1), _semID(-1), +#endif + _area(NULL), _areaSize(0), _isLocked(false), _isLockedReadOnly(false), _isCreatedLocally(false) +{ + // empty +} + +SharedMemory :: ~SharedMemory() +{ + UnsetArea(); // clean up +} + +status_t SharedMemory :: SetArea(const char * keyString, uint32 createSize, bool returnLocked) +{ + UnsetArea(); // make sure everything is deallocated to start with + +#if defined(MUSCLE_FAKE_SHARED_MEMORY) + if (createSize > 0) + { + _area = muscleAlloc(createSize); + if (_area) + { + memset(_area, 0, createSize); + _areaName = keyString; + _areaSize = createSize; + _isCreatedLocally = true; + _isLocked = returnLocked; + _isLockedReadOnly = false; + return B_NO_ERROR; + } + else WARN_OUT_OF_MEMORY; + } +#elif defined(WIN32) + char buf[64]; + if (keyString == NULL) + { + sprintf(buf, INT32_FORMAT_SPEC, GetTickCount()); // No user-supplied name? We'll pick an arbitrary name then + keyString = buf; + } + _areaName = keyString; + + // For windows we only use a Mutex, because even a Windows semaphore isn't enough to + // do shared read locking. When I figure out how to do interprocess shared read locking + // under Windows I will implement that, but for now it's always exclusive-locking. :^( + _mutex = CreateMutexA(NULL, true, (_areaName+"__mutex")()); + if (_mutex != NULL) + { + bool ok = true; + if (GetLastError() == ERROR_ALREADY_EXISTS) ok = (LockAreaReadWrite() == B_NO_ERROR); + else + { + // We created it in our CreateMutex() call, and it's already locked for us + _isLocked = true; + _isLockedReadOnly = false; + } + if (ok) + { + char buf[MAX_PATH]; + if (GetTempPathA(sizeof(buf), buf) > 0) + { + _fileName = _areaName.Prepend(buf)+"__file"; + _file = CreateFileA(_fileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH|FILE_FLAG_RANDOM_ACCESS, NULL); + if (_file != INVALID_HANDLE_VALUE) + { + _isCreatedLocally = (GetLastError() != ERROR_ALREADY_EXISTS); + if (createSize == 0) createSize = GetFileSize(_file, NULL); + _areaSize = createSize; // assume the file will be resized automagically for us + if (_areaSize > 0) + { + _map = CreateFileMappingA(_file, NULL, PAGE_READWRITE, 0, createSize, (_areaName+"__map")()); + if (_map) + { + _area = MapViewOfFile(_map, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (_area) + { + if (returnLocked == false) UnlockArea(); + return B_NO_ERROR; + } + } + } + } + } + } + } +#else + key_t requestedKey = IPC_PRIVATE; + if (keyString) + { + requestedKey = (key_t) CalculateHashCode(keyString,strlen(keyString)); + if (requestedKey == IPC_PRIVATE) requestedKey++; + _areaName = keyString; + } + + DECLARE_SEMCTL_ARG(semopts); + const int permissionBits = 0777; + + // Try to create a new semaphore to control access to our area + _semID = semget(requestedKey, 1, IPC_CREAT|IPC_EXCL|permissionBits); + if (_semID >= 0) + { + // race condition here!? + semopts.val = LARGEST_SEMAPHORE_DELTA; + if (semctl(_semID, 0, SETVAL, semopts) < 0) _semID = -1; // oops! + } + else _semID = semget(requestedKey, 1, permissionBits); // Couldn't create? then get the existing one + + if (_semID >= 0) + { + _key = requestedKey; + + // If we requested a private key, we still need to know the actual key value + if (_key == IPC_PRIVATE) + { + struct semid_ds semInfo = {}; // the braces zero-initialize the struct for us, to keep clang++SA happy + semopts.buf = &semInfo; + if (semctl(_semID, 0, IPC_STAT, semopts) == 0) + { +# ifdef __linux__ + _key = semInfo.sem_perm.__key; + +// Mac os-x leopard uses '_key' by default. Both Tiger and Leopard may use _key if the following condition is true, otherwise they use 'key'. +# elif (defined(__APPLE__) && (defined(__POSIX_C_SOURCE) || defined(__LP64__))) || __DARWIN_UNIX03 + _key = semInfo.sem_perm._key; +# else + _key = semInfo.sem_perm.key; +# endif + } + _areaName = "private"; // sorry, it's the best I can do short of figuring out how to invert the hash function! + } + + if ((_key != IPC_PRIVATE)&&(LockAreaReadWrite() == B_NO_ERROR)) + { + _areaID = shmget(_key, 0, permissionBits); + if ((_areaID < 0)&&(createSize > 0)) + { + _areaID = shmget(_key, createSize, IPC_CREAT|IPC_EXCL|permissionBits); + _isCreatedLocally = true; + } + if (_areaID >= 0) + { + _area = shmat(_areaID, NULL, 0); + if ((_area)&&(_area != ((void *)-1))) // FogBugz #7294 + { + // Now get the stats on our area + struct shmid_ds buf; + if (shmctl(_areaID, IPC_STAT, &buf) == 0) + { + _areaSize = (uint32) buf.shm_segsz; + if (returnLocked == false) UnlockArea(); + return B_NO_ERROR; + } + } + } + } + } +#endif + + UnsetArea(); // oops, roll back everything! + return B_ERROR; +} + +status_t SharedMemory :: DeleteArea() +{ +#if defined(MUSCLE_FAKE_SHARED_MEMORY) + (void) UnsetArea(); + return B_NO_ERROR; +#else +# if defined(WIN32) + if (_mutex != NULL) +# else + if (_semID >= 0) +# endif + { + if ((_isLocked)&&(_isLockedReadOnly)) UnlockArea(); + if ((_isLocked)||(LockAreaReadWrite() == B_NO_ERROR)) + { +# ifdef WIN32 + String fileName = _fileName; // hold as temp since UnsetArea() will clear it + UnsetArea(); + return DeleteFileA(fileName()) ? B_NO_ERROR : B_ERROR; // now that everything is detached, try to delete the file +# else + if (_areaID >= 0) (void) shmctl(_areaID, IPC_RMID, NULL); // bye bye shared memory! + _areaID = -1; + + (void) semctl(_semID, 0, IPC_RMID, 0); // bye bye semaphore! + + _semID = -1; + UnsetArea(); + return B_NO_ERROR; +# endif + } + } + return B_ERROR; +#endif +} + +void SharedMemory :: UnsetArea() +{ + UnlockArea(); + +#if defined(MUSCLE_FAKE_SHARED_MEMORY) + if (_area) + { + muscleFree(_area); + _area = NULL; + } +#elif defined(WIN32) + if (_area) + { + UnmapViewOfFile(_area); + _area = NULL; + } + if (_map) + { + CloseHandle(_map); + _map = NULL; + } + if (_file != INVALID_HANDLE_VALUE) + { + CloseHandle(_file); + _file = INVALID_HANDLE_VALUE; + } + if (_mutex) + { + CloseHandle(_mutex); + _mutex = NULL; + } + _fileName.Clear(); +#else + if (_area) + { + (void) shmdt(_area); // we're done with it now + _area = NULL; + } + _areaID = -1; + _key = IPC_PRIVATE; + _semID = -1; +#endif + + _areaName.Clear(); + _areaSize = 0; + _isCreatedLocally = false; +} + +status_t SharedMemory :: LockArea(bool readOnly) +{ +#if defined(MUSCLE_FAKE_SHARED_MEMORY) + _isLocked = true; + _isLockedReadOnly = readOnly; + return B_NO_ERROR; +#else + if (_isLocked == false) + { + _isLocked = true; // Set these first just so they are correct while we're waiting + _isLockedReadOnly = readOnly; +# ifdef WIN32 + if (WaitForSingleObject(_mutex, INFINITE) == WAIT_OBJECT_0) return B_NO_ERROR; +# else + if (AdjustSemaphore(_isLockedReadOnly ? -1: -LARGEST_SEMAPHORE_DELTA) == B_NO_ERROR) return B_NO_ERROR; +# endif + _isLocked = _isLockedReadOnly = false; // oops, roll back! + } + return B_ERROR; +#endif +} + +void SharedMemory :: UnlockArea() +{ + if (_isLocked) + { +#if !defined(MUSCLE_FAKE_SHARED_MEMORY) +# ifdef WIN32 + (void) ReleaseMutex(_mutex); +# else + (void) AdjustSemaphore(_isLockedReadOnly ? 1 : LARGEST_SEMAPHORE_DELTA); +# endif +#endif + _isLocked = _isLockedReadOnly = false; + } +} + +#ifndef WIN32 +status_t SharedMemory :: AdjustSemaphore(short delta) +{ + if (_semID >= 0) + { + struct sembuf sop = {0, delta, SEM_UNDO}; + + while(1) + { + if (semop(_semID, &sop, 1) == 0) return B_NO_ERROR; + if (errno != EINTR) break; // on EINTR, we'll try again --jaf + } + } + return B_ERROR; +} +#endif + +}; // end namespace muscle diff --git a/system/SharedMemory.h b/system/SharedMemory.h new file mode 100644 index 00000000..d2439273 --- /dev/null +++ b/system/SharedMemory.h @@ -0,0 +1,140 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSharedMemory_h +#define MuscleSharedMemory_h + +#include "util/String.h" +#include "util/CountedObject.h" + +// This needs to be AFTER the MUSCLE includes, so that WIN32 will be defined if appropriate +#ifndef WIN32 +# include +#endif + +namespace muscle { + +/** This class is a simple platform-independent API wrapper around the native OS's shared-memory facilities. + * It can be used to create and access shared memory areas from different processes. + * The current implementation only works under Windows and POSIX, but other implementations may be + * added in the future. + */ +class SharedMemory : private CountedObject +{ +public: + /** Default constructor. You'll need to call SetArea() before this object will be useful. */ + SharedMemory(); + + /** Destructor. Calls UnsetArea(). */ + ~SharedMemory(); + + /** Finds or demand-allocates a shared memory area with at least the specified size. + * Calls UnsetArea() first to make sure any previously attached area gets detached. + * Note that created areas will remain until DeleteArea() is called (if it isn't called, + * they will persist even after the process has terminated!) + * @see IsCreatedLocally() to determine if the area got created by this call or not. + * @param areaName Name of the shared memory area to open or create. + * If NULL, then a new, unnamed area will be created. + * @param createSize If a new area needs to be created, this is the minimum size of the + * created area, in bytes. If zero, auto-creation of a shared area is disabled. + * @param returnLocked If set true, then the area will be read/write locked when this function + * returns successfully, and it is the caller's responsibility to call + * UnlockArea() when appropriate. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t SetArea(const char * areaName, uint32 createSize = 0, bool returnLocked = false); + + /** Unlocks any current locks, and terminates our relationship with the current shared memory area. + * @returns B_NO_ERROR on success, or B_ERROR if we had no current area. + */ + void UnsetArea(); + + /** Locks the current area, using a shared locking mode. + * Reading the contents of the shared memory area is safe while in this mode, + * but modifying them is not. If you need to modify the shared memory area, + * use LockAreaReadWrite() instead. + * @returns B_NO_ERROR on success, or B_ERROR on failure (no area, or already locked) + * @note under Windows this call is currently equivalent to the LockAreaReadWrite() + * call, because as far as I can tell there is no good way to implement + * shared-reader/single-writer interprocess locks under Windows. If you + * know of a way to do it, please tell me how so I can improve the code. :^) + */ + status_t LockAreaReadOnly() {return LockArea(true);} + + /** Locks the current area using an exclusive locking mode. It is guaranteed + * that no more than one process will have an exclusive lock on a given area + * at any given time. + * It is safe to read or modify the shared memory area while this lock is active, + * but if you will not need to modify anything, then it is more efficient to + * lock using LockAreaReadOnly() instead. + * @returns B_NO_ERROR on success, or B_ERROR on failure (no area, or already locked) + */ + status_t LockAreaReadWrite() {return LockArea(false);} + + /** Unlocks any current lock on our shared memory area. + * Has no effect if our area is already unlocked. + */ + void UnlockArea(); + + /** Returns the size of our current area in bytes, or zero if there is no current area */ + uint32 GetAreaSize() const {return _areaSize;} + + /** Returns true iff we are currently locked */ + bool IsLocked() const {return _isLocked;} + + /** Returns true iff we created our memory area ourselves, or false if we found + * it already created. The returned value is meaningless if there is no current area. + */ + bool IsCreatedLocally() const {return _isCreatedLocally;} + + /** Returns the name of our current area (as passed in to SetArea() or + * generated within SetArea(), or "" if we have no current area. + */ + const String & GetAreaName() const {return _areaName;} + + /** Returns a pointer to shared memory area. Note that this memory + * may be accessed, written to, or even deleted by other processes! So you'll typically + * want to call one of the LockArea*() methods before accessing the memory it points to. + * @returns Pointer to the shared memory area, or NULL if there is no current area. + */ + uint8 * GetAreaPointer() const {return (uint8 *) _area;} + + /** Convenience synonym for GetAreaPointer(). */ + uint8 * operator()() const {return (uint8 *) _area;} + + /** Rudely deletes the current shared area. + * After this call returns, no processes will be able to SetArea() or LockArea() + * our shared memory any more. Note that this method will call LockAreaReadWrite() + * before deleting everything, so other processes who currently have the area + * locked will at least be able to finish their current transaction before everything + * goes away. + * @returns B_NO_ERROR on success, or B_ERROR if there is no current area or + * some other problem prevented the current area from being deleted. + */ + status_t DeleteArea(); + +private: + status_t LockArea(bool readOnly); + +#ifdef WIN32 + ::HANDLE _mutex; + ::HANDLE _file; + ::HANDLE _map; + String _fileName; +#else + status_t AdjustSemaphore(short delta); + key_t _key; + int _areaID; + int _semID; +#endif + + String _areaName; + void * _area; + uint32 _areaSize; + bool _isLocked; + bool _isLockedReadOnly; + bool _isCreatedLocally; +}; + +}; // end namespace muscle + +#endif diff --git a/system/SignalMultiplexer.cpp b/system/SignalMultiplexer.cpp new file mode 100644 index 00000000..e66c26a8 --- /dev/null +++ b/system/SignalMultiplexer.cpp @@ -0,0 +1,138 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#if defined(__linux__) || defined(__APPLE__) +# define MUSCLE_USE_POSIX_SIGNALS 1 +# include +#endif + +#include "system/SignalMultiplexer.h" + +namespace muscle { + +status_t ISignalHandler :: GetNthSignalNumber(uint32 n, int & signalNumber) const +{ +#if defined(WIN32) + switch(n) + { + case 0: signalNumber = CTRL_CLOSE_EVENT; return B_NO_ERROR; + case 1: signalNumber = CTRL_LOGOFF_EVENT; return B_NO_ERROR; + case 2: signalNumber = CTRL_SHUTDOWN_EVENT; return B_NO_ERROR; + } +#elif defined(MUSCLE_USE_POSIX_SIGNALS) + switch(n) + { + case 0: signalNumber = SIGINT; return B_NO_ERROR; + case 1: signalNumber = SIGTERM; return B_NO_ERROR; + case 2: signalNumber = SIGHUP; return B_NO_ERROR; + } +#endif + return B_ERROR; +} + +#if defined(WIN32) +static BOOL Win32SignalHandlerCallbackFunc(DWORD sigNum) {SignalMultiplexer::GetSignalMultiplexer().CallSignalHandlers(sigNum); return true;} +#else +static void POSIXSignalHandlerCallbackFunc(int sigNum) {SignalMultiplexer::GetSignalMultiplexer().CallSignalHandlers(sigNum);} +#endif + +status_t SignalMultiplexer :: AddHandler(ISignalHandler * s) +{ + MutexGuard m(_mutex); + if (_handlers.AddTail(s) != B_NO_ERROR) return B_ERROR; + if (UpdateSignalSets() == B_NO_ERROR) return B_NO_ERROR; + else + { + _handlers.RemoveTail(); + return B_ERROR; + } +} + +void SignalMultiplexer :: RemoveHandler(ISignalHandler * s) +{ + MutexGuard m(_mutex); + if (_handlers.RemoveFirstInstanceOf(s) == B_NO_ERROR) (void) UpdateSignalSets(); +} + +void SignalMultiplexer :: CallSignalHandlers(int sigNum) +{ + _totalSignalCounts++; + if (muscleInRange(sigNum, 0, (int)(ARRAYITEMS(_signalCounts)-1))) _signalCounts[sigNum]++; + + // Can't lock the Mutex here because we are being called within a signal context! + // So we just have to hope that _handlers won't change while we do this + for (uint32 i=0; i<_handlers.GetNumItems(); i++) _handlers[i]->SignalHandlerFunc(sigNum); +} + +status_t SignalMultiplexer :: UpdateSignalSets() +{ + Queue newSignalSet; + for (uint32 i=0; i<_handlers.GetNumItems(); i++) + { + const ISignalHandler * s = _handlers[i]; + int sigNum; + for (uint32 i=0; s->GetNthSignalNumber(i, sigNum) == B_NO_ERROR; i++) if ((newSignalSet.IndexOf(sigNum) < 0)&&(newSignalSet.AddTail(sigNum) != B_NO_ERROR)) return B_ERROR; + } + newSignalSet.Sort(); + +#ifdef WIN32 + // For Windows, we only need to register/unregister the callback function, not the individual signals + if (newSignalSet.HasItems() == _currentSignalSet.HasItems()) + { + _currentSignalSet.SwapContents(newSignalSet); + return B_NO_ERROR; + } +#else + if (newSignalSet == _currentSignalSet) return B_NO_ERROR; // already done! +#endif + + UnregisterSignals(); + _currentSignalSet.SwapContents(newSignalSet); + return RegisterSignals(); +} + +status_t SignalMultiplexer :: RegisterSignals() +{ +#if defined(WIN32) + return ((_currentSignalSet.IsEmpty())||(SetConsoleCtrlHandler((PHANDLER_ROUTINE) Win32SignalHandlerCallbackFunc, true))) ? B_NO_ERROR : B_ERROR; +#elif defined(MUSCLE_USE_POSIX_SIGNALS) + struct sigaction newact; + sigemptyset(&newact.sa_mask); + newact.sa_flags = 0; + newact.sa_handler = POSIXSignalHandlerCallbackFunc; /*set the new handler*/ + for (uint32 i=0; i<_currentSignalSet.GetNumItems(); i++) + { + int sigNum = _currentSignalSet[i]; + if (sigaction(sigNum, &newact, NULL) == -1) + { + LogTime(MUSCLE_LOG_WARNING, "Could not install signal handler for signal #%i\n", sigNum); + UnregisterSignals(); + return B_ERROR; + } + } + return B_NO_ERROR; +#else + return B_ERROR; +#endif +} + +void SignalMultiplexer :: UnregisterSignals() +{ +#if defined(WIN32) + if (_currentSignalSet.HasItems()) (void) SetConsoleCtrlHandler((PHANDLER_ROUTINE)Win32SignalHandlerCallbackFunc, false); +#elif defined(MUSCLE_USE_POSIX_SIGNALS) + struct sigaction newact; + sigemptyset(&newact.sa_mask); + newact.sa_flags = 0; + newact.sa_handler = NULL; + for (uint32 i=0; i<_currentSignalSet.GetNumItems(); i++) (void) sigaction(_currentSignalSet[i], NULL, NULL); +#endif +} + +SignalMultiplexer :: SignalMultiplexer() : _totalSignalCounts(0) +{ + for (uint32 i=0; i _handlers; + Queue _currentSignalSet; + + uint32 _totalSignalCounts; // incremented inside the interrupt code, beware! + uint32 _signalCounts[32]; // incremented inside the interrupt code, beware! + + static SignalMultiplexer _signalMultiplexer; // the singleton object +}; + +}; // end namespace muscle + +#endif + diff --git a/system/SystemInfo.cpp b/system/SystemInfo.cpp new file mode 100644 index 00000000..4173c5bc --- /dev/null +++ b/system/SystemInfo.cpp @@ -0,0 +1,278 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/SystemInfo.h" + +#if defined(__APPLE__) +# include +# include +#endif +#ifdef WIN32 +# include "Shlwapi.h" +#endif + +namespace muscle { + +const char * GetOSName(const char * defStr) +{ + const char * ret = defStr; + (void) ret; // just to shut the Borland compiler up + +#ifdef WIN32 + ret = "Windows"; +#endif + +#ifdef __CYGWIN__ + ret = "Windows (CygWin)"; +#endif + +#ifdef __APPLE__ + ret = "MacOS/X"; +#endif + +#ifdef __linux__ + ret = "Linux"; +#endif + +#ifdef __BEOS__ + ret = "BeOS"; +#endif + +#ifdef __HAIKU__ + ret = "Haiku"; +#endif + +#ifdef __ATHEOS__ + ret = "AtheOS"; +#endif + +#if defined(__QNX__) || defined(__QNXTO__) + ret = "QNX"; +#endif + +#ifdef __FreeBSD__ + ret = "FreeBSD"; +#endif + +#ifdef __OpenBSD__ + ret = "OpenBSD"; +#endif + +#ifdef __NetBSD__ + ret = "NetBSD"; +#endif + +#ifdef __osf__ + ret = "Tru64"; +#endif + +#if defined(IRIX) || defined(__sgi) + ret = "Irix"; +#endif + +#ifdef __OS400__ + ret = "AS400"; +#endif + +#ifdef __OS2__ + ret = "OS/2"; +#endif + +#ifdef _AIX + ret = "AIX"; +#endif + +#ifdef _SEQUENT_ + ret = "Sequent"; +#endif + +#ifdef _SCO_DS + ret = "OpenServer"; +#endif + +#if defined(_HP_UX) || defined(__hpux) || defined(_HPUX_SOURCE) + ret = "HPUX"; +#endif + +#if defined(SOLARIS) || defined(__SVR4) + ret = "Solaris"; +#endif + +#if defined(__UNIXWARE__) || defined(__USLC__) + ret = "UnixWare"; +#endif + + return ret; +} + +status_t GetSystemPath(uint32 whichPath, String & outStr) +{ + bool found = false; + char buf[2048]; // scratch space + + switch(whichPath) + { + case SYSTEM_PATH_CURRENT: // current working directory + { +#ifdef WIN32 + found = muscleInRange((int)GetCurrentDirectoryA(sizeof(buf), buf), 1, (int)sizeof(buf)-1); +#else + found = (getcwd(buf, sizeof(buf)) != NULL); +#endif + if (found) outStr = buf; + } + break; + + case SYSTEM_PATH_EXECUTABLE: // executable's directory + { +#ifdef WIN32 + found = muscleInRange((int)GetModuleFileNameA(NULL, buf, sizeof(buf)), (int)1, (int)sizeof(buf)-1); + if (found) + { + PathRemoveFileSpecA(buf); + if (found) outStr = buf; + } +#else +# ifdef __APPLE__ + CFURLRef bundleURL = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + CFStringRef cfPath = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle); + char bsdPath[2048]; + if (CFStringGetCString((CFStringRef)cfPath, bsdPath, sizeof(bsdPath), kCFStringEncodingUTF8)) + { + found = true; + outStr = bsdPath; + + int32 lastSlash = outStr.LastIndexOf(GetFilePathSeparator()); + if (lastSlash >= 0) outStr = outStr.Substring(0, lastSlash+1); + } + CFRelease(cfPath); + CFRelease(bundleURL); +# else + // For Linux, anyway, we can try to find out our pid's executable via the /proc filesystem + // And it can't hurt to at least try it under other OS's, anyway... + char linkName[64]; sprintf(linkName, "/proc/%i/exe", getpid()); + char linkVal[1024]; + int rl = readlink(linkName, linkVal, sizeof(linkVal)); + if ((rl >= 0)&&(rl < (int)sizeof(linkVal))) + { + linkVal[rl] = '\0'; // gotta terminate the string! + char * lastSlash = strrchr(linkVal, '/'); // cut out the executable name, we only want the dir + if (lastSlash) *lastSlash = '\0'; + found = true; + outStr = linkVal; + } +# endif +#endif + } + break; + + case SYSTEM_PATH_TEMPFILES: // scratch directory + { +#ifdef WIN32 + found = muscleInRange((int)GetTempPathA(sizeof(buf), buf), 1, (int)sizeof(buf)-1); + if (found) outStr = buf; +#else + found = true; + outStr = "/tmp"; +#endif + } + break; + + case SYSTEM_PATH_USERHOME: // user's home directory + { + const char * homeDir = getenv("HOME"); +#ifdef WIN32 + if (homeDir == NULL) homeDir = getenv("USERPROFILE"); +#endif + if (homeDir) + { + found = true; + outStr = homeDir; + } + } + break; + + case SYSTEM_PATH_DESKTOP: // user's desktop directory + if (GetSystemPath(SYSTEM_PATH_USERHOME, outStr) == B_NO_ERROR) + { + found = true; + outStr += "Desktop"; // it's the same under WinXP, Linux, and OS/X, yay! + } + break; + + case SYSTEM_PATH_DOCUMENTS: // user's documents directory + if (GetSystemPath(SYSTEM_PATH_USERHOME, outStr) == B_NO_ERROR) + { + found = true; +#ifndef WIN32 + outStr += "Documents"; // For WinXP, it's the same as the home dir; for others, add Documents to the end +#endif + } + break; + + case SYSTEM_PATH_ROOT: // the highest possible directory + { +#ifdef WIN32 + const char * homeDrive = getenv("HOMEDRIVE"); + if (homeDrive) + { + outStr = homeDrive; + found = true; + } +#else + outStr = "/"; + found = true; +#endif + } + break; + } + + // Make sure the path name ends in a slash + if (found) + { + const char * c = GetFilePathSeparator(); + if (outStr.EndsWith(c) == false) outStr += c; + } + + return found ? B_NO_ERROR : B_ERROR; +}; + +status_t GetNumberOfProcessors(uint32 & retNumProcessors) +{ +#if defined(__BEOS__) || defined(__HAIKU__) + system_info info; + if (get_system_info(&info) == B_NO_ERROR) + { + retNumProcessors = info.cpu_count; + return B_NO_ERROR; + } +#elif defined(__APPLE__) + host_basic_info_data_t hostInfo; + mach_msg_type_number_t infoCount = HOST_BASIC_INFO_COUNT; + if (host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount) == KERN_SUCCESS) + { + retNumProcessors = (uint32) hostInfo.max_cpus; + return B_NO_ERROR; + } +#elif defined(WIN32) + SYSTEM_INFO info; + GetSystemInfo(&info); + retNumProcessors = info.dwNumberOfProcessors; + return B_NO_ERROR; +#elif defined(__linux__) + FILE * f = fopen("/proc/cpuinfo", "r"); + if (f) + { + retNumProcessors = 0; + char line[256]; + while(fgets(line, sizeof(line), f)) if (strncmp("processor", line, 9) == 0) retNumProcessors++; + fclose(f); + return B_NO_ERROR; + } +#else + (void) retNumProcessors; // dunno how to do it on this OS! +#endif + + return B_ERROR; +} + +}; // end namespace muscle diff --git a/system/SystemInfo.h b/system/SystemInfo.h new file mode 100644 index 00000000..35d3f16b --- /dev/null +++ b/system/SystemInfo.h @@ -0,0 +1,62 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSystemInfos_h +#define MuscleSystemInfos_h + +#include "util/String.h" + +namespace muscle { + +/** Returns a human-readable name for the operating system that the code has + * been compiled on. For example, "Windows", "MacOS/X", or "Linux". If the + * operating system's name is unknown, returns "Unknown". + * @param defaultString What to return if we don't know what the host OS is. Defaults to "Unknown". + */ +const char * GetOSName(const char * defaultString = "Unknown"); + +enum { + SYSTEM_PATH_CURRENT = 0, // our current working directory + SYSTEM_PATH_EXECUTABLE, // directory where our process's executable binary is + SYSTEM_PATH_TEMPFILES, // scratch directory where temp files may be stored + SYSTEM_PATH_USERHOME, // the current user's home folder + SYSTEM_PATH_DESKTOP, // the current user's desktop folder + SYSTEM_PATH_DOCUMENTS, // the current user's documents folder + SYSTEM_PATH_ROOT, // the root directory + NUM_SYSTEM_PATHS +}; + +/** Given a SYSTEM_PATH_* token, returns the system's directory path + * for that directory. This is an easy cross-platform way to determine + * where various various directories of interest are. + * @param whichPath a SYSTEM_PATH_* token. + * @param outStr on success, this string will contain the appopriate + * path name, The path is guaranteed to end with a file + * separator character (i.e. "/" or "\\", as appropriate). + * @returns B_NO_ERROR on success, or B_ERROR if the requested path could + * not be determined. + */ +status_t GetSystemPath(uint32 whichPath, String & outStr); + +/** Queries the number of CPU processing cores available on this computer. + * @param retNumProcessors On success, the number of cores is placed here + * @returns B_NO_ERROR on success, or B_ERROR if the number of processors + * could not be determined (e.g. call is unimplemented on this OS) + */ +status_t GetNumberOfProcessors(uint32 & retNumProcessors); + +/** Returns the file-path-separator character to use for this operating + * system: i.e. backslash for Windows, and forward-slash for every + * other operating system. + */ +inline const char * GetFilePathSeparator() +{ +#ifdef WIN32 + return "\\"; +#else + return "/"; +#endif +} + +}; // end namespace muscle + +#endif diff --git a/system/Thread.cpp b/system/Thread.cpp new file mode 100644 index 00000000..a2ce0471 --- /dev/null +++ b/system/Thread.cpp @@ -0,0 +1,412 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/Thread.h" +#include "util/NetworkUtilityFunctions.h" +#include "dataio/TCPSocketDataIO.h" // to get the proper #includes for recv()'ing +#include "system/SetupSystem.h" // for GetCurrentThreadID() + +#if defined(MUSCLE_PREFER_WIN32_OVER_QT) +# include // for _beginthreadex() +#endif + +#ifdef MUSCLE_SINGLE_THREAD_ONLY +# error "You're not allowed use the Thread class if you have the MUSCLE_SINGLE_THREAD_ONLY compiler constant defined!" +#endif + +namespace muscle { + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER +extern void DeadlockFinder_PrintAndClearLogEventsForCurrentThread(); +#endif + +Thread :: Thread(bool useMessagingSockets) : _useMessagingSockets(useMessagingSockets), _messageSocketsAllocated(!useMessagingSockets), _threadRunning(false), _suggestedStackSize(0), _threadStackBase(NULL) +{ +#if defined(MUSCLE_USE_QT_THREADS) + _thread.SetOwner(this); +#endif +} + +Thread :: ~Thread() +{ + MASSERT(IsInternalThreadRunning() == false, "You must not delete a Thread object while its internal thread is still running! (i.e. You must call thread.ShutdownInternalThread() or thread.WaitForThreadToExit() before deleting the Thread object)"); + CloseSockets(); +} + +const ConstSocketRef & Thread :: GetInternalThreadWakeupSocket() +{ + return GetThreadWakeupSocketAux(_threadData[MESSAGE_THREAD_INTERNAL]); +} + +const ConstSocketRef & Thread :: GetOwnerWakeupSocket() +{ + return GetThreadWakeupSocketAux(_threadData[MESSAGE_THREAD_OWNER]); +} + +const ConstSocketRef & Thread :: GetThreadWakeupSocketAux(ThreadSpecificData & tsd) +{ + if ((_messageSocketsAllocated == false)&&(CreateConnectedSocketPair(_threadData[MESSAGE_THREAD_INTERNAL]._messageSocket, _threadData[MESSAGE_THREAD_OWNER]._messageSocket) != B_NO_ERROR)) return GetNullSocket(); + + _messageSocketsAllocated = true; + return tsd._messageSocket; +} + +void Thread :: CloseSockets() +{ + if (_useMessagingSockets) + { + for (uint32 i=0; i= 0) + { + if (resume_thread(_thread) == B_NO_ERROR) return B_NO_ERROR; + else kill_thread(_thread); + } +#elif defined(__ATHEOS__) + if ((_thread = spawn_thread("MUSCLE Thread", InternalThreadEntryFunc, NORMAL_PRIORITY, 32767, this)) >= 0) + { + if (resume_thread(_thread) == B_NO_ERROR) return B_NO_ERROR; + } +#endif + + _threadRunning = false; // oops, nevermind, thread spawn failed + } + return B_ERROR; +} + +void Thread :: ShutdownInternalThread(bool waitForThread) +{ + if (IsInternalThreadRunning()) + { + SendMessageToInternalThread(MessageRef()); // a NULL message ref tells him to quit + if (waitForThread) WaitForInternalThreadToExit(); + } +} + +status_t Thread :: SendMessageToInternalThread(const MessageRef & ref) +{ + return SendMessageAux(MESSAGE_THREAD_INTERNAL, ref); +} + +status_t Thread :: SendMessageToOwner(const MessageRef & ref) +{ + return SendMessageAux(MESSAGE_THREAD_OWNER, ref); +} + +status_t Thread :: SendMessageAux(int whichQueue, const MessageRef & replyRef) +{ + status_t ret = B_ERROR; + ThreadSpecificData & tsd = _threadData[whichQueue]; + if (tsd._queueLock.Lock() == B_NO_ERROR) + { + if (tsd._messages.AddTail(replyRef) == B_NO_ERROR) ret = B_NO_ERROR; + bool sendNotification = (tsd._messages.GetNumItems() == 1); + (void) tsd._queueLock.Unlock(); + if ((sendNotification)&&(_signalLock.Lock() == B_NO_ERROR)) + { + switch(whichQueue) + { + case MESSAGE_THREAD_INTERNAL: SignalInternalThread(); break; + case MESSAGE_THREAD_OWNER: SignalOwner(); break; + } + _signalLock.Unlock(); + } + } + return ret; +} + +void Thread :: SignalInternalThread() +{ + SignalAux(MESSAGE_THREAD_OWNER); // we send a byte on the owner's socket and the byte comes out on the internal socket +} + +void Thread :: SignalOwner() +{ + SignalAux(MESSAGE_THREAD_INTERNAL); // we send a byte on the internal socket and the byte comes out on the owner's socket +} + +void Thread :: SignalAux(int whichSocket) +{ + if (_messageSocketsAllocated) + { + int fd = _threadData[whichSocket]._messageSocket.GetFileDescriptor(); + if (fd >= 0) + { + char junk = 'S'; + (void) send_ignore_eintr(fd, &junk, sizeof(junk), 0); + } + } +} + +int32 Thread :: GetNextReplyFromInternalThread(MessageRef & ref, uint64 wakeupTime) +{ + return WaitForNextMessageAux(_threadData[MESSAGE_THREAD_OWNER], ref, wakeupTime); +} + +int32 Thread :: WaitForNextMessageFromOwner(MessageRef & ref, uint64 wakeupTime) +{ + return WaitForNextMessageAux(_threadData[MESSAGE_THREAD_INTERNAL], ref, wakeupTime); +} + +int32 Thread :: WaitForNextMessageAux(ThreadSpecificData & tsd, MessageRef & ref, uint64 wakeupTime) +{ + int32 ret = -1; // pessimistic default + if (tsd._queueLock.Lock() == B_NO_ERROR) + { + if (tsd._messages.RemoveHead(ref) == B_NO_ERROR) ret = tsd._messages.GetNumItems(); + (void) tsd._queueLock.Unlock(); + + int msgfd; + if ((ret < 0)&&((msgfd = tsd._messageSocket.GetFileDescriptor()) >= 0)) // no Message available? then we'll have to wait until there is one! + { + // block until either + // (a) a new-message-signal-byte wakes us, or + // (b) we reach our wakeup/timeout time, or + // (c) a user-specified socket in the socket set selects as ready-for-something + for (uint32 i=0; i & t = tsd._socketSets[i]; + if (t.HasItems()) + { + for (HashtableIterator iter(t, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) + { + int nextFD = iter.GetKey().GetFileDescriptor(); + if (nextFD >= 0) tsd._multiplexer.RegisterSocketForEventsByTypeIndex(nextFD, i); + } + } + } + tsd._multiplexer.RegisterSocketForReadReady(msgfd); + + if (tsd._multiplexer.WaitForEvents(wakeupTime) >= 0) + { + for (uint32 j=0; j & t = tsd._socketSets[j]; + if (t.HasItems()) for (HashtableIterator iter(t, HTIT_FLAG_NOREGISTER); iter.HasData(); iter++) iter.GetValue() = tsd._multiplexer.IsSocketEventOfTypeFlagged(iter.GetKey().GetFileDescriptor(), j); + } + + if (tsd._multiplexer.IsSocketReadyForRead(msgfd)) // any signals from the other thread? + { + uint8 bytes[256]; + if (ConvertReturnValueToMuscleSemantics(recv_ignore_eintr(msgfd, (char *)bytes, sizeof(bytes), 0), sizeof(bytes), false) > 0) ret = WaitForNextMessageAux(tsd, ref, wakeupTime); + } + } + } + } + return ret; +} + +void Thread :: InternalThreadEntry() +{ + while(true) + { + MessageRef msgRef; + int32 numLeft = WaitForNextMessageFromOwner(msgRef); + if ((numLeft >= 0)&&(MessageReceivedFromOwner(msgRef, numLeft) != B_NO_ERROR)) break; + } +} + +status_t Thread :: MessageReceivedFromOwner(const MessageRef & ref, uint32) +{ + return ref() ? B_NO_ERROR : B_ERROR; +} + +status_t Thread :: WaitForInternalThreadToExit() +{ + if (_threadRunning) + { +#if defined(MUSCLE_USE_PTHREADS) + (void) pthread_join(_thread, NULL); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + (void) WaitForSingleObject(_thread, INFINITE); + ::CloseHandle(_thread); // Raymond Dahlberg's fix for handle-leak problem +#elif defined(MUSCLE_QT_HAS_THREADS) + (void) _thread.wait(); +#elif defined(__BEOS__) || defined(__HAIKU__) + status_t junk; + (void) wait_for_thread(_thread, &junk); +#elif defined(__ATHEOS__) + (void) wait_for_thread(_thread); +#endif + _threadRunning = false; + CloseSockets(); + return B_NO_ERROR; + } + else return B_ERROR; +} + +Queue * Thread :: LockAndReturnMessageQueue() +{ + ThreadSpecificData & tsd = _threadData[MESSAGE_THREAD_INTERNAL]; + return (tsd._queueLock.Lock() == B_NO_ERROR) ? &tsd._messages : NULL; +} + +status_t Thread :: UnlockMessageQueue() +{ + return _threadData[MESSAGE_THREAD_INTERNAL]._queueLock.Unlock(); +} + +Queue * Thread :: LockAndReturnReplyQueue() +{ + ThreadSpecificData & tsd = _threadData[MESSAGE_THREAD_OWNER]; + return (tsd._queueLock.Lock() == B_NO_ERROR) ? &tsd._messages : NULL; +} + +status_t Thread :: UnlockReplyQueue() +{ + return _threadData[MESSAGE_THREAD_OWNER]._queueLock.Unlock(); +} + +Hashtable Thread::_curThreads; +Mutex Thread::_curThreadsMutex; + +Thread * Thread :: GetCurrentThread() +{ + muscle_thread_key key = GetCurrentThreadKey(); + + Thread * ret = NULL; + if (_curThreadsMutex.Lock() == B_NO_ERROR) + { + (void) _curThreads.Get(key, ret); + _curThreadsMutex.Unlock(); + } + return ret; +} + +// This method is here to 'wrap' the internal thread's virtual method call with some standard setup/tear-down code of our own +void Thread::InternalThreadEntryAux() +{ + const uint32 threadStackBase = 0; // only here so we can get its address below + _threadStackBase = &threadStackBase; // remember this stack location so GetCurrentStackUsage() can reference it later on + + muscle_thread_key curThreadKey = GetCurrentThreadKey(); + if (_curThreadsMutex.Lock() == B_NO_ERROR) + { + (void) _curThreads.Put(curThreadKey, this); + _curThreadsMutex.Unlock(); + } + + if (_threadData[MESSAGE_THREAD_OWNER]._messages.HasItems()) SignalOwner(); + InternalThreadEntry(); + _threadData[MESSAGE_THREAD_INTERNAL]._messageSocket.Reset(); // this will wake up the owner thread with EOF on socket + + if (_curThreadsMutex.Lock() == B_NO_ERROR) + { + (void) _curThreads.Remove(curThreadKey); + _curThreadsMutex.Unlock(); + } + + _threadStackBase = NULL; +} + +Thread::muscle_thread_key Thread :: GetCurrentThreadKey() +{ +#if defined(MUSCLE_USE_PTHREADS) + return pthread_self(); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + return GetCurrentThreadId(); +#elif defined(MUSCLE_QT_HAS_THREADS) + return QThread::currentThread(); +#elif defined(__BEOS__) || defined(__HAIKU__) || defined(__ATHEOS__) + return find_thread(NULL); +#else + #error "Thread::GetCurrentThreadKey(): Unsupported platform?" +#endif +} + +bool Thread :: IsCallerInternalThread() const +{ + if (IsInternalThreadRunning() == false) return false; // we can't be him if he doesn't exist! + +#if defined(MUSCLE_USE_PTHREADS) + return pthread_equal(pthread_self(), _thread); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + return (_threadID == GetCurrentThreadId()); +#elif defined(MUSCLE_QT_HAS_THREADS) + return (QThread::currentThread() == static_cast(&_thread)); +#elif defined(__BEOS__) || defined(__HAIKU__) + return (_thread == find_thread(NULL)); +#elif defined(__ATHEOS__) + return (_thread == find_thread(NULL)); +#else + #error "Thread::IsCallerInternalThread(): Unsupported platform?" +#endif +} + +uint32 Thread :: GetCurrentStackUsage() const +{ + if ((IsCallerInternalThread() == false)||(_threadStackBase == NULL)) return 0; + + const uint32 curStackPos = 0; + const uint32 * curStackPtr = &curStackPos; + return (uint32) muscleAbs(curStackPtr-_threadStackBase); +} + +void CheckThreadStackUsage(const char * fileName, uint32 line) +{ + Thread * curThread = Thread::GetCurrentThread(); + if (curThread) + { + uint32 maxUsage = curThread->GetSuggestedStackSize(); + if (maxUsage != 0) // if the Thread doesn't have a suggested stack size, then we don't know what the limit is + { + uint32 curUsage = curThread->GetCurrentStackUsage(); + if (curUsage > maxUsage) + { + char buf[20]; + LogTime(MUSCLE_LOG_CRITICALERROR, "Thread %s exceeded its suggested stack usage (" UINT32_FORMAT_SPEC " > " UINT32_FORMAT_SPEC") at (%s:" UINT32_FORMAT_SPEC "), aborting program!\n", muscle_thread_id::GetCurrentThreadID().ToString(buf), curUsage, maxUsage, fileName, line); + MCRASH("MUSCLE Thread exceeded its suggested stack allowance"); + } + } + } + else + { + char buf[20]; + printf("Warning, CheckThreadStackUsage() called from non-MUSCLE thread %s at (%s:" UINT32_FORMAT_SPEC ")\n", muscle_thread_id::GetCurrentThreadID().ToString(buf), fileName, line); + } +} + + +}; // end namespace muscle diff --git a/system/Thread.h b/system/Thread.h new file mode 100644 index 00000000..ad17202d --- /dev/null +++ b/system/Thread.h @@ -0,0 +1,465 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleThread_h +#define MuscleThread_h + +#include "system/Mutex.h" +#include "message/Message.h" +#include "util/Queue.h" +#include "util/SocketMultiplexer.h" + +#ifdef MUSCLE_SINGLE_THREAD_ONLY +# error "You're not allowed use the Thread class if you have the MUSCLE_SINGLE_THREAD_ONLY compiler constant defined!" +#endif + +#if defined(MUSCLE_USE_PTHREADS) +# include +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) +# // empty +#elif defined(MUSCLE_QT_HAS_THREADS) +# define MUSCLE_USE_QT_THREADS +# if QT_VERSION >= 0x040000 +# include +# define QT_HAS_THREAD_PRIORITIES +# else +# include +# if QT_VERSION >= 0x030200 +# define QT_HAS_THREAD_PRIORITIES +# endif +# endif +#elif defined(__BEOS__) || defined(__HAIKU__) +# include +#elif defined(__ATHEOS__) +# include +#else +# error "Thread: threading support not implemented for this platform. You'll need to add support for your platform to the MUSCLE Lock and Thread classes for your OS before you can use the Thread class here (or define MUSCLE_USE_PTHREADS or QT_THREAD_SUPPORT to use those threading APIs, respectively)." +#endif + +namespace muscle { + +/** This class is an platform-independent class that creates an internally held thread and executes it. + * You will want to subclass Thread in order to specify the behaviour of the internally held thread... + * The default thread implementation doesn't do anything very useful. + * It also includes support for sending Messages to the thread, receiving reply Messages from the thread, + * and for waiting for the thread to exit. + */ +class Thread : private CountedObject +{ +public: + /** Constructor. Does very little (in particular, the internal thread is not started here... + * that happens when you call StartInternalThread()) + * @param useMessagingSockets Whether or not this thread should allocate a connected pair of + * sockets to handle messaging between the internal thread and the + * calling thread. Defaults to true. Don't set this to false unless + * you really know what you are doing, as setting it to false will + * break much of the Thread object's standard functionality. + */ + Thread(bool useMessagingSockets = true); + + /** Destructor. You must have made sure that the internal thread is no longer running before + * deleting the Thread object, or an assertion failure will occur. (You should make sure the + * internal thread is gone by calling WaitForInternalThreadToExit() before deleting this Thread object) + */ + virtual ~Thread(); + + /** Start the internal thread running + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, or thread is already running) + */ + virtual status_t StartInternalThread(); + + /** Returns true iff the thread is considered to be running. + * (Note that the thread is considered running from the time StartInternalThread() returns B_NO_ERROR + * until the time WaitForInternalThreadToExit() is called and returns B_NO_ERROR. Even if the thread + * terminates itself before then, it is still considered to be 'running' as far as we're concerned) + */ + bool IsInternalThreadRunning() const {return _threadRunning;} + + /** Returns true iff the calling thread is the internal thread, or false if the caller is any other thread. */ + bool IsCallerInternalThread() const; + + /** If the current thread is the internal thread of a Thread object, returns a pointer to that object. + * Otherwise, returns NULL. + */ + static Thread * GetCurrentThread(); + + /** Tells the internal thread to quit by sending it a NULL MessageRef, and then optionally + * waits for it to go away by calling WaitForInternalThreadToExit(). + * If the internal thread isn't running, this method is a no-op. + * You must call this before deleting the MessageTransceiverThread object! + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + * @param waitForThread if true, this method won't return until the thread is gone. Defaults to true. + * (if you set this to false, you'll need to also call WaitForThreadToExit() before deleting this object) + */ + virtual void ShutdownInternalThread(bool waitForThread = true); + + /** Blocks and won't return until after the internal thread exits. If you have called + * StartInternalThread(), you'll need to call this method (or ShutdownInternalThread()) + * before deleting this Thread object or calling StartInternalThread() again--even if + * your thread has already terminated itself! That way consistency is guaranteed and + * race conditions are avoided. + * @returns B_NO_ERROR on success, or B_ERROR if the internal thread wasn't running. + */ + status_t WaitForInternalThreadToExit(); + + /** Puts the given message into a message queue for the internal thread to pick up, + * and then calls SignalInternalThread() (if necessary) to signal the internal thread that a + * new message is ready. If the internal thread isn't currently running, then the + * MessageRef will be queued up and available to the internal thread to process when it is started. + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + * @param msg Reference to the message that is to be given to the internal thread. + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory) + */ + virtual status_t SendMessageToInternalThread(const MessageRef & msg); + + /** This method attempts to retrieve the next reply message that has been + * sent back to the main thread by the internal thread (via SendMessageToOwner()). + * @param ref On success, (ref) will be a reference to the new reply message. + * @param wakeupTime Time at which this method should stop blocking and return, + * even if there is no new reply message ready. If this value is + * 0 (the default) or otherwise less than the current time + * (as returned by GetRunTime64()), then this method does a + * non-blocking poll of the reply queue. + * If (wakeuptime) is set to MUSCLE_TIME_NEVER, then this method + * will block indefinitely, until a new reply is ready. + * @see GetOwnerSocketSet() for advanced control of this method's behaviour + * @returns The number of Messages left in the reply queue on success, or -1 on failure + * (The call timed out without any replies ever showing up) + */ + virtual int32 GetNextReplyFromInternalThread(MessageRef & ref, uint64 wakeupTime = 0); + + /** Locks the internal thread's message queue and returns a pointer to it. + * Since the queue is locked, you may examine or modify the queue safely. + * Once this method has returned successfully, you are responsible for unlocking the + * message queue again by calling UnlockMessageQueue(). If you don't, the Thread will + * remain locked and stuck! + * @returns a pointer to our internal Message queue, on success, or NULL on failure (couldn't lock) + */ + Queue * LockAndReturnMessageQueue(); + + /** Unlocks our internal message queue, so that the internal thread can again pop messages off of it. + * Should be called exactly once after each successful call to LockAndReturnMessageQueue(). + * After this call returns, it is no longer safe to use the pointer that was + * previously returned by LockAndReturnMessageQueue(). + * @returns B_NO_ERROR on success, or B_ERROR if the unlock call failed (perhaps it wasn't locked?) + */ + status_t UnlockMessageQueue(); + + /** Locks this Thread's reply queue and returns a pointer to it. Since the queue is + * locked, you may examine or modify the queue safely. + * Once this method has returned successfully, you are responsible for unlocking the + * message queue again by calling UnlockReplyQueue(). If you don't, the Thread will + * remain locked and stuck! + * @returns a pointer to our internal reply queue on success, or NULL on failure (couldn't lock) + */ + Queue * LockAndReturnReplyQueue(); + + /** Unlocks the reply message queue, so that the internal thread can again append messages to it. + * Should be called exactly once after each successful call to LockAndReturnReplyQueue(). + * After this call returns, it is no longer safe to use the pointer that was + * previously returned by LockAndReturnReplyQueue(). + * @returns B_NO_ERROR on success, or B_ERROR if the unlock call failed (perhaps it wasn't locked?) + */ + status_t UnlockReplyQueue(); + + /** Returns the socket that the main thread may select() for read on for wakeup-notification bytes. + * This Thread object's thread-signalling sockets will be allocated by this method if they aren't already allocated. + * @note this method will return a NULL reference if this Thread was created with constructor argument useMessagingSockets=false. + */ + const ConstSocketRef & GetOwnerWakeupSocket(); + + /** Enumeration of the socket sets that are available for blocking on; used in GetOwnerSocketSet() + * and GetInternalSocketSet() calls. + */ + enum { + SOCKET_SET_READ = 0, /**< set of sockets to watch for ready-to-read (i.e. incoming data available) */ + SOCKET_SET_WRITE, /**< set of sockets to watch for ready-to-write (i.e. outgoing buffer space available) */ + SOCKET_SET_EXCEPTION, /**< set of sockets to watch for exceptional conditions (implementation defined) */ + NUM_SOCKET_SETS /**< A guard value */ + }; + + /** This function returns a reference to one of the three socket-sets that + * GetNextReplyFromInternalThread() will optionally use to determine whether + * to return early. By default, all of the socket-sets are empty, and + * GetNextReplyFromInternalThread() will return only when a new Message + * has arrived from the internal thread, or when the timeout period has elapsed. + * + * However, in some cases it is useful to have GetNextReplyFromInternalThread() + * return under other conditions as well, such as when a specified socket becomes + * ready-to-read-from or ready-to-write-to. You can specify that a socket should be + * watched in this manner, by adding that socket to the appropriate socket set(s). + * For example, to tell GetNextReplyFromInternalThread() to always return when + * mySocket is ready to be written to, you would add mySocket to the SOCKET_SET_WRITE + * set, like this: + * + * _thread.GetOwnerSocketSet(SOCKET_SET_WRITE).Put(mySocket, false); + * + * (This only needs to be done once) After GetNextReplyFromInternalThread() + * returns, you can determine whether your socket is ready-to-write-to by checking + * its associated value in the table, like this: + * + * bool canWrite = false; + * _thread.GetOwnerSocketSet(SOCKET_SET_WRITE).Get(mySocket, canWrite); + * if (canWrite) printf("Socket is ready to be written to!\n"); + * + * @param socketSet SOCKET_SET_* indicating which socket-set to return a reference to. + * @note This method should only be called from the main thread! + */ + Hashtable & GetOwnerSocketSet(uint32 socketSet) {return _threadData[MESSAGE_THREAD_OWNER]._socketSets[socketSet];} + + /** As above, but returns a read-only reference. */ + const Hashtable & GetOwnerSocketSet(uint32 socketSet) const; + + /** Call this to set a suggested stack size for the new thread. If set to non-zero, StartInternalThread() + * will try to set the stack size of the thread it creates to this value. Note that calling this method + * has no effect on an internal thread that is already running. + * @param stackSizeBytes The number of bytes suggested for the internal thread to use, or 0 if + * it's best to just use the stack size provided by default by the OS. + */ + void SetSuggestedStackSize(uint32 stackSizeBytes) {_suggestedStackSize = stackSizeBytes;} + + /** Returns the current suggested stack size, as was previously set by SetSuggestedStackSize(). + * Returns 0 if there is no suggested stack size specified (which is the default behavior). + */ + uint32 GetSuggestedStackSize() const {return _suggestedStackSize;} + + /** If called from within the internal thread, returns (roughly) the amount of data (in bytes) + * currently on the thread's stack. Returns zero if the thread stack size is unknown + * (e.g. because this method was called from a different thread) + */ + uint32 GetCurrentStackUsage() const; + +protected: + /** If you are using the default implementation of InternalThreadEntry(), then this + * method will be called whenever a new MessageRef is received by the internal thread. + * Default implementation does nothing, and returns B_NO_ERROR if (msgRef) is valid, + * or B_ERROR if (msgRef) is a NULL reference. + * @note this method will not be called if this Thread was created with constructor argument useMessagingSockets=false. + * @param msgRef Reference to the just-received Message object. + * @param numLeft Number of Messages still left in the owner's message queue. + * @return B_NO_ERROR if you wish to continue processing, or B_ERROR if you wish to + * terminate the internal thread and go away. + */ + virtual status_t MessageReceivedFromOwner(const MessageRef & msgRef, uint32 numLeft); + + /** May be called by the internal thread to send a Message back to the owning thread. + * Puts the given MessageRef into the replies queue, and then calls SignalOwner() + * (if necessary) to notify the main thread that replies are pending. + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + * @param replyRef MessageRef to send back to the owning thread. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t SendMessageToOwner(const MessageRef & replyRef); + + /** You may override this method to be your Thread's execution entry point. + * Default implementation runs in a loop calling WaitForNextMessageFromOwner() and + * then MessageReceivedFromOwner(). In many cases, that is all you need, so you may + * not need to override this method. + */ + virtual void InternalThreadEntry(); + + /** This method is meant to be called by the internally held thread. + * It will attempt retrieve the next message that has been sent to the + * thread via SendMessageToThread(). + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + * @param ref On success, (ref) will be set to be a reference to the retrieved Message. + * @param wakeupTime Time at which this method should stop blocking and return, + * even if there is no new message ready. If this value is + * 0 or otherwise less than the current time (as returned by GetRunTime64()), + * then this method does a non-blocking poll of the queue. + * If (wakeuptime) is set to MUSCLE_TIME_NEVER (the default value), + * then this method will block indefinitely, until a Message is ready. + * @returns The number of Messages still remaining in the message queue on success, or + * -1 on failure (i.e. the call was aborted before any Messages ever showing up, + * and (ref) was not written to) + * @see GetInternalSocketSet() for advanced control of this method's behaviour + */ + virtual int32 WaitForNextMessageFromOwner(MessageRef & ref, uint64 wakeupTime = MUSCLE_TIME_NEVER); + + /** Called by SendMessageToThread() whenever there is a need to wake up the internal + * thread so that it will look at its reply queue. + * Default implementation sends a byte on a socket to implement this, + * but you can override this method to do it a different way if you need to. + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + */ + virtual void SignalInternalThread(); + + /** Called by SendMessageToOwner() whenever there is a need to wake up the owning + * thread so that it will look at its reply queue. Default implementation sends + * a byte to the main-thread-listen socket, but you can override this method to + * do it different way if you need to. + * @note this method will not work if this Thread was created with constructor argument useMessagingSockets=false. + */ + virtual void SignalOwner(); + + /** Returns the socket that the internal thread may select() for read on for wakeup-notification bytes. + * This Thread object's thread-signalling sockets will be allocated by this method if they aren't already allocated. + * @note this method will return a NULL reference if this Thread was created with constructor argument useMessagingSockets=false. + * @returns The socket fd that the thread is to listen on, or a NULL reference on error. + */ + const ConstSocketRef & GetInternalThreadWakeupSocket(); + + /** Locks the lock we use to serialize calls to SignalInternalThread() and + * SignalOwner(). Be sure to call UnlockSignallingLock() when you are done with the lock. + * @returns B_NO_ERROR on success, or B_ERROR on failure (couldn't lock) + */ + status_t LockSignalling() {return _signalLock.Lock();} + + /** Unlocks the lock we use to serialize calls to SignalInternalThread() and SignalOwner(). + * @returns B_NO_ERROR on success, or B_ERROR on failure (couldn't unlock) + */ + status_t UnlockSignalling() {return _signalLock.Unlock();} + + /** Closes all of our threading sockets, if they are open. */ + void CloseSockets(); + + /** This function returns a reference to one of the three socket-sets that + * WaitForNextMessageFromOwner() will optionally use to determine whether + * to return early. By default, all of the socket-sets are empty, and + * WaitForNextMessageFromOwner() will return only when a new Message + * has arrived from the owner thread, or when the timeout period has elapsed. + * + * However, in some cases it is useful to have WaitForNextMessageFromOwner() + * return under other conditions as well, such as when a specified socket becomes + * ready-to-read-from or ready-to-write-to. You can specify that a socket should be + * watched in this manner, by adding that socket to the appropriate socket set(s). + * For example, to tell WaitForNextMessageFromOwner() to always return when + * mySocket is ready to be written to, you would add mySocket to the SOCKET_SET_WRITE + * set, like this: + * + * _thread.GetInternalSocketSet(SOCKET_SET_WRITE).Put(mySocket, false); + * + * (This only needs to be done once) After WaitForNextMessageFromOwner() + * returns, you can determine whether your socket is ready-to-write-to by checking + * its associated value in the table, like this: + * + * bool canWrite = false; + * _thread.GetInternalSocketSet(SOCKET_SET_WRITE).Get(mySocket, canWrite); + * if (canWrite) printf("Socket is ready to be written to!\n"); + * + * @param socketSet SOCKET_SET_* indicating which socket-set to return a reference to. + * @note This method should only be called from the internal thread! + */ + Hashtable & GetInternalSocketSet(uint32 socketSet) {return _threadData[MESSAGE_THREAD_INTERNAL]._socketSets[socketSet];} + + /** As above, but returns a read-only reference. */ + const Hashtable & GetInternalSocketSet(uint32 socketSet) const; + +private: + Thread(const Thread & rhs); // deliberately private and unimplemented + Thread & operator = (const Thread & rhs); // deliberately private and unimplemented + + /** This class encapsulates data that is used by one of our two threads (internal or owner). + * It's put in a class like this so that I can easily access two copies of everything. + */ + class ThreadSpecificData + { + public: + ThreadSpecificData() {/* empty */} + + Mutex _queueLock; + ConstSocketRef _messageSocket; + Queue _messages; + Hashtable _socketSets[NUM_SOCKET_SETS]; // (socket -> isFlagged) + SocketMultiplexer _multiplexer; + }; + + status_t StartInternalThreadAux(); + const ConstSocketRef & GetThreadWakeupSocketAux(ThreadSpecificData & tsd); + int32 WaitForNextMessageAux(ThreadSpecificData & tsd, MessageRef & ref, uint64 wakeupTime = MUSCLE_TIME_NEVER); + status_t SendMessageAux(int whichQueue, const MessageRef & ref); + void SignalAux(int whichSocket); + void InternalThreadEntryAux(); + + enum { + MESSAGE_THREAD_INTERNAL = 0, // internal thread's (input queue, socket to block on) + MESSAGE_THREAD_OWNER, // main thread's (input queue, socket to block on) + NUM_MESSAGE_THREADS + }; + + const bool _useMessagingSockets; + bool _messageSocketsAllocated; + + ThreadSpecificData _threadData[NUM_MESSAGE_THREADS]; + + bool _threadRunning; + Mutex _signalLock; + +#if defined(MUSCLE_USE_PTHREADS) + pthread_t _thread; + static void * InternalThreadEntryFunc(void * This) {((Thread *)This)->InternalThreadEntryAux(); return NULL;} +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + ::HANDLE _thread; + DWORD _threadID; + static DWORD WINAPI InternalThreadEntryFunc(LPVOID This) {((Thread*)This)->InternalThreadEntryAux(); return 0;} +#elif defined(MUSCLE_USE_QT_THREADS) + class MuscleQThread : public QThread + { + public: + MuscleQThread() : _owner(NULL) {/* empty */} // _owner not set here, for VC++6 compatibility + void SetOwner(Thread * owner) {_owner = owner;} + virtual void run() {_owner->InternalThreadEntryAux();} + + private: + Thread * _owner; + }; + MuscleQThread _thread; + friend class MuscleQThread; + +protected: +# ifdef QT_HAS_THREAD_PRIORITIES + /** Returns the priority that the internal thread should be launched under. Only available + * when using Qt 3.2 or higher, so you may want to wrap your references to this method in an + * #ifdef QT_HAS_THREAD_PRIORITIES test, to make sure your code remains portable. + * @returns the QThread::Priority value, as described in the QThread documentation. Default + * implementation always returns QThread::InheritPriority. + */ + virtual QThread::Priority GetInternalQThreadPriority() const {return QThread::InheritPriority;} +# endif +#endif + +private: +#if defined(MUSCLE_USE_PTHREADS) + typedef pthread_t muscle_thread_key; +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + typedef DWORD muscle_thread_key; +#elif defined(MUSCLE_USE_QT_THREADS) + typedef QThread * muscle_thread_key; +#elif defined(__BEOS__) || defined(__HAIKU__) + typedef thread_id muscle_thread_key; + thread_id _thread; + static int32 InternalThreadEntryFunc(void * This) {((Thread *)This)->InternalThreadEntryAux(); return 0;} +#elif defined(__ATHEOS__) + typedef thread_id muscle_thread_key; + thread_id _thread; + static void InternalThreadEntryFunc(void * This) {((Thread *)This)->InternalThreadEntryAux();} +#endif + + static muscle_thread_key GetCurrentThreadKey(); + static Mutex _curThreadsMutex; + static Hashtable _curThreads; + uint32 _suggestedStackSize; + const uint32 * _threadStackBase; +}; + +#ifdef MUSCLE_AVOID_CHECK_THREAD_STACK_USAGE +# define CHECK_THREAD_STACK_USAGE +#else +/** Macro for checking the current Thread's stack usage, and aborting with an error message + * if the Thread has overrun it's suggested stack-space. Note that this macro only works + * if called from a Thread object's internal thread, and only for Thread objects that + * had SetSuggestedStackSize() called on them before the internal thread was started. + */ +# define CHECK_THREAD_STACK_USAGE CheckThreadStackUsage(__FILE__, __LINE__) +#endif + +/** Typically called by the CHECK_THREAD_STACK_USAGE macro. Checks the current thread's stack usage, + * and causes an assertion failure (and program exit) to occur if the thread is using more stack + * space than was suggested via SetSuggestedStackSize(). + * @param fileName the name of the source code file this method was called from (i.e. __FILE__) + * @param line the line number that this method was called from (i.e. __LINE__) + */ +void CheckThreadStackUsage(const char * fileName, uint32 line); + +}; // end namespace muscle + +#endif diff --git a/system/ThreadLocalStorage.h b/system/ThreadLocalStorage.h new file mode 100644 index 00000000..47b99886 --- /dev/null +++ b/system/ThreadLocalStorage.h @@ -0,0 +1,187 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleThreadLocalStorage_h +#define MuscleThreadLocalStorage_h + +#include "system/Thread.h" // to get the #defines that are calculated in Thread.h + +#if defined(MUSCLE_USE_PTHREADS) + // deliberately empty +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + // deliberately empty +#elif defined(MUSCLE_QT_HAS_THREADS) && (QT_VERSION >= 0x030200) +# if (QT_VERSION >= 0x040000) +# include +# else +# include +# endif +# define MUSCLE_USE_QT_THREADLOCALSTORAGE +#else +# error "muscle/ThreadLocalStorage.h: ThreadLocalStorage not implemented for this environment, sorry!" +#endif + +namespace muscle { + +/** This class implements easy-to-use thread-local-storage. Typically what you would do is + * to create a ThreadLocalStorage object at the beginning of program execution (e.g. as a global + * variable, or at the top of main()). Then the various threads of your program can call + * GetThreadLocalObject() on it, and each thread will receive a pointer that is unique to that + * thread, which it can use without needing to do any serialization. + */ +template class ThreadLocalStorage +{ +public: + /** Constructor. + * @param freeHeldObjectsOnExit If left as true (the default value) then any thread-local objects we allocate + * will be automatically deleted when their thread exits. If set to false, they will + * be left in-place. + */ + ThreadLocalStorage(bool freeHeldObjectsOnExit = true) : _freeHeldObjects(freeHeldObjectsOnExit) + { +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + // Avoid re-entrancy problems when the deadlock callbacks are using ThreadLocalStorage to initialize themselves! + _allocedObjsMutex.AvoidFindDeadlockCallbacks(); +#endif + +#if defined(MUSCLE_USE_PTHREADS) + _isKeyValid = (pthread_key_create(&_key, _freeHeldObjects?((PthreadDestructorFunction)DeleteObjFunc):NULL) == 0); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + _tlsIndex = TlsAlloc(); +#endif + } + + /** Destructor. Deletes all objects that were previously created by this ThreadLocalStorage object. */ + virtual ~ThreadLocalStorage() + { + if (IsSetupOkay()) + { +#if defined(MUSCLE_USE_PTHREADS) + pthread_key_delete(_key); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + TlsFree(_tlsIndex); +#endif + } +#if !(defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) || defined(MUSCLE_USE_PTHREADS)) + if (_freeHeldObjects) for (uint32 i=0; i<_allocedObjs.GetNumItems(); i++) delete _allocedObjs[i]; +#endif + } + + /** Returns a pointer to the copy of the thread-local-object to be used by the calling thread, or NULL if no such object is installed. */ + ObjType * GetThreadLocalObject() const {return IsSetupOkay() ? GetThreadLocalObjectAux() : NULL;} + + /** Returns a pointer to the thread-local object used by the calling thread, if there is one. + * If there isn't one, a default object will be created and installed for the current thread, and a pointer + * to that object will be returned. + * If the creation of the default object fails, NULL will be returned. + */ + ObjType * GetOrCreateThreadLocalObject() + { + ObjType * ret = GetThreadLocalObject(); + if (ret) return ret; + + // If we got here, we'll auto-create an object to return + ret = newnothrow ObjType; + if (ret == NULL) {WARN_OUT_OF_MEMORY; return NULL;} + + if (SetThreadLocalObject(ret) == B_NO_ERROR) return ret; + else + { + delete ret; + return NULL; + } + } + + /** Sets the thread-local object to (newObj). + * @param newObj An object to install as the thread-local object, or NULL. + * If (newObj) is non-NULL, then this class takes ownership of the object + * and will call delete on it at the appropriate time. If (newObj) is + * NULL, then this method will free any existing object only. + * @returns B_NO_ERROR if (newObj) was successfully installed, or B_ERROR if it was not. + * If an error occurred, then (newObj) still belongs to the caller. + */ + status_t SetThreadLocalObject(ObjType * newObj) + { + if (IsSetupOkay() == false) return B_ERROR; + + ObjType * oldObj = GetThreadLocalObjectAux(); + if (oldObj == newObj) return B_NO_ERROR; // nothing to do! + +#if defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) || defined(MUSCLE_USE_PTHREADS) + return SetThreadLocalObjectAux(newObj); // pthreads and Qt manage memory so we don't have to +#else + if (_allocedObjsMutex.Lock() != B_NO_ERROR) return B_ERROR; + + status_t ret = B_NO_ERROR; + if (SetThreadLocalObjectAux(newObj) == B_NO_ERROR) // SetThreadLocalObjectAux() MUST be called first to avoid re-entrancy trouble! + { + int32 idx = oldObj ? _allocedObjs.IndexOf(oldObj) : -1; + if (idx >= 0) + { + if (newObj) _allocedObjs[idx] = newObj; + else (void) _allocedObjs.RemoveItemAt(idx); + } + else if (newObj) (void) _allocedObjs.AddTail(newObj); + } + else ret = B_ERROR; + + _allocedObjsMutex.Unlock(); + if (ret == B_NO_ERROR) delete oldObj; + return ret; +#endif + } + +private: +#if defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) + QThreadStorage _storage; +#else +# if defined(MUSCLE_USE_PTHREADS) + typedef void (*PthreadDestructorFunction )(void *); + static void DeleteObjFunc(void * o) {delete ((ObjType *)o);} + bool _isKeyValid; + pthread_key_t _key; +# elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + DWORD _tlsIndex; +# endif + Mutex _allocedObjsMutex; + Queue _allocedObjs; +#endif + + bool _freeHeldObjects; + + inline ObjType * GetThreadLocalObjectAux() const + { +#if defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) + return _storage.localData(); +#elif defined(MUSCLE_USE_PTHREADS) + return (ObjType *) pthread_getspecific(_key); +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + return (ObjType *) TlsGetValue(_tlsIndex); +#endif + } + + inline status_t SetThreadLocalObjectAux(ObjType * o) + { +#if defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) + _storage.setLocalData(o); return B_NO_ERROR; +#elif defined(MUSCLE_USE_PTHREADS) + return (pthread_setspecific(_key, o) == 0) ? B_NO_ERROR : B_ERROR; +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + return TlsSetValue(_tlsIndex, o) ? B_NO_ERROR : B_ERROR; +#endif + } + + inline bool IsSetupOkay() const + { +#if defined(MUSCLE_USE_QT_THREADLOCALSTORAGE) + return true; +#elif defined(MUSCLE_USE_PTHREADS) + return _isKeyValid; +#elif defined(MUSCLE_PREFER_WIN32_OVER_QT) + return (_tlsIndex != TLS_OUT_OF_INDEXES); +#endif + } +}; + +}; // end namespace muscle + +#endif diff --git a/system/ThreadPool.cpp b/system/ThreadPool.cpp new file mode 100644 index 00000000..d454e5ce --- /dev/null +++ b/system/ThreadPool.cpp @@ -0,0 +1,228 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/ThreadPool.h" + +namespace muscle { + +static Message _dummyMsg; + +IThreadPoolClient :: ~IThreadPoolClient() +{ + MASSERT(_threadPool == NULL, "You must not delete an IThreadPoolClient Thread object it is still registered with a ThreadPool! (Call SetThreadPool(NULL) on it BEFORE deleting it!)"); +} + +status_t IThreadPoolClient :: SendMessageToThreadPool(const MessageRef & msg) +{ + return ((_threadPool)&&(msg())) ? _threadPool->SendMessageToThreadPool(this, msg) : B_ERROR; +} + +void IThreadPoolClient :: SetThreadPool(ThreadPool * tp) +{ + if (tp != _threadPool) + { + if (_threadPool) _threadPool->UnregisterClient(this); + _threadPool = tp; + if (_threadPool) _threadPool->RegisterClient(this); + } +} + +ThreadPool :: ThreadPool(uint32 maxThreadCount) : _maxThreadCount(maxThreadCount), _shuttingDown(false), _threadIDCounter(0) +{ + (void) _availableThreads.EnsureSize(maxThreadCount); + (void) _activeThreads.EnsureSize(maxThreadCount); +} + +ThreadPool :: ~ThreadPool() +{ + (void) Shutdown(); +} + +uint32 ThreadPool :: Shutdown() +{ + MutexGuard mg(_poolLock); + _shuttingDown = true; + + for (HashtableIterator iter(_availableThreads); iter.HasData(); iter++) iter.GetValue()()->ShutdownInternalThread(); + for (HashtableIterator iter(_activeThreads); iter.HasData(); iter++) iter.GetValue()()->ShutdownInternalThread(); + for (HashtableIterator iter(_registeredClients); iter.HasData(); iter++) iter.GetKey()->_threadPool = NULL; // so they won't try to unregister from us + + uint32 ret = _availableThreads.GetNumItems()+_activeThreads.GetNumItems()+_registeredClients.GetNumItems()+_pendingMessages.GetNumItems()+_deferredMessages.GetNumItems()+_waitingForCompletion.GetNumItems(); + _availableThreads.Clear(); + _activeThreads.Clear(); + _registeredClients.Clear(); + _pendingMessages.Clear(); + _deferredMessages.Clear(); + _waitingForCompletion.Clear(); + return ret; +} + +void ThreadPool :: RegisterClient(IThreadPoolClient * client) +{ + MutexGuard mg(_poolLock); + (void) _registeredClients.Put(client, false); +} + +static bool AreThereMessagesInQueue(const Queue * mq) {return ((mq)&&(mq->HasItems()));} + +// Assumes _poolLock is locked! +bool ThreadPool :: DoesClientHaveMessagesOutstandingUnsafe(IThreadPoolClient * client) const +{ + return ((_registeredClients.GetWithDefault(client))||(AreThereMessagesInQueue(_pendingMessages.Get(client)))||(AreThereMessagesInQueue(_deferredMessages.Get(client)))); +} + +void ThreadPool :: UnregisterClient(IThreadPoolClient * client) +{ + ConstSocketRef waitSock; + + { + // If this client has any Messages pending, we need to block until they are gone + MutexGuard mg(_poolLock); + if (DoesClientHaveMessagesOutstandingUnsafe(client)) + { + ConstSocketRef signalSock; + if (CreateConnectedSocketPair(waitSock, signalSock, true) == B_NO_ERROR) (void) _waitingForCompletion.Put(client, signalSock); + else printf("ThreadPool::UnregisterClient: Couldn't set up socket pair for shutdown notification!\n"); + } + } + + char buf; + if (waitSock()) (void) ReadData(waitSock, &buf, sizeof(buf), true); // block here until ReadData() returns, indicating that we can continue + + // final cleanup + MutexGuard mg(_poolLock); + (void) _registeredClients.Remove(client); + (void) _pendingMessages.Remove(client); + (void) _deferredMessages.Remove(client); +} + +status_t ThreadPool :: ThreadPoolThread :: SendMessagesToInternalThread(IThreadPoolClient * client, Queue & mq) +{ + MASSERT((_currentClient == NULL), "ThreadPoolThread::SendMessagesToInternalThread: _currentClient isn't NULL!"); + MASSERT((_internalQueue.IsEmpty()), "ThreadPoolThread::SendMessagesToInternalThread: _internalQueue isn't empty!"); + + _currentClient = client; + _internalQueue.SwapContents(mq); + if (SendMessageToInternalThread(MessageRef(&_dummyMsg, false)) == B_NO_ERROR) return B_NO_ERROR; // Send an empty Message, just to signal the internal thread + else + { + // roll back! + _currentClient = NULL; + _internalQueue.SwapContents(mq); + return B_NO_ERROR; + } +} + +status_t ThreadPool :: ThreadPoolThread :: MessageReceivedFromOwner(const MessageRef & msgRef, uint32 /*numLeft*/) +{ + if (msgRef() == NULL) return B_ERROR; // time to go away! + + MASSERT((_currentClient != NULL), "ThreadPoolThread::MessageReceivedFromOwner: _currentClient is NULL!"); + MASSERT((_internalQueue.HasItems()), "ThreadPoolThread::MessageReceivedFromOwner: _internalQueue is empty!"); + + while(_internalQueue.HasItems()) + { + _threadPool->MessageReceivedFromThreadPoolAux(_currentClient, _internalQueue.Head(), _internalQueue.GetNumItems()-1); + _internalQueue.RemoveHead(); + } + + IThreadPoolClient * client = _currentClient; + _currentClient = NULL; // we need _currentClient to be NULL when we call ThreadFinishedProcessingClientMessages() + _threadPool->ThreadFinishedProcessingClientMessages(_threadID, client); + return B_NO_ERROR; +} + +status_t ThreadPool :: SendMessageToThreadPool(IThreadPoolClient * client, const MessageRef & msg) +{ + MutexGuard mg(_poolLock); + + bool * isBeingHandled = mg.IsMutexLocked() ? _registeredClients.Get(client) : NULL; + if (isBeingHandled == NULL) return B_ERROR; + + Queue * mq = ((*isBeingHandled)?_deferredMessages:_pendingMessages).GetOrPut(client); + if ((mq == NULL)||(mq->AddTail(msg) != B_NO_ERROR)) return B_ERROR; + if ((*isBeingHandled == false)&&(mq->GetNumItems() == 1)) DispatchPendingMessagesUnsafe(); + return B_NO_ERROR; +} + +// Note: This method assumes the _poolLock is already unlocked! +void ThreadPool :: DispatchPendingMessagesUnsafe() +{ + if (_shuttingDown) return; // no sense dispatching more messages if we're in the process of shutting down + + while(_pendingMessages.HasItems()) + { + IThreadPoolClient * client = *_pendingMessages.GetFirstKey(); + Queue * mq = _pendingMessages.GetFirstValue(); + bool * isBeingHandled = _registeredClients.Get(client); + if ((isBeingHandled)&&(mq)&&(mq->HasItems())) // we actually know mq will be non-NULL, but testing it makes clang++ happy + { + MASSERT(*isBeingHandled == false, "DispatchPendingMessagesUnsafe: Client that is being handled is in the _pendingMessages table!"); + + if ((_availableThreads.IsEmpty())&&(_activeThreads.GetNumItems() < _maxThreadCount)) + { + // demand-allocate a new Thread for us to use + ThreadPoolThreadRef tRef(newnothrow ThreadPoolThread(this, _threadIDCounter++)); + if (tRef() == NULL) {WARN_OUT_OF_MEMORY; break;} + if (StartInternalThread(*tRef()) != B_NO_ERROR) {LogTime(MUSCLE_LOG_ERROR, "ThreadPool: Error launching thread!\n"); break;} + if (_availableThreads.Put(tRef()->GetThreadID(), tRef) != B_NO_ERROR) {tRef()->ShutdownInternalThread(); break;} // should never happen, but just in case + } + + if (_availableThreads.HasItems()) + { + // Okay, there's an idle thread awaiting something to do, so we'll just pass the next client's requests off to him. + ThreadPoolThreadRef tRef = *_availableThreads.GetLastValue(); // use the last thread because it's "hottest" in cache + if (_availableThreads.MoveToTable(tRef()->GetThreadID(), _activeThreads) == B_NO_ERROR) + { + if (tRef()->SendMessagesToInternalThread(client, *mq) == B_NO_ERROR) + { + *isBeingHandled = true; // this is to note that this client now has a Thread that is processing its data + (void) _pendingMessages.RemoveFirst(); + } + else + { + (void) _activeThreads.MoveToTable(tRef()->GetThreadID(), _availableThreads); // roll back! + break; + } + } + else break; // wtf!? + } + else break; // nothing more we can do for now, all our pool threads are already busy + } + else _pendingMessages.RemoveFirst(); // nothing to do for this client!? + } +} + +void ThreadPool :: ThreadFinishedProcessingClientMessages(uint32 threadID, IThreadPoolClient * client) +{ + MutexGuard mg(_poolLock); + if (_shuttingDown) return; + + bool * isClientBeingHandled = _registeredClients.Get(client); + if (isClientBeingHandled) + { + MASSERT(*isClientBeingHandled, "ThreadFinishedProcessingClientMessages: *isBeingClientHandled if false!"); + *isClientBeingHandled = false; + + Queue * deferredMessages = _deferredMessages.Get(client); + if ((deferredMessages)&&(deferredMessages->HasItems())) + { + Queue * pendingMessages = _pendingMessages.GetOrPut(client); + if (pendingMessages) + { + MASSERT(pendingMessages->IsEmpty(), "ThreadPool::ThreadFinishedProcessingClientMessages(): pendingMessages isn't empty!"); + pendingMessages->SwapContents(*deferredMessages); + } + } + } + (void) _activeThreads.MoveToTable(threadID, _availableThreads); // this thread is now available again for further tasks + DispatchPendingMessagesUnsafe(); + + if (DoesClientHaveMessagesOutstandingUnsafe(client) == false) _waitingForCompletion.Remove(client); // wake up user thread if he's waiting in UnregisterClient() +} + +status_t ThreadPool :: StartInternalThread(Thread & thread) +{ + return thread.StartInternalThread(); +} + +}; // end namespace muscle diff --git a/system/ThreadPool.h b/system/ThreadPool.h new file mode 100644 index 00000000..0f950b71 --- /dev/null +++ b/system/ThreadPool.h @@ -0,0 +1,161 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleThreadPool_h +#define MuscleThreadPool_h + +#include "system/Thread.h" +#include "system/Mutex.h" +#include "util/Queue.h" +#include "util/RefCount.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/ObjectPool.h" // for AbstractObjectRecycler + +namespace muscle { + +class ThreadPool; + +/** This is an interface class that should be implemented by objects that want to make use of a ThreadPool. */ +class IThreadPoolClient +{ +public: + /** Constructor. + * @param threadPool Pointer to the ThreadPool object this client should register with, or NULL if you wish this client + * to start out unregistered. (If the latter, be sure to call SetThreadPool() later on). + */ + IThreadPoolClient(ThreadPool * threadPool) : _threadPool(NULL) {SetThreadPool(threadPool);} + + /** Destructor. Note that if this object is still registered with a ThreadPool when this destructor is called, + * an assertion failure will be triggered -- registered IThreadPoolClient objects MUST call SetThreadPool(NULL) + * BEFORE any part of them is destroyed, in order to avoid race conditions between their constructors and + * their MessageReceivedFromThreadPool() method callbacks. + */ + virtual ~IThreadPoolClient(); + + /** Sends the specified MessageRef object to the ThreadPool for later handling. + * @param msg A Message for the ThreadPool to handle. Our MessageReceivedFromThreadPoolClient() method + * will be called in the near future, from within a ThreadPool thread. + * @returns B_NO_ERROR if the Message was scheduled for execution by a thread in the ThreadPool, or B_ERROR if it wasn't. + * Messages are guaranteed to be processed in the order that they were passed to this method, but there is no + * guarantee that they will all be processed in the same thread as each other. + */ + status_t SendMessageToThreadPool(const MessageRef & msg); + + /** Changes this IThreadPoolClient to use a different ThreadPool. This will unregister this client + * from the current ThreadPool if necessary, and register with the new one if necessary. + * + * @param tp The new ThreadPool to register with, or NULL if you only want to unregister from the old one. + * + * Note that it is REQUIRED for registered IThreadPoolClient objects to call SetThreadPool(NULL) to + * un-register themselves from their ThreadPool BEFORE their destructors start to tear down their state; + * otherwise race conditions will result if the ThreadPool object happens to call MessageReceivedFromThreadPoolClient() + * on the partially-deconstructed IThreadPoolClient object. + * + * Note that if Messages callbacks for this IThreadPoolClient are still pending in the ThreadPool object + * when this method un-registers the IThreadPoolClient object, then this method will block until after all those + * callbacks have completed. + */ + void SetThreadPool(ThreadPool * tp); + + /** Returns a pointer to the ThreadPool this client is currently registered with, or NULL if it isn't registered anywhere. */ + ThreadPool * GetThreadPool() const {return _threadPool;} + +protected: + /** Called from inside one of the ThreadPool's threads, some time after SendMessageToThreadPool(msg) was + * called by the IThreadPoolClient. + * @param msg The MessageRef that was passed to SendMessageToThreadPool(). + * @param numLeft The number of additional Messages that will arrive in this batch after this one. + * Note that since this method will be called in a different thread from the thread that called + * SendMessageToThreadPool(), implementations of this method need to be careful when accessing member + * variables to avoid race conditions. + */ + virtual void MessageReceivedFromThreadPool(const MessageRef & msg, uint32 numLeft) = 0; + +private: + friend class ThreadPool; + + ThreadPool * _threadPool; +}; + +/** This class allows you to multiplex the handling of a large number of parallel Message streams + * into a finite number of actual Threads. By doing so you can (roughly) simulate the behavior + * of a much larger number of Threads than are actually created. + * + * This class is Thread-safe, in that you can have IThreadPoolClients using it from different + * threads simultaneously, and it will still work as expected. + */ +class ThreadPool : private AbstractObjectRecycler, private CountedObject +{ +public: + /** Constructor. + * @param maxThreadCount The maximum number of Threads this ThreadPool object will be allowed + * to create at once. Defaults to 16. + */ + ThreadPool(uint32 maxThreadCount = 16); + + /** Destructor. */ + virtual ~ThreadPool(); + + /** Returns the maximum number of threads this ThreadPool is allowed to keep around at once time (as specified in the constructor) */ + uint32 GetMaxThreadCount() const {return _maxThreadCount;} + +protected: + /** Starts the specified Thread object's internal thread running. + * Broken out into a virtual method so that the Thread's attributes (stack size, etc) can be customized if desired. + * Default implementation just calls StartInternalThread() on the thread object. + * @param thread The Thread object to start. + * @returns B_NO_ERROR if the Thread was successfully started, or B_ERROR otherwise. + */ + virtual status_t StartInternalThread(Thread & thread); + +private: + virtual void RecycleObject(void * /*obj*/) {/* empty */} + virtual uint32 FlushCachedObjects() {return Shutdown();} // called by SetupSystem destructor, to avoid crashes on exit + uint32 Shutdown(); + + class ThreadPoolThread : public Thread, public RefCountable + { + public: + ThreadPoolThread(ThreadPool * tp, uint32 threadID) : _threadID(threadID), _threadPool(tp), _currentClient(NULL) {/* empty */} + + uint32 GetThreadID() const {return _threadID;} + status_t SendMessagesToInternalThread(IThreadPoolClient * client, Queue & mq); + + protected: + virtual status_t MessageReceivedFromOwner(const MessageRef & msgRef, uint32 numLeft); + + private: + const uint32 _threadID; + ThreadPool * _threadPool; + IThreadPoolClient * _currentClient; + Queue _internalQueue; + }; + DECLARE_REFTYPES(ThreadPoolThread); + + friend class IThreadPoolClient; + friend class ThreadPoolThread; + + void RegisterClient(IThreadPoolClient * client); + void UnregisterClient(IThreadPoolClient * client); + status_t SendMessageToThreadPool(IThreadPoolClient * client, const MessageRef & msg); + void DispatchPendingMessagesUnsafe(); // _poolLock must be locked when this is called! + void ThreadFinishedProcessingClientMessages(uint32 threadID, IThreadPoolClient * client); + bool DoesClientHaveMessagesOutstandingUnsafe(IThreadPoolClient * client) const; + void MessageReceivedFromThreadPoolAux(IThreadPoolClient * client, const MessageRef & msg, uint32 numLeft) {client->MessageReceivedFromThreadPool(msg, numLeft);} // just to skirt protected-member issues + + const uint32 _maxThreadCount; + + Mutex _poolLock; + bool _shuttingDown; + + uint32 _threadIDCounter; + Hashtable _availableThreads; + Hashtable _activeThreads; + Hashtable _registeredClients; // registered clients -> (true iff a Thread is currently handling them) + Hashtable > _pendingMessages; // Messages ready to be sent to a pool Thread + Hashtable > _deferredMessages; // Messages to be be sent to a pool Thread when the current Thread's Messages are done + Hashtable _waitingForCompletion; // Clients who are blocked in UnregisterClient() waiting for Messages to complete processing +}; + +}; // end namespace muscle + +#endif diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..f1571dca --- /dev/null +++ b/test/Makefile @@ -0,0 +1,320 @@ +DEFINES += -DMUSCLE_ENABLE_ZLIB_ENCODING +DEFINES += -DMUSCLE_USE_PTHREADS +DEFINES += -DMUSCLE_AVOID_SIGNAL_HANDLING +#DEFINES += -DMUSCLE_AVOID_NEWNOTHROW +#DEFINES += -DMUSCLE_USE_MUTEXES_FOR_ATOMIC_OPERATIONS +#DEFINES += -DMUSCLE_ENABLE_DEADLOCK_FINDER +#DEFINES += -DMUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS +#DEFINES += -DMUSCLE_AVOID_IPV6 +#DEFINES += -DMUSCLE_AVOID_BITSTUFFING +#DEFINES += -DMUSCLE_AVOID_MULTICAST_API +#DEFINES += -DMUSCLE_POOL_SLAB_SIZE=0 +#DEFINES += -DMUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS +#DEFINES += -DMUSCLE_USE_POLL +#DEFINES += -DMUSCLE_USE_KQUEUE +#DEFINES += -DMUSCLE_USE_EPOLL +#DEFINES += -DMUSCLE_AVOID_MINIMIZED_HASHTABLES +#DEFINES += -DMUSCLE_ENABLE_SSL + +CFLAGS = -I.. -I../zlib/zlib -g +GCCFLAGS = -fno-exceptions -DMUSCLE_NO_EXCEPTIONS -W -Wall -Wno-multichar +CXXFLAGS = $(CFLAGS) -O3 $(DEFINES) + +# Uncomment this to enable C++11 support +#CXXFLAGS += -std=c++11 -stdlib=libc++ -DMUSCLE_USE_CPLUSPLUS11 -DMUSCLE_COUNT_STRING_COPY_OPERATIONS + +# Uncomment these to enable clang++'s static analyzer (for more thorough error checking) +#CXXFLAGS += --analyze -Xanalyzer -analyzer-output=text +#CFLAGS += --analyze -Xanalyzer -analyzer-output=text + +LFLAGS = +LIBS = -lpthread +EXECUTABLES = testhashtable microchatclient testmini testfilepathinfo testmicro microreflectclient minireflectclient minichatclient testmessage testzip testrefcount testqueue testtuple testgateway calctypecode printtypecode portablereflectclient portscan testudp testsocketmultiplexer testpackettunnel testpacketio teststring testbytebuffer testmatchfiles testparsefile testtime deadlockfinder deadlock testendian testsysteminfo portableplaintextclient uploadstress bandwidthtester readmessage testregex testnagle testresponse testqueryfilter testtypedefs hexterm udpproxy serialproxy printsourcelocations findsourcelocations svncopy testserial chatclient testpulsenode testnetconfigdetect testnetutil testpool testbatchguard testthread testthreadpool +REGEXOBJS = +ZLIBOBJS = adler32.o deflate.o trees.o zutil.o inflate.o inftrees.o inffast.o crc32.o compress.o gzclose.o gzread.o gzwrite.o gzlib.o +ZIPOBJS = zip.o unzip.o ioapi.o +STDOBJS = $(ZLIBOBJS) FilePathInfo.o Directory.o StringMatcher.o + +VPATH = ../hashtable ../message ../iogateway ../reflector ../regex ../util ../syslog ../system ../dataio ../zlib ../zlib/zlib ../zlib/zlib/contrib/minizip ../minimessage ../micromessage + +# if the OS type variable is unset, try to set it using the uname shell command +ifeq ($(OSTYPE),) + OSTYPE = $(strip $(shell uname)) +endif + +# IRIX may report itself as IRIX or as IRIX64. They are both the same to us. +ifeq ($(OSTYPE),IRIX64) + OSTYPE = IRIX +endif + +ifeq ($(OSTYPE),beos) + VPATH += ../besupport + EXECUTABLES += testbesupport + ifeq ($(BE_HOST_CPU),ppc) + CXX = mwcc + OBJFILES += regcomp.o regerror.o regexec.o regfree.o + VPATH += ../regex/regex + CFLAGS += -I../regex/regex -I../zlib/zlib + MEMORY_TRACKING_SUPPORTED = no # mwcc can't handle it correctly :^( + else # not ppc + CXXFLAGS += $(GCCFLAGS) $(CCOPTFLAGS) + LIBS = -lbe -lnet -lroot + ifeq ($(shell ls 2>/dev/null -1 /boot/develop/headers/be/bone/bone_api.h), /boot/develop/headers/be/bone/bone_api.h) + CFLAGS += -I/boot/develop/headers/be/bone -DBONE + LIBS = -nodefaultlibs -lbind -lsocket -lbe -lroot -L/boot/beos/system/lib + endif + endif +else # not beos + ifneq (,$(findstring g++,$(CXX))) + CXXFLAGS += $(GCCFLAGS) $(CCOPTFLAGS) + else + CXXFLAGS += $(CCOPTFLAGS) + endif +endif + +ifeq ($(OSTYPE),Haiku) + VPATH += ../besupport + CXXFLAGS += -DMUSCLE_AVOID_IPV6 + EXECUTABLES += testbesupport + LIBS = -lbe -lnetwork -lroot +else + EXECUTABLES += testchildprocess +endif + +ifeq ($(SYSTEM),AtheOS) + VPATH += ../atheossupport + EXECUTABLES += testatheossupport + LIBS = -latheos +endif + +ifeq ($(OSTYPE),freebsd4.0) + CXXFLAGS += -I/usr/include/machine +endif + +ifeq ($(OSTYPE),DragonFly) + # void +endif + +ifeq ($(OSTYPE),Darwin) + LIBS += -framework IOKit -framework Carbon -framework SystemConfiguration +endif + +ifeq ($(OSTYPE),linux) + LIBS += -lutil +endif + +ifeq ($(OSTYPE),Linux) + LIBS += -lutil +endif + +ifeq ($(OSTYPE),IRIX) + CXXFLAGS += -DSGI -DMIPS + ifneq (,$(findstring g++,$(CXX))) # if we are using SGI's CC compiler, we gotta change a few things + CXX = CC + CCFLAGS = -g2 -n32 -LANG:std -woff 1110,1174,1552,1460,3303 + LFLAGS = -g2 -n32 + CXXFLAGS += -DNEW_H_NOT_AVAILABLE + MEMORY_TRACKING_SUPPORTED = no # SGI CC can't handle it correctly :^( + endif +endif + +ifeq ($(MEMORY_TRACKING_SUPPORTED),yes) + CXXFLAGS += -DMUSCLE_ENABLE_MEMORY_TRACKING +endif + +SSLOBJS = +ifneq (,$(findstring MUSCLE_ENABLE_SSL,$(CXXFLAGS))) # Add OpenSSL-specific files only if OpenSSL is enabled + SSLOBJS += SSLSocketDataIO.o SSLSocketAdapterGateway.o + LIBS += -lssl -lcrypto +endif + +HEXTERMOBJS = hexterm.o NetworkUtilityFunctions.o SocketMultiplexer.o SysLog.o SetupSystem.o String.o RS232DataIO.o ChildProcessDataIO.o ByteBuffer.o MiscUtilityFunctions.o Message.o StdinDataIO.o PlainTextMessageIOGateway.o AbstractMessageIOGateway.o FileDescriptorDataIO.o PulseNode.o + +ifeq ($(MUSCLE_BUILD_CONTEXT),meyer) + CXXFLAGS += -DBUILD_MUSCLE_IN_MEYER_CONTEXT -I../../.. + CXXFLAGS += -DLCS_RELEASE_IS_FULLY_QUALIFIED=$(LCS_RELEASE_IS_FULLY_QUALIFIED) + CXXFLAGS += -DSVN_VERSION_STRING="$(SVN_REVISION)" + VPATH += ../../../version/ + HEXTERMOBJS += dmitri_version.o +endif + +# Makes all the programs that can be made using just cross-platform code +all : $(EXECUTABLES) + +testatheossupport : $(STDOBJS) ConvertMessages.o Message.o String.o testatheossupport.o SysLog.o SetupSystem.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testbesupport : $(STDOBJS) ConvertMessages.o Message.o String.o testbesupport.o SysLog.o SetupSystem.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +calctypecode : $(STDOBJS) calctypecode.o SysLog.o SetupSystem.o String.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +printtypecode : $(STDOBJS) printtypecode.o SysLog.o SetupSystem.o String.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testhashtable : $(STDOBJS) String.o testhashtable.o SysLog.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o Message.o SetupSystem.o MiscUtilityFunctions.o ByteBuffer.o Thread.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testmessage : $(STDOBJS) Message.o String.o testmessage.o SysLog.o ByteBuffer.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testfilepathinfo : $(STDOBJS) testfilepathinfo.o FilePathInfo.o SetupSystem.o String.o SysLog.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testthread : $(STDOBJS) testthread.o SetupSystem.o Message.o String.o ByteBuffer.o SysLog.o Thread.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testthreadpool : $(STDOBJS) testthreadpool.o SetupSystem.o Message.o String.o ByteBuffer.o SysLog.o Thread.o ThreadPool.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testpool : $(STDOBJS) Message.o String.o testpool.o SysLog.o ByteBuffer.o SetupSystem.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testbatchguard : $(STDOBJS) Message.o String.o testbatchguard.o SysLog.o ByteBuffer.o SetupSystem.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testzip : $(STDOBJS) Message.o String.o testzip.o SysLog.o ByteBuffer.o SetupSystem.o ZipFileUtilityFunctions.o MiscUtilityFunctions.o SocketMultiplexer.o NetworkUtilityFunctions.o $(ZIPOBJS) + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testmicro : $(STDOBJS) Message.o String.o testmicro.o SysLog.o ByteBuffer.o SetupSystem.o MicroMessage.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +microchatclient : microchatclient.o MicroMessage.o MicroMessageGateway.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +microreflectclient : microreflectclient.o MicroMessage.o MicroMessageGateway.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testmini : $(STDOBJS) Message.o String.o testmini.o SysLog.o ByteBuffer.o SetupSystem.o MiniMessage.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +minireflectclient : minireflectclient.o MiniMessage.o MiniMessageGateway.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +minichatclient : minichatclient.o MiniMessage.o MiniMessageGateway.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +readmessage : $(STDOBJS) Message.o String.o readmessage.o SysLog.o ByteBuffer.o SetupSystem.o ZLibUtilityFunctions.o ZLibCodec.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testresponse : $(STDOBJS) testresponse.o MessageIOGateway.o AbstractMessageIOGateway.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o SetupSystem.o PulseNode.o Message.o String.o ByteBuffer.o ZLibCodec.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testnagle : $(STDOBJS) testnagle.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o SetupSystem.o String.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testrefcount : $(STDOBJS) testrefcount.o SysLog.o String.o SetupSystem.o MiscUtilityFunctions.o Thread.o Message.o ByteBuffer.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +teststring : $(STDOBJS) teststring.o SysLog.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o MiscUtilityFunctions.o Message.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testbytebuffer : $(STDOBJS) testbytebuffer.o SysLog.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o MiscUtilityFunctions.o Message.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testmatchfiles : $(STDOBJS) testmatchfiles.o SysLog.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o MiscUtilityFunctions.o Message.o ByteBuffer.o StringMatcher.o FilePathExpander.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testparsefile : $(STDOBJS) testparsefile.o SysLog.o String.o MiscUtilityFunctions.o SetupSystem.o Message.o ByteBuffer.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testtime : $(STDOBJS) testtime.o SysLog.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o MiscUtilityFunctions.o ByteBuffer.o Message.o MiscUtilityFunctions.o + + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testendian : $(STDOBJS) testendian.o MiscUtilityFunctions.o ByteBuffer.o String.o SysLog.o SocketMultiplexer.o NetworkUtilityFunctions.o SetupSystem.o Message.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testsysteminfo : $(STDOBJS) testsysteminfo.o String.o SetupSystem.o SystemInfo.o SysLog.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testqueue : $(STDOBJS) testqueue.o SysLog.o String.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testtuple : $(STDOBJS) testtuple.o String.o SysLog.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testserial : $(STDOBJS) testserial.o SysLog.o RS232DataIO.o String.o SetupSystem.o SocketMultiplexer.o NetworkUtilityFunctions.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testgateway : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o testgateway.o SysLog.o PulseNode.o SetupSystem.o ZLibDataIO.o ZLibCodec.o ByteBuffer.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testregex : $(STDOBJS) testregex.o String.o SysLog.o SetupSystem.o $(REGEXOBJS) + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +portablereflectclient : $(STDOBJS) $(SSLOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o portablereflectclient.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o StdinDataIO.o FileDescriptorDataIO.o MiscUtilityFunctions.o PlainTextMessageIOGateway.o QueryFilter.o $(REGEXOBJS) + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +portscan : $(STDOBJS) portscan.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o SetupSystem.o String.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +chatclient : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o chatclient.o MiscUtilityFunctions.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o PathMatcher.o QueryFilter.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testudp : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o RawDataMessageIOGateway.o String.o testudp.o SocketMultiplexer.o MiscUtilityFunctions.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testsocketmultiplexer : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o testsocketmultiplexer.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testpackettunnel : $(STDOBJS) testpackettunnel.o Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o PacketTunnelIOGateway.o PacketizedDataIO.o MiscUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testpacketio : $(STDOBJS) testpacketio.o Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o SetupSystem.o PacketizedDataIO.o MiscUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +bandwidthtester : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o bandwidthtester.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +uploadstress : $(STDOBJS) Message.o AbstractMessageIOGateway.o MessageIOGateway.o String.o uploadstress.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +portableplaintextclient : $(STDOBJS) Message.o AbstractMessageIOGateway.o PlainTextMessageIOGateway.o String.o portableplaintextclient.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testchildprocess : $(STDOBJS) Message.o AbstractMessageIOGateway.o PlainTextMessageIOGateway.o String.o MiscUtilityFunctions.o ChildProcessDataIO.o testchildprocess.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o PulseNode.o SetupSystem.o ByteBuffer.o ZLibCodec.o StdinDataIO.o FileDescriptorDataIO.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +hexterm : $(STDOBJS) $(HEXTERMOBJS) + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +serialproxy : $(STDOBJS) serialproxy.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o SetupSystem.o String.o RS232DataIO.o ByteBuffer.o MiscUtilityFunctions.o Message.o AbstractMessageIOGateway.o PulseNode.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +udpproxy : $(STDOBJS) udpproxy.o SocketMultiplexer.o NetworkUtilityFunctions.o SysLog.o SetupSystem.o String.o ByteBuffer.o MiscUtilityFunctions.o Message.o AbstractMessageIOGateway.o PulseNode.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +findsourcelocations : $(STDOBJS) findsourcelocations.o SysLog.o SetupSystem.o String.o AbstractMessageIOGateway.o PlainTextMessageIOGateway.o PulseNode.o Message.o ByteBuffer.o SocketMultiplexer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +printsourcelocations : $(STDOBJS) printsourcelocations.o SysLog.o SetupSystem.o String.o AbstractMessageIOGateway.o PlainTextMessageIOGateway.o PulseNode.o Message.o ByteBuffer.o SocketMultiplexer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testsharedmem: $(STDOBJS) SysLog.o SharedMemory.o SocketMultiplexer.o NetworkUtilityFunctions.o testsharedmem.o String.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testqueryfilter: $(STDOBJS) SysLog.o ByteBuffer.o Message.o QueryFilter.o String.o testqueryfilter.o SetupSystem.o MiscUtilityFunctions.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testpulsenode: $(STDOBJS) $(SSLOBJS) SysLog.o ByteBuffer.o Message.o QueryFilter.o String.o SetupSystem.o MiscUtilityFunctions.o AbstractReflectSession.o PulseNode.o ReflectServer.o SocketMultiplexer.o AbstractMessageIOGateway.o ServerComponent.o SocketMultiplexer.o NetworkUtilityFunctions.o MessageIOGateway.o ZLibCodec.o testpulsenode.o ByteBuffer.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testnetconfigdetect: $(STDOBJS) $(SSLOBJS) SysLog.o ByteBuffer.o Message.o QueryFilter.o String.o SetupSystem.o MiscUtilityFunctions.o SocketMultiplexer.o NetworkUtilityFunctions.o AbstractReflectSession.o PulseNode.o ReflectServer.o SocketMultiplexer.o AbstractMessageIOGateway.o DetectNetworkConfigChangesSession.o ServerComponent.o MessageIOGateway.o ZLibCodec.o Thread.o testnetconfigdetect.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +testnetutil: $(STDOBJS) $(SSLOBJS) SysLog.o ByteBuffer.o Message.o QueryFilter.o String.o SetupSystem.o MiscUtilityFunctions.o SocketMultiplexer.o NetworkUtilityFunctions.o AbstractReflectSession.o PulseNode.o ReflectServer.o SocketMultiplexer.o AbstractMessageIOGateway.o DetectNetworkConfigChangesSession.o ServerComponent.o MessageIOGateway.o ZLibCodec.o Thread.o testnetutil.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +deadlockfinder : $(STDOBJS) deadlockfinder.o SysLog.o String.o SetupSystem.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +deadlock: $(STDOBJS) deadlock.o SysLog.o String.o SetupSystem.o Thread.o SocketMultiplexer.o NetworkUtilityFunctions.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +svncopy : $(STDOBJS) svncopy.o String.o SetupSystem.o SysLog.o + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) + +clean : + rm -f *.o *.xSYM $(EXECUTABLES) diff --git a/test/Makefile-mt b/test/Makefile-mt new file mode 100644 index 00000000..0f877973 --- /dev/null +++ b/test/Makefile-mt @@ -0,0 +1,48 @@ +CFLAGS = -I.. -g +CXXFLAGS = $(CFLAGS) -DMUSCLE_ENABLE_ZLIB_ENCODING -DMUSCLE_AVOID_NEWNOTHROW -DMUSCLE_SINGLE_THREAD_ONLY +LFLAGS = +LIBS = +EXECUTABLES = testreflectclient +REGEXOBJS = +ZLIBOBJS = adler32.o deflate.o trees.o zutil.o inflate.o crc32.o inftrees.o compress.o inffast.o + +VPATH = ../hashtable ../message ../iogateway ../reflector ../regex ../util ../syslog ../system ../dataio ../zlib ../zlib/zlib + +ifeq ($(OSTYPE),beos) + VPATH += ../besupport + EXECUTABLES += testreflectclient + ifeq ($(BE_HOST_CPU),ppc) + CXX = mwcc + REGEXOBJS = regcomp.o regerror.o regexec.o regfree.o + VPATH += ../regex/regex + CFLAGS += -I../regex/regex + else + LIBS = -lbe -lnet -lroot + ifeq ($(shell ls 2>/dev/null -1 /boot/develop/headers/be/bone/bone_api.h), /boot/develop/headers/be/bone/bone_api.h) + CFLAGS += -I/boot/develop/headers/be/bone -DBONE + LIBS = -nodefaultlibs -lbind -lsocket -lbe -lroot -L/boot/beos/system/lib + endif + endif +else + ifeq ($(OSTYPE),freebsd4.0) + CXXFLAGS += -I/usr/include/machine + else + ifeq ($(OSTYPE),darwin) + CXX = c++ + CXXFLAGS += -I/usr/include/machine + else + ifeq ($(SYSTEM),AtheOS) + VPATH += ../atheossupport + EXECUTABLES += testreflectclient + LIBS = -latheos + endif + endif + endif +endif + + +# Makes all the programs that can be made using just cross-platform code +all : $(EXECUTABLES) + +testreflectclient : MemoryAllocator.o ConvertMessages.o Message.o AbstractMessageIOGateway.o MessageIOGateway.o PlainTextMessageIOGateway.o String.o testreflectclient.o MessageTransceiverThread.o NetworkUtilityFunctions.o SysLog.o PulseNode.o Thread.o SetupSystem.o PathMatcher.o StringMatcher.o AbstractReflectSession.o DumbReflectSession.o StorageReflectSession.o ReflectServer.o ServerComponent.o ByteBuffer.o ZLibCodec.o QueryFilter.o $(REGEXOBJS) $(ZLIBOBJS) + $(CXX) $(LFLAGS) -o $@ $^ $(LIBS) diff --git a/test/Makefile.win32client.borland b/test/Makefile.win32client.borland new file mode 100644 index 00000000..fde70e66 --- /dev/null +++ b/test/Makefile.win32client.borland @@ -0,0 +1,199 @@ +############################################################################# +# Makefile for building: win32client +# (originally generated by qmake, then hacked by jaf to compile win32client) +# (so if you don't like it, too bad) +############################################################################# + +####### Compiler, tools and options + +CC = bcc32 +CXX = bcc32 +CFLAGS = -O2 -x- -w -w-hid -v -D__MT__ #-DMUSCLE_ENABLE_ZLIB_ENCODING +CXXFLAGS= -O2 -x- -w -w-hid -v -D__MT__ #-DMUSCLE_ENABLE_ZLIB_ENCODING +INCPATH = -I"." -I".." -I"../regex/regex" +LINK = ilink32 +LFLAGS = -ap -Gn -Gi -x -Tpe +LIBS = import32.lib cw32mti.lib +STARTUP = c0x32.obj + + +####### Files + +SOURCES = ..\..\muscle\test\win32client.cpp \ + ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp \ + ..\..\muscle\iogateway\MessageIOGateway.cpp \ + ..\..\muscle\message\Message.cpp \ + ..\..\muscle\reflector\AbstractReflectSession.cpp \ + ..\..\muscle\reflector\DumbReflectSession.cpp \ + ..\..\muscle\reflector\StorageReflectSession.cpp \ + ..\..\muscle\reflector\FilterSessionFactory.cpp \ + ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp \ + ..\..\muscle\reflector\ReflectServer.cpp \ + ..\..\muscle\reflector\ServerComponent.cpp \ + ..\..\muscle\regex\StringMatcher.cpp \ + ..\..\muscle\regex\PathMatcher.cpp \ + ..\..\muscle\regex\QueryFilter.cpp \ + ..\..\muscle\regex\regex\regcomp.c \ + ..\..\muscle\regex\regex\regerror.c \ + ..\..\muscle\regex\regex\regexec.c \ + ..\..\muscle\regex\regex\regfree.c \ + ..\..\muscle\syslog\SysLog.cpp \ + ..\..\muscle\system\GlobalMemoryAllocator.cpp \ + ..\..\muscle\system\MessageTransceiverThread.cpp \ + ..\..\muscle\system\Thread.cpp \ + ..\..\muscle\system\SetupSystem.cpp \ + ..\..\muscle\util\ByteBuffer.cpp \ + ..\..\muscle\util\MemoryAllocator.cpp \ + ..\..\muscle\util\MiscUtilityFunctions.cpp \ + ..\..\muscle\util\NetworkUtilityFunctions.cpp \ + ..\..\muscle\util\String.cpp \ + ..\..\muscle\util\PulseNode.cpp + +OBJECTS = win32client.obj \ + AbstractMessageIOGateway.obj \ + MessageIOGateway.obj \ + Message.obj \ + AbstractReflectSession.obj \ + DumbReflectSession.obj \ + StorageReflectSession.obj \ + FilterSessionFactory.obj \ + RateLimitSessionIOPolicy.obj \ + ReflectServer.obj \ + ServerComponent.obj \ + StringMatcher.obj \ + PathMatcher.obj \ + QueryFilter.obj \ + regcomp.obj \ + regerror.obj \ + regexec.obj \ + regfree.obj \ + SysLog.obj \ + GlobalMemoryAllocator.obj \ + MessageTransceiverThread.obj \ + Thread.obj \ + SetupSystem.obj \ + ByteBuffer.obj \ + MemoryAllocator.obj \ + MiscUtilityFunctions.obj \ + NetworkUtilityFunctions.obj \ + String.obj \ + PulseNode.obj + +TARGET = win32client.exe + +####### Implicit rules + +.SUFFIXES: .cpp .cxx .cc .c + +.cpp.obj: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o$@ $< + +####### Build rules + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(LINK) $(LFLAGS) $(OBJECTS) $(LIBS) $(STARTUP) + +#$(TARGET): $(OBJECTS) +# $(LINK) @&&| +# $(LFLAGS) $(OBJECTS) $(LIBS) +#| + +# $(LFLAGS) $(OBJECTS) $(TARGET),,$(LIBS) + +clean: + -del *.exe + -del *.lib + -del *.obj + -del *.tds + -del $(TARGET) + +####### Compile + +AbstractMessageIOGateway.obj: ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oAbstractMessageIOGateway.obj ..\..\muscle\iogateway\AbstractMessageIOGateway.cpp + +MessageIOGateway.obj: ..\..\muscle\iogateway\MessageIOGateway.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMessageIOGateway.obj ..\..\muscle\iogateway\MessageIOGateway.cpp + +Message.obj: ..\..\muscle\message\Message.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMessage.obj ..\..\muscle\message\Message.cpp + +AbstractReflectSession.obj: ..\..\muscle\reflector\AbstractReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oAbstractReflectSession.obj ..\..\muscle\reflector\AbstractReflectSession.cpp + +DumbReflectSession.obj: ..\..\muscle\reflector\DumbReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oDumbReflectSession.obj ..\..\muscle\reflector\DumbReflectSession.cpp + +StorageReflectSession.obj: ..\..\muscle\reflector\StorageReflectSession.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oStorageReflectSession.obj ..\..\muscle\reflector\StorageReflectSession.cpp + +FilterSessionFactory.obj: ..\..\muscle\reflector\FilterSessionFactory.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oFilterSessionFactory.obj ..\..\muscle\reflector\FilterSessionFactory.cpp + +RateLimitSessionIOPolicy.obj: ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oRateLimitSessionIOPolicy.obj ..\..\muscle\reflector\RateLimitSessionIOPolicy.cpp + +ReflectServer.obj: ..\..\muscle\reflector\ReflectServer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oReflectServer.obj ..\..\muscle\reflector\ReflectServer.cpp + +ServerComponent.obj: ..\..\muscle\reflector\ServerComponent.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oServerComponent.obj ..\..\muscle\reflector\ServerComponent.cpp + +StringMatcher.obj: ..\..\muscle\regex\StringMatcher.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oStringMatcher.obj ..\..\muscle\regex\StringMatcher.cpp + +PathMatcher.obj: ..\..\muscle\regex\PathMatcher.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oPathMatcher.obj ..\..\muscle\regex\PathMatcher.cpp + +QueryFilter.obj: ..\..\muscle\regex\QueryFilter.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oQueryFilter.obj ..\..\muscle\regex\QueryFilter.cpp + +regcomp.obj: ..\..\muscle\regex\regex\regcomp.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregcomp.obj ..\..\muscle\regex\regex\regcomp.c + +regerror.obj: ..\..\muscle\regex\regex\regerror.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregerror.obj ..\..\muscle\regex\regex\regerror.c + +regexec.obj: ..\..\muscle\regex\regex\regexec.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregexec.obj ..\..\muscle\regex\regex\regexec.c + +regfree.obj: ..\..\muscle\regex\regex\regfree.c + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oregfree.obj ..\..\muscle\regex\regex\regfree.c + +win32client.obj: ..\..\muscle\test\win32client.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -owin32client.obj ..\..\muscle\test\win32client.cpp + +SysLog.obj: ..\..\muscle\syslog\SysLog.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oSysLog.obj ..\..\muscle\syslog\SysLog.cpp + +GlobalMemoryAllocator.obj: ..\..\muscle\system\GlobalMemoryAllocator.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oGlobalMemoryAllocator.obj ..\..\muscle\system\GlobalMemoryAllocator.cpp + +MessageTransceiverThread.obj: ..\..\muscle\system\MessageTransceiverThread.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMessageTransceiverThread.obj ..\..\muscle\system\MessageTransceiverThread.cpp + +Thread.obj: ..\..\muscle\system\Thread.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oThread.obj ..\..\muscle\system\Thread.cpp + +SetupSystem.obj: ..\..\muscle\system\SetupSystem.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oSetupSystem.obj ..\..\muscle\system\SetupSystem.cpp + +ByteBuffer.obj: ..\..\muscle\util\ByteBuffer.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oByteBuffer.obj ..\..\muscle\util\ByteBuffer.cpp + +MemoryAllocator.obj: ..\..\muscle\util\MemoryAllocator.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMemoryAllocator.obj ..\..\muscle\util\MemoryAllocator.cpp + +MiscUtilityFunctions.obj: ..\..\muscle\util\MiscUtilityFunctions.cpp ..\..\muscle\util\string.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oMiscUtilityFunctions.obj ..\..\muscle\util\MiscUtilityFunctions.cpp + +NetworkUtilityFunctions.obj: ..\..\muscle\util\NetworkUtilityFunctions.cpp ..\..\muscle\util\string.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oNetworkUtilityFunctions.obj ..\..\muscle\util\NetworkUtilityFunctions.cpp + +String.obj: ..\..\muscle\util\String.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oString.obj ..\..\muscle\util\String.cpp + +PulseNode.obj: ..\..\muscle\util\PulseNode.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -oPulseNode.obj ..\..\muscle\util\PulseNode.cpp diff --git a/test/bandwidthtester.cpp b/test/bandwidthtester.cpp new file mode 100644 index 00000000..ead7c013 --- /dev/null +++ b/test/bandwidthtester.cpp @@ -0,0 +1,97 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include +#include +#include + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +/** Sends a stream of messages to the server, or receives them. Prints out the average send/receive speed */ +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + IPAddressAndPort iap((argc>1)?argv[1]:"localhost", 2960, true); + + ConstSocketRef s = Connect(iap, argv[1], "bandwidthtester", false); + if (s() == NULL) return 10; + + MessageIOGateway gw; + gw.SetDataIO(DataIORef(new TCPSocketDataIO(s, false))); + + bool send = (argc > 2)&&(strcmp(argv[2], "send") == 0); + if (send) printf("Sending bandwidthtester messages...\n"); + else + { + printf("Listening for bandwidthtester messages....\n"); + // Tell the server that we are interested in receiving bandwidthtester messages + MessageRef uploadMsg(GetMessageFromPool(PR_COMMAND_SETDATA)); + uploadMsg()->AddMessage("bandwidthtester", GetMessageFromPool()); + gw.AddOutgoingMessage(uploadMsg); + } + + // Here is a (fairly large) message that we will send repeatedly in order to bandwidthtester the server + MessageRef sendMsgRef(GetMessageFromPool(0x666)); + sendMsgRef()->AddString(PR_NAME_KEYS, "bandwidthtester"); + sendMsgRef()->AddData("bandwidthtester test data", B_RAW_TYPE, NULL, 8000); + + SocketMultiplexer multiplexer; + uint64 startTime = GetRunTime64(); + struct timeval lastPrintTime = {0, 0}; + uint32 tallyBytesSent = 0, tallyBytesReceived = 0; + QueueGatewayMessageReceiver inQueue; + while(true) + { + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + if ((send)||(gw.HasBytesToOutput())) multiplexer.RegisterSocketForWriteReady(fd); + + const struct timeval printInterval = {5, 0}; + if (OnceEvery(printInterval, lastPrintTime)) + { + uint64 now = GetRunTime64(); + if (tallyBytesSent > 0) + { + if (send) LogTime(MUSCLE_LOG_INFO, "Sending at " UINT32_FORMAT_SPEC" bytes/second\n", tallyBytesSent/((uint32)(((now-startTime))/MICROS_PER_SECOND))); + else LogTime(MUSCLE_LOG_INFO, "Sent " UINT32_FORMAT_SPEC" bytes\n", tallyBytesSent); + tallyBytesSent = 0; + } + if (tallyBytesReceived > 0) + { + if (send) LogTime(MUSCLE_LOG_INFO, "Received " UINT32_FORMAT_SPEC" bytes\n", tallyBytesReceived); + else LogTime(MUSCLE_LOG_INFO, "Receiving at " UINT32_FORMAT_SPEC" bytes/second\n", tallyBytesReceived/((uint32)((now-startTime)/MICROS_PER_SECOND))); + tallyBytesReceived = 0; + } + startTime = now; + } + + if (multiplexer.WaitForEvents() < 0) LogTime(MUSCLE_LOG_CRITICALERROR, "bandwidthtester: WaitForEvents() failed!\n"); + if ((send)&&(gw.HasBytesToOutput() == false)) for (int i=0; i<10; i++) gw.AddOutgoingMessage(sendMsgRef); + bool reading = multiplexer.IsSocketReadyForRead(fd); + bool writing = multiplexer.IsSocketReadyForWrite(fd); + int32 wroteBytes = (writing) ? gw.DoOutput() : 0; + int32 readBytes = (reading) ? gw.DoInput(inQueue) : 0; + if ((readBytes < 0)||(wroteBytes < 0)) + { + LogTime(MUSCLE_LOG_ERROR, "Connection closed, exiting.\n"); + break; + } + tallyBytesSent += wroteBytes; + tallyBytesReceived += readBytes; + + MessageRef incoming; + while(inQueue.RemoveHead(incoming) == B_NO_ERROR) {/* ignore it, we just want to measure bandwidth */} + } + LogTime(MUSCLE_LOG_INFO, "\n\nBye!\n"); + return 0; +} diff --git a/test/calctypecode.cpp b/test/calctypecode.cpp new file mode 100644 index 00000000..850f6251 --- /dev/null +++ b/test/calctypecode.cpp @@ -0,0 +1,25 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "support/MuscleSupport.h" + +// Just a silly program to calculate the decimal expression for +// a 'TYPE' typecode constant (because Linux's gcc doesn't like +// the 'TYPE' format and gives lots of warning messages) +int main(int argc, char ** argv) +{ + if ((argc == 2)&&(strlen(argv[1])==4)) + { + unsigned char * code = (unsigned char *) argv[1]; + uint32 v = 0; + uint32 m = 1; + for (int i=3; i>=0; i--) + { + v += (m * ((long)(code[i]))); + m *= 256; + } + printf(UINT32_FORMAT_SPEC "; // '%s' \n", v, code); + } + else printf("Usage Example: calctypecode MSGG\n"); + return 0; +} diff --git a/test/chatclient.cpp b/test/chatclient.cpp new file mode 100644 index 00000000..43cfc71d --- /dev/null +++ b/test/chatclient.cpp @@ -0,0 +1,340 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "regex/PathMatcher.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/Hashtable.h" +#include "util/SocketMultiplexer.h" +#include "util/String.h" +#include "util/StringTokenizer.h" +#include "util/MiscUtilityFunctions.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define VERSION_STRING "1.05" + +// stolen from ShareNetClient.h +enum +{ + NET_CLIENT_CONNECTED_TO_SERVER = 0, + NET_CLIENT_DISCONNECTED_FROM_SERVER, + NET_CLIENT_NEW_CHAT_TEXT, + NET_CLIENT_CONNECT_BACK_REQUEST, + NET_CLIENT_CHECK_FILE_COUNT, + NET_CLIENT_PING, + NET_CLIENT_PONG, + NET_CLIENT_SCAN_THREAD_REPORT +}; + +// ditto +enum +{ + ROOT_DEPTH = 0, // root node + HOST_NAME_DEPTH, + SESSION_ID_DEPTH, + BESHARE_HOME_DEPTH, // used to separate our stuff from other (non-BeShare) data on the same server + USER_NAME_DEPTH, // user's handle node would be found here + FILE_INFO_DEPTH // user's shared file list is here +}; + +static MessageRef GenerateChatMessage(const char * targetSessionID, const char * messageText) +{ + MessageRef chatMessage = GetMessageFromPool(NET_CLIENT_NEW_CHAT_TEXT); + if (chatMessage()) + { + String toString("/*/"); // send message to all hosts... + toString += targetSessionID; + toString += "/beshare"; + chatMessage()->AddString(PR_NAME_KEYS, toString.Cstr()); + chatMessage()->AddString("session", "blah"); // will be set by server + chatMessage()->AddString("text", messageText); + if (strcmp(targetSessionID, "*")) chatMessage()->AddBool("private", true); + } + return chatMessage; +} + +static MessageRef GenerateServerSubscription(const char * subscriptionString, bool quietly) +{ + MessageRef queryMsg = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + if (queryMsg()) + { + queryMsg()->AddBool(subscriptionString, true); // the true doesn't signify anything + if (quietly) queryMsg()->AddBool(PR_NAME_SUBSCRIBE_QUIETLY, true); // suppress initial-state response + } + return queryMsg; +} + +static MessageRef GenerateSetLocalUserName(const char * name) +{ + MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + MessageRef nameMessage = GetMessageFromPool(); + if ((uploadMsg())&&(nameMessage())) + { + nameMessage()->AddString("name", name); + nameMessage()->AddInt32("port", 0); // BeShare requires this, even though we don't use it + nameMessage()->AddString("version_name", "MUSCLE demo chat client"); + nameMessage()->AddString("version_num", VERSION_STRING); + uploadMsg()->AddMessage("beshare/name", nameMessage); + } + return uploadMsg; +} + +static MessageRef GenerateSetLocalUserStatus(const char * name) +{ + MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + MessageRef nameMessage = GetMessageFromPool(); + if ((uploadMsg())&&(nameMessage())) + { + nameMessage()->AddString("userstatus", name); + uploadMsg()->AddMessage("beshare/userstatus", nameMessage); + } + return uploadMsg; +} + +static String GetUserName(Hashtable & users, const String & sessionID) +{ + String ret; + String * userName = users.Get(sessionID); + return sessionID + "/" + (userName ? (*userName) : String("")); +} + +// Simple little text-based BeShare-compatible chat client. Should work with any muscled server. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message args; + (void) ParseArgs(argc, argv, args); + + const char * hostName = "beshare.tycomsystems.com"; (void) args.FindString("server", &hostName); + const char * userName = "clyde"; (void) args.FindString("nick", &userName); + const char * userStatus = "here"; (void) args.FindString("status", &userStatus); + const char * tempStr; + uint16 port = 0; if (args.FindString("port", &tempStr) == B_NO_ERROR) port = (uint16) atoi(tempStr); + if (port == 0) port = 2960; + + // Connect to the server + ConstSocketRef s = Connect(hostName, port, "clyde", false); + if (s() == NULL) return 10; + + // Do initial setup + MessageIOGateway gw; gw.SetDataIO(DataIORef(new TCPSocketDataIO(s, false))); + gw.AddOutgoingMessage(GenerateSetLocalUserName(userName)); + gw.AddOutgoingMessage(GenerateSetLocalUserStatus(userStatus)); + gw.AddOutgoingMessage(GenerateServerSubscription("SUBSCRIBE:beshare/*", false)); + + // Our event loop + char buf[2048] = ""; + Hashtable _users; + QueueGatewayMessageReceiver inQueue; + SocketMultiplexer multiplexer; + while(s()) + { + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + + String text; + multiplexer.RegisterSocketForReadReady(STDIN_FILENO); + + while(s()) + { + if (multiplexer.WaitForEvents() < 0) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "WaitForEvents() failed!\n"); + s.Reset(); + break; + } + if (multiplexer.IsSocketReadyForRead(STDIN_FILENO)) + { + if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + char * ret = strchr(buf, '\n'); if (ret) *ret = '\0'; + text = buf; + } + + text = text.Trim(); + StringTokenizer tok(text()); + const char * targetSessionID = "*"; + if (text.StartsWith("/msg ")) + { + (void) tok(); + targetSessionID = tok(); + String sendText = String(tok.GetRemainderOfString()).Trim(); + if (sendText.HasChars()) gw.AddOutgoingMessage(GenerateChatMessage(targetSessionID, sendText())); + } + else if (text.StartsWith("/nick ")) + { + (void) tok(); + String name = String(tok.GetRemainderOfString()).Trim(); + if (name.HasChars()) + { + LogTime(MUSCLE_LOG_INFO, "Setting local user name to [%s]\n", name()); + gw.AddOutgoingMessage(GenerateSetLocalUserName(name())); + } + } + else if (text.StartsWith("/status ")) + { + (void) tok(); + String status = String(tok.GetRemainderOfString()).Trim(); + if (status.HasChars()) + { + LogTime(MUSCLE_LOG_INFO, "Setting local user status to [%s]\n", status()); + gw.AddOutgoingMessage(GenerateSetLocalUserStatus(status())); + } + } + else if (text.StartsWith("/help")) + { + LogTime(MUSCLE_LOG_INFO, "Available commands are: /nick, /msg, /status, /help, and /quit\n"); + } + else if (text.StartsWith("/quit")) s.Reset(); + else if (strlen(text()) > 0) gw.AddOutgoingMessage(GenerateChatMessage("*", text())); + + text.Clear(); + + bool reading = multiplexer.IsSocketReadyForRead(fd); + bool writing = multiplexer.IsSocketReadyForWrite(fd); + bool writeError = ((writing)&&(gw.DoOutput() < 0)); + bool readError = ((reading)&&(gw.DoInput(inQueue) < 0)); + if ((readError)||(writeError)) + { + LogTime(MUSCLE_LOG_ERROR, "Connection closed, exiting.\n"); + s.Reset(); + } + + MessageRef msg; + while(inQueue.RemoveHead(msg) == B_NO_ERROR) + { + switch(msg()->what) + { + case NET_CLIENT_PING: // respond to other clients' pings + { + String replyTo; + if (msg()->FindString("session", replyTo) == B_NO_ERROR) + { + msg()->what = NET_CLIENT_PONG; + + String toString("/*/"); + toString += replyTo; + toString += "/beshare"; + + msg()->RemoveName(PR_NAME_KEYS); + msg()->AddString(PR_NAME_KEYS, toString); + + msg()->RemoveName("session"); + msg()->AddString("session", "blah"); // server will set this correctly for us + + msg()->RemoveName("version"); + msg()->AddString("version", "MUSCLE demo chat client v" VERSION_STRING); + + gw.AddOutgoingMessage(msg); + } + } + break; + + case NET_CLIENT_NEW_CHAT_TEXT: + { + // Someone has sent a line of chat text to display + const char * text; + const char * session; + if ((msg()->FindString("text", &text) == B_NO_ERROR)&&(msg()->FindString("session", &session) == B_NO_ERROR)) + { + if (strncmp(text, "/me ", 4) == 0) LogTime(MUSCLE_LOG_INFO, ": %s %s\n", GetUserName(_users, session)(), &text[4]); + else LogTime(MUSCLE_LOG_INFO, "%s(%s): %s\n", msg()->HasName("private") ? ": ":"", GetUserName(_users, session)(), text); + } + } + break; + + case PR_RESULT_DATAITEMS: + { + // Look for sub-messages that indicate that nodes were removed from the tree + String nodepath; + for (int i=0; (msg()->FindString(PR_NAME_REMOVED_DATAITEMS, i, nodepath) == B_NO_ERROR); i++) + { + int pathDepth = GetPathDepth(nodepath.Cstr()); + if (pathDepth >= USER_NAME_DEPTH) + { + String sessionID = GetPathClause(SESSION_ID_DEPTH, nodepath.Cstr()); + sessionID = sessionID.Substring(0, sessionID.IndexOf('/')); + + switch(GetPathDepth(nodepath.Cstr())) + { + case USER_NAME_DEPTH: + if (strncmp(GetPathClause(USER_NAME_DEPTH, nodepath.Cstr()), "name", 4) == 0) + { + String userName = GetUserName(_users, sessionID); + if (_users.Remove(sessionID) == B_NO_ERROR) LogTime(MUSCLE_LOG_INFO, "User [%s] has disconnected.\n", userName()); + } + break; + } + } + } + + // Look for sub-messages that indicate that nodes were added to the tree + for (MessageFieldNameIterator iter = msg()->GetFieldNameIterator(B_MESSAGE_TYPE); iter.HasData(); iter++) + { + const String & np = iter.GetFieldName(); + int pathDepth = GetPathDepth(np()); + if (pathDepth == USER_NAME_DEPTH) + { + MessageRef tempRef; + if (msg()->FindMessage(np, tempRef) == B_NO_ERROR) + { + const Message * pmsg = tempRef(); + String sessionID = GetPathClause(SESSION_ID_DEPTH, np()); + sessionID = sessionID.Substring(0, sessionID.IndexOf('/')); + switch(pathDepth) + { + case USER_NAME_DEPTH: + { + String hostName = GetPathClause(HOST_NAME_DEPTH, np()); + hostName = hostName.Substring(0, hostName.IndexOf('/')); + + const char * nodeName = GetPathClause(USER_NAME_DEPTH, np()); + if (strncmp(nodeName, "name", 4) == 0) + { + const char * name; + if (pmsg->FindString("name", &name) == B_NO_ERROR) + { + if (_users.ContainsKey(sessionID) == false) LogTime(MUSCLE_LOG_INFO, "User #%s has connected\n", sessionID()); + _users.Put(sessionID, name); + LogTime(MUSCLE_LOG_INFO, "User #%s is now known as %s\n", sessionID(), name); + } + } + else if (strncmp(nodeName, "userstatus", 9) == 0) + { + const char * status; + if (pmsg->FindString("userstatus", &status) == B_NO_ERROR) LogTime(MUSCLE_LOG_INFO, "%s is now [%s]\n", GetUserName(_users, sessionID)(), status); + } + } + break; + } + } + } + } + } + } + } + + if ((reading == false)&&(writing == false)) break; + + multiplexer.RegisterSocketForReadReady(STDIN_FILENO); + multiplexer.RegisterSocketForReadReady(fd); + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + } + } + + if (gw.HasBytesToOutput()) + { + LogTime(MUSCLE_LOG_INFO, "Waiting for all pending messages to be sent...\n"); + while((gw.HasBytesToOutput())&&(gw.DoOutput() >= 0)) {Log(MUSCLE_LOG_INFO, "."); fflush(stdout);} + } + LogTime(MUSCLE_LOG_INFO, "Bye!\n"); + + return 0; +} diff --git a/test/deadlock.cpp b/test/deadlock.cpp new file mode 100644 index 00000000..acde5843 --- /dev/null +++ b/test/deadlock.cpp @@ -0,0 +1,51 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "system/SetupSystem.h" +#include "system/Thread.h" + +using namespace muscle; + +static Mutex _mutexA; +static Mutex _mutexB; + +class TestThread : public Thread +{ +public: + TestThread() {/* empty */} + + virtual void InternalThreadEntry() + { + const uint32 numIterations = 25; // enough iterations that we deadlock sometimes, but not always + for (uint32 i=0; iLock() != B_NO_ERROR) printf("Error, couldn't lock first Mutex! (this should never happen!)\n"); + if (m2->Lock() != B_NO_ERROR) printf("Error, couldn't lock second Mutex! (this should never happen!)\n"); + if (m2->Unlock() != B_NO_ERROR) printf("Error, couldn't unlock second Mutex! (this should never happen!)\n"); + if (m1->Unlock() != B_NO_ERROR) printf("Error, couldn't unlock first Mutex! (this should never happen!)\n"); + } + } +}; + +// This program is designed to sometimes deadlock! Compile it with -DMUSCLE_ENABLE_DEADLOCK_FINDER +// and feed its stdout output into the deadlockfinder program to see if deadlockfinder can detect +// the potential deadlock! +int main(int /*argc*/, char ** /*argv*/) +{ + CompleteSetupSystem css; + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + _enableDeadlockFinderPrints = true; +#endif + + printf("Deadlocking program begins!\n"); + TestThread threads[10]; + for (uint32 i=0; i + +#include "util/Hashtable.h" +#include "util/Queue.h" +#include "util/String.h" +#include "util/StringTokenizer.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" + +// This program is useful for finding potential synchronization deadlocks in your multi-threaded +// application. To use it add -DMUSCLE_ENABLE_DEADLOCK_FINDER to your Makefile and then fully +// recompile your program. +// +// Then run your program and have it output stdout to a file, like this; +// +// ./mymultithreadedprogram > outfile +// +// Once you have exercised your program in the normal manner, run deadlockfinder like this: +// +// ./deadlockfinder > & state) +{ + printf("--------- Begin Current state ------------\n"); + for (HashtableIterator > iter(state); iter.HasData(); iter++) + { + printf(" Thread %i:\n", iter.GetKey()); + for (uint32 i=0; i & q1, const Queue & q2) +{ + if (q1.GetNumItems() != q2.GetNumItems()) return false; + + for (uint32 i=0; i & seqB, const Queue & cantBeBeforeK, const Queue & cantBeAfterK) +{ + for (uint32 i=0; i & seq, const char * desc, const Hashtable & threads) +{ + printf("\n%s SEQUENCE " UINT32_FORMAT_SPEC" (" UINT32_FORMAT_SPEC" threads ", desc, i, threads.GetNumItems()); + bool isFirst = true; + for (HashtableIterator iter(threads); iter.HasData(); iter++) + { + if (isFirst == false) printf(", "); + isFirst = false; + printf("%lu", iter.GetKey()); + } + printf("):\n"); + for (uint32 j=0; j > maxLogs; + + Hashtable > sequenceToThreads; + Hashtable > curLockState; + char buf[1024]; + while(fgets(buf, sizeof(buf), stdin)) + { + String s = buf; s = s.Trim(); + StringTokenizer tok(s()); + String actionStr = tok(); + if ((actionStr == "mx_lock:")||(actionStr == "mx_unlk:")) + { + String threadStr = tok(); + String mutexStr = tok(); + String locStr = tok(); + if ((threadStr.StartsWith("tid="))&&(mutexStr.StartsWith("m="))&&(locStr.StartsWith("loc="))) + { + threadStr = threadStr.Substring(4); + mutexStr = mutexStr.Substring(2); + locStr = locStr.Substring(4); + + //printf("thread=[%s] actionStr=[%s] mutexStr=[%s] locStr=[%s]\n", threadStr(), actionStr(), mutexStr(), locStr()); + String lockDesc = mutexStr + " & " + locStr; + + unsigned long threadID = Atoull(threadStr()); + if (actionStr == "mx_lock:") + { + Queue * q = curLockState.GetOrPut(threadID, Queue()); + q->AddTail(lockDesc); + if (q->GetNumItems() > 1) + { + // See if we have this queue logged anywhere already + int32 logIdx = -1; + for (uint32 i=0; i * seqToThread = sequenceToThreads.GetOrPut(logIdx); + seqToThread->PutWithDefault(threadID); + } + } + else + { + Queue * q = curLockState.Get(threadID); + if (q) + { + // Find the last instance of this mutex in our list + String lockName = lockDesc; {int32 lastAmp = lockName.LastIndexOf('&'); lockName = lockName.Substring(0, lastAmp);} + bool foundLock = false; + for (int32 i=q->GetNumItems()-1; i>=0; i--) + { + String s = (*q)[i]; {int32 lastAmp = s.LastIndexOf('&'); s = s.Substring(0, lastAmp);} + if (s == lockName) + { + foundLock = true; + q->RemoveItemAt(i); + break; + } + } + if (foundLock == false) printf("ERROR: thread %lu is unlocking a lock he never locked!!! [%s]\n", threadID, lockDesc()); + if (q->IsEmpty()) curLockState.Remove(threadID); + } + else printf("ERROR: thread %lu is unlocking when he has nothing locked!!! [%s]\n", threadID, lockDesc()); + } + } + else printf("ERROR PARSING OUTPUT LINE [%s]\n", s()); +#ifdef NOT_CURRENTLY_USED + PrintState(curLockState); +#endif + } + } + + for (HashtableIterator > iter(curLockState); iter.HasData(); iter++) + { + printf("\n\nERROR, AT END OF PROCESSING, LOCKS WERE STILL HELD BY THREAD %lu:\n", iter.GetKey()); + const Queue & q = iter.GetValue(); + for (uint32 i=0; i 1) PrintSequence(i, maxLogs[i], "UNIQUE", *sequenceToThreads.Get(i)); + printf("\n------------------- END UNIQUE LOCK SEQUENCES -----------------\n\n"); + + // Now see if there are any inconsistencies. First, go through the sequences and remove any redundant re-locks, as they don't count + Queue > simplifiedMaxLogs = maxLogs; + for (uint32 i=0; i useCounts; + Queue & seqA = simplifiedMaxLogs[i]; + for (uint32 j=0; j iter(useCounts); iter.HasData(); iter++) + { + uint32 numToRemove = iter.GetValue()-1; + for (int32 j=seqA.GetNumItems()-1; ((numToRemove>0)&&(j>=0)); j--) if (ExtractPointerString(seqA[j]) == iter.GetKey()) seqA.RemoveItemAt(j); + } + useCounts.Clear(); + } + + // Then do the inconsistencies check + bool foundProblems = false; + for (uint32 i=0; i & seqA = simplifiedMaxLogs[i]; + if (seqA.GetNumItems() > 1) + { + for (uint32 j=0; j & seqB = simplifiedMaxLogs[j]; + if ((i < j)&&(seqB.GetNumItems() > 1)) // the (i cantBeAfterK; for (uint32 m=0; m cantBeBeforeK; for (uint32 m=k+1; m * ti = sequenceToThreads.Get(i); + Hashtable * tj = sequenceToThreads.Get(j); + bool isDefinite = ((ti)&&(tj)&&((ti->GetNumItems() > 1)||(tj->GetNumItems() > 1)||(*ti != *tj))); // (ti) and (tj) are actually never going to be NULL, but this makes clang++ happy + + printf("\n\n------------------------------------------\n"); + printf("ERROR, %s LOCK-ACQUISITION ORDERING INCONSISTENCY DETECTED: SEQUENCE #" UINT32_FORMAT_SPEC" vs SEQUENCE #" UINT32_FORMAT_SPEC" !!\n", isDefinite?"DEFINITE":"POTENTIAL", i, j); + PrintSequence(i, maxLogs[i], "PROBLEM", *sequenceToThreads.Get(i)); + PrintSequence(j, maxLogs[j], "PROBLEM", *sequenceToThreads.Get(j)); + break; + } + } + } + } + } + } + if (foundProblems == false) printf("No Mutex-acquisition ordering problems detected, yay!\n"); + + return 0; +} diff --git a/test/findsourcelocations.cpp b/test/findsourcelocations.cpp new file mode 100644 index 00000000..ddf02249 --- /dev/null +++ b/test/findsourcelocations.cpp @@ -0,0 +1,96 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "dataio/FileDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "util/Directory.h" +#include "util/FilePathInfo.h" +#include "syslog/SysLog.h" +#include "system/SetupSystem.h" +#include "system/SystemInfo.h" + +using namespace muscle; + +static void CheckFile(const String & path, uint32 code) +{ + FileDataIO dio(fopen(path(), "r")); + if (dio.GetFile()) + { + String fileName = path.Substring(GetFilePathSeparator()); + + PlainTextMessageIOGateway gw; + gw.SetDataIO(DataIORef(&dio, false)); + + // Read in the file + QueueGatewayMessageReceiver q; + while(gw.DoInput(q) > 0) {/* empty */} + + // Now parse the lines, and see if any match + uint32 lineNumber = 1; + MessageRef msg; + while(q.RemoveHead(msg) == B_NO_ERROR) + { + const String * line; + for (uint32 i=0; msg()->FindString(PR_NAME_TEXT_LINE, i, &line) == B_NO_ERROR; i++) + { + if (GenerateSourceCodeLocationKey(fileName(), lineNumber) == code) printf("%s:" UINT32_FORMAT_SPEC": %s\n", path(), lineNumber, line->Cstr()); + lineNumber++; + } + } + } +} + +static void DoSearch(const String & path, uint32 code) +{ + Directory d(path()); + if (d.IsValid()) + { + const char * nextName; + while((nextName = d.GetCurrentFileName()) != NULL) + { + if (nextName[0] != '.') + { + String subPath = path + GetFilePathSeparator() + nextName; + FilePathInfo fpi(subPath()); + if (fpi.IsDirectory()) DoSearch(subPath, code); + else if (fpi.IsRegularFile()) + { + String iName = nextName; iName = iName.ToLowerCase(); + if ((iName.EndsWith(".c"))||(iName.EndsWith(".cpp"))||(iName.EndsWith(".h"))||(iName.EndsWith(".hpp"))||(iName.EndsWith(".cc"))) CheckFile(subPath, code); + } + } + d++; + } + } +} + +// This program accepts a source-code-location key (e.g. "FB72", as shown in MUSCLE Log messages +// when -DMUSCLE_INCLUDE_SOURCE_LOCATION_IN_LOGTIME is defined) and iterates over all .c, .cpp, and .h +// files in or under the specified directory and prints out any lines that match the code. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + if (argc < 3) + { + printf("Usage: findsourcelocations code dirname\n"); + return 10; + } + + uint32 code = SourceCodeLocationKeyFromString(argv[1]); + if (code == 0) + { + printf("Invalid source location code [%s]\n", argv[1]); + return 10; + } + + Directory d(argv[2]); + if (d.IsValid() == false) + { + printf("[%s] is not a valid directory path.\n", argv[2]); + return 10; + } + + DoSearch(argv[2], code); + return 0; +} diff --git a/test/hexterm.cpp b/test/hexterm.cpp new file mode 100644 index 00000000..e25af666 --- /dev/null +++ b/test/hexterm.cpp @@ -0,0 +1,436 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "dataio/ChildProcessDataIO.h" +#include "dataio/StdinDataIO.h" +#include "dataio/TCPSocketDataIO.h" +#include "dataio/RS232DataIO.h" +#include "dataio/UDPSocketDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "system/SetupSystem.h" +#include "system/SystemInfo.h" // for GetFilePathSeparator() +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" +#include "util/MiscUtilityFunctions.h" + +#ifdef BUILD_MUSCLE_IN_MEYER_CONTEXT +# include "version/dmitri_version.h" +#endif + +using namespace muscle; + +static bool _useHex = true; +static bool _printChecksums = false; +static bool _decorateOutput = true; +static bool _printReceivedBytes = true; +static bool _quietSend = false; +static uint32 _spamsPerSecond = 0; +static uint32 _spamSize = 1024; + +static uint32 Calculate32BitChecksum(const uint8 * bytes, uint32 numBytes) +{ + // djb2 hash, as described at http://www.cse.yorku.ca/~oz/hash.html + uint32 hash = 5381; + for (uint32 i=0; i>= 7;} + return HexBytesToString(bytes, 5); +} + +static void LogChecksum(const uint8 * buf, uint32 numBytes) +{ + uint32 chk = Calculate32BitChecksum(buf, numBytes); + LogTime(MUSCLE_LOG_INFO, "Computed checksum is " UINT32_FORMAT_SPEC " [%s]\n", chk, ChecksumHexString(chk)()); +} + +static void LogBytes(const uint8 * buf, uint32 numBytes, const char * optDesc) +{ + if (_useHex) + { + if (!_quietSend) LogHexBytes(MUSCLE_LOG_INFO, buf, numBytes, optDesc); + if (_printChecksums) LogChecksum(buf, numBytes); + } + else + { + if (_decorateOutput) + { + LogTime(MUSCLE_LOG_INFO, "/-----------Begin " UINT32_FORMAT_SPEC " bytes of %s%sAscii Data-----------\\\n", numBytes, optDesc?optDesc:"", optDesc?" ":""); + + bool atFront = true; + for (uint32 i=0; i 0) spamBuf = GetByteBufferFromPool(_spamSize); + + const UDPSocketDataIO * optUDPIO = dynamic_cast(&io); + String scratchString; + + SocketMultiplexer multiplexer; + + uint64 spamTime = ((_spamsPerSecond > 0)&&(_spamsPerSecond != MUSCLE_NO_LIMIT)) ? GetRunTime64() : MUSCLE_TIME_NEVER; + while(true) + { + int readFD = io.GetReadSelectSocket().GetFileDescriptor(); + int writeFD = io.GetWriteSelectSocket().GetFileDescriptor(); + int stdinFD = stdinIO.GetReadSelectSocket().GetFileDescriptor(); + + multiplexer.RegisterSocketForReadReady(readFD); + if (_spamsPerSecond == MUSCLE_NO_LIMIT) multiplexer.RegisterSocketForWriteReady(writeFD); + multiplexer.RegisterSocketForReadReady(stdinFD); + if (multiplexer.WaitForEvents(spamTime) >= 0) + { + if (((_spamsPerSecond == MUSCLE_NO_LIMIT)&&(multiplexer.IsSocketReadyForWrite(writeFD)))||(GetRunTime64() >= spamTime)) + { + int32 spamBytesSent = 0; + uint8 * b = spamBuf() ? spamBuf()->GetBuffer() : NULL; + if (b) + { + uint8 v = (uint8)(spamTime%256); + for (uint32 i=0; i<_spamSize; i++) b[i] = v++; // just some nice arbitrary data + spamBytesSent = io.WriteFully(b, _spamSize); + } + if ((!_quietSend)&&(_decorateOutput)) LogTime(MUSCLE_LOG_ERROR, "Sent " INT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " bytes of spam!\n", spamBytesSent, _spamSize); + spamTime += (1000000/_spamsPerSecond); + } + + if (multiplexer.IsSocketReadyForRead(readFD)) + { + uint8 buf[2048]; + int32 ret = io.Read(buf, sizeof(buf)); + if (ret > 0) + { + if (_printReceivedBytes) + { + const char * desc = "Received"; + if (optUDPIO) + { + scratchString = String("Received from %1").Arg(optUDPIO->GetSourceOfLastReadPacket().ToString()()); + desc = scratchString(); + } + LogBytes(buf, ret, desc); + } + else LogTime(MUSCLE_LOG_DEBUG, "Received " INT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC " bytes of data.\n", ret, sizeof(buf)); + } + else if (ret < 0) + { + LogTime(MUSCLE_LOG_ERROR, "Read() returned " INT32_FORMAT_SPEC ", aborting!\n", ret); + break; + } + } + if ((stdinFD >= 0)&&(multiplexer.IsSocketReadyForRead(stdinFD))) + { + while(1) + { + int32 bytesRead = stdinGateway.DoInput(receiver); + if (bytesRead < 0) + { + stdinFD = -1; // indicate that stdin is no longer available + break; + } + if (bytesRead == 0) break; // nothing more to read, for now + } + + // Gather stdin bytes together into a single large buffer, so we can send them in as few groups as possible + // (Main benefit is that this makes for prettier pretty-printed output on the receiving, if the receiver is another hexterm) + ByteBufferRef outBuf = GetByteBufferFromPool(); + MessageRef nextMsg; + while(receiver.RemoveHead(nextMsg) == B_NO_ERROR) + { + const char * b; + for (int32 i=0; (nextMsg()->FindString(PR_NAME_TEXT_LINE, i, &b) == B_NO_ERROR); i++) + { + ByteBufferRef nextBuf; + if (_useHex) nextBuf = ParseHexBytes(b); + else + { + nextBuf = GetByteBufferFromPool(strlen(b)+1, (const uint8 *) b); + if (nextBuf()) nextBuf()->AppendByte('\n'); // add a newline byte + } + + uint32 count = nextBuf() ? nextBuf()->GetNumBytes() : 0; + if ((count > 0)&&(outBuf()->AppendBytes(nextBuf()->GetBuffer(), nextBuf()->GetNumBytes()) != B_NO_ERROR)) + { + WARN_OUT_OF_MEMORY; + break; + } + } + } + + if (outBuf()) + { + uint32 wrote = io.WriteFully(outBuf()->GetBuffer(), outBuf()->GetNumBytes()); + if (wrote == outBuf()->GetNumBytes()) + { + if (_decorateOutput) LogBytes(outBuf()->GetBuffer(), outBuf()->GetNumBytes(), "Sent"); + } + else + { + LogTime(MUSCLE_LOG_ERROR, "Error, Write() only wrote " INT32_FORMAT_SPEC " of " UINT32_FORMAT_SPEC " bytes... aborting!\n", wrote, outBuf()->GetNumBytes()); + break; + } + } + if (stdinFD < 0) break; // all done now! + } + } + else break; + } +} + +static void DoUDPSession(const String & optHost, uint16 port) +{ + ConstSocketRef ss = CreateUDPSocket(); + if (ss() == NULL) + { + LogTime(MUSCLE_LOG_ERROR, "Error creating UDP socket!\n"); + return; + } + + UDPSocketDataIO udpIO(ss, false); + if (optHost.HasChars()) + { + ip_address ip = GetHostByName(optHost(), false); + if (ip != invalidIP) + { +#ifndef MUSCLE_AVOID_MULTICAST_API + // If it's a multicast address, we need to add ourselves to the multicast group + // in order to get packets from the group. + if (IsMulticastIPAddress(ip)) + { + if (BindUDPSocket(ss, port, NULL, invalidIP, true) == B_NO_ERROR) + { + if (AddSocketToMulticastGroup(ss, ip) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Added UDP socket to multicast group %s!\n", Inet_NtoA(ip)()); +#ifdef DISALLOW_MULTICAST_TO_SELF + if (SetSocketMulticastToSelf(ss, false) != B_NO_ERROR) LogTime(MUSCLE_LOG_ERROR, "Error disabling multicast-to-self on socket\n"); +#endif + } + else LogTime(MUSCLE_LOG_ERROR, "Error adding UDP socket to multicast group %s!\n", Inet_NtoA(ip)()); + } + else LogTime(MUSCLE_LOG_ERROR, "Error binding multicast socket to port %u\n", port); + } +#endif + +#ifdef MUSCLE_AVOID_IPV6 + if ((ip & 0xFF) == 0xFF) + { + if (SetUDPSocketBroadcastEnabled(ss, true) == B_NO_ERROR) LogTime(MUSCLE_LOG_INFO, "Broadcast UDP address detected: UDP broadcast enabled on socket.\n"); + else LogTime(MUSCLE_LOG_ERROR, "Could not enable UDP broadcast on socket!\n"); + } +#endif + IPAddressAndPort iap(ip, port); + udpIO.SetSendDestination(iap); + LogTime(MUSCLE_LOG_INFO, "Ready to send UDP packets to %s\n", iap.ToString()()); + DoSession(udpIO); + } + else LogTime(MUSCLE_LOG_ERROR, "Could not look up target hostname [%s]\n", optHost()); + } + else + { + if (BindUDPSocket(ss, port) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Listening for incoming UDP packets on port %i\n", port); + DoSession(udpIO); + } + else LogTime(MUSCLE_LOG_ERROR, "Could not bind UDP socket to port %i\n", port); + } +} + +static void LogUsage(const char * argv0) +{ + String progName = String(argv0).Substring(GetFilePathSeparator()); + +#ifdef BUILD_MUSCLE_IN_MEYER_CONTEXT + char buf[256]; + Log(MUSCLE_LOG_INFO, "%s (%s)\n\n", progName(), GetLocalDmitriReleaseVersionTitle(progName(), false, buf)); +#else + Log(MUSCLE_LOG_INFO, "%s (compiled from MUSCLE v%s)\n\n", progName(), MUSCLE_VERSION_STRING); +#endif + Log(MUSCLE_LOG_INFO, "Usage: hexterm tcp= (listen for incoming TCP connections on the given port)\n"); + Log(MUSCLE_LOG_INFO, " or: hexterm tcp=: (make an outgoing TCP connection to the given host/port)\n"); + Log(MUSCLE_LOG_INFO, " or: hexterm udp=: (send outgoing UDP packets to the given host/port)\n"); + Log(MUSCLE_LOG_INFO, " or: hexterm udp= (listen for incoming UDP packets on the given port)\n"); + Log(MUSCLE_LOG_INFO, " or: hexterm serial=: (send/receive via a serial device, e.g. /dev/ttyS0)\n"); + Log(MUSCLE_LOG_INFO, " or: hexterm child= (send/receive via a child process, e.g. 'ls -l')\n"); + Log(MUSCLE_LOG_INFO, " Additional optional args include:\n"); + Log(MUSCLE_LOG_INFO, " ascii (print and parse bytes as ASCII rather than hexadecimal)\n"); + Log(MUSCLE_LOG_INFO, " plain (Suppress decorative elements in hexterm's output)\n"); + Log(MUSCLE_LOG_INFO, " quietreceive (Suppress the printing out of incoming data bytes)\n"); + Log(MUSCLE_LOG_INFO, " spamrate= (Specify number of automatic-spam-transmissions to send per second)\n"); + Log(MUSCLE_LOG_INFO, " spamsize= (Specify size of each automatic-spam-transmission; defaults to 1024)\n"); + Log(MUSCLE_LOG_INFO, " printchecksums (print checksums for incoming and sent data)\n"); + Log(MUSCLE_LOG_INFO, " help (print this help text)\n"); +} + +// Secondary entry point, used when embedding hexterm in a unified daemon +int hextermmain(const char * argv0, const Message & args) +{ + _printChecksums = args.HasName("printchecksums"); + if (_printChecksums) printf("Checksum printing enabled.\n"); + + if (args.HasName("help")) + { + LogUsage(argv0); + return 0; + } + if (args.HasName("ascii")) + { + LogTime(MUSCLE_LOG_INFO, "ASCII mode activated!\n"); + _useHex = false; + } + if (args.HasName("plain")) + { + LogTime(MUSCLE_LOG_INFO, "Decorative output characters will be suppressed.\n"); + _decorateOutput = false; + } + + _printReceivedBytes = (args.HasName("quietreceive") == false); + _quietSend = args.HasName("quietsend"); + + if (args.HasName("spamspersecond")) + { + const char * sizeStr = args.GetCstr("spamsize"); + if (sizeStr) _spamSize = atol(sizeStr); + + _spamsPerSecond = atoi(args.GetCstr("spamspersecond")); + LogTime(MUSCLE_LOG_INFO, "Will generate and send " UINT32_FORMAT_SPEC " " UINT32_FORMAT_SPEC "-byte spam-transmissions per second.\n", _spamsPerSecond, _spamSize); + } + + String host; + uint16 port; + + String arg; + if (args.FindString("child", arg) == B_NO_ERROR) + { + ChildProcessDataIO cpdio(false); + int32 spaceIdx = arg.IndexOf(' '); + String childProgName = arg.Substring(0, spaceIdx).Trim(); + String childArgs = arg.Substring(spaceIdx).Trim()(); + if (cpdio.LaunchChildProcess(arg()) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Communicating with child process (%s), childArgs=[%s]\n", childProgName(), childArgs()); + DoSession(cpdio); + LogTime(MUSCLE_LOG_INFO, "Child process session aborted, exiting.\n"); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Unable to open child process (%s) with childArgs (%s)\n", childProgName(), childArgs()); + } + else if (args.FindString("serial", arg) == B_NO_ERROR) + { + const char * colon = strchr(arg(), ':'); + uint32 baudRate = colon ? atoi(colon+1) : 0; if (baudRate == 0) baudRate = 38400; + String devName = arg.Substring(0, ":"); + Queue devs; + if (RS232DataIO::GetAvailableSerialPortNames(devs) == B_NO_ERROR) + { + String serName; + for (int32 i=devs.GetNumItems()-1; i>=0; i--) + { + if (devs[i] == devName) + { + serName = devs[i]; + break; + } + } + if (serName.HasChars()) + { + RS232DataIO io(devName(), baudRate, false); + if (io.IsPortAvailable()) + { + LogTime(MUSCLE_LOG_INFO, "Communicating with serial port %s (baud rate " UINT32_FORMAT_SPEC ")\n", serName(), baudRate); + DoSession(io); + LogTime(MUSCLE_LOG_INFO, "Serial session aborted, exiting.\n"); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Unable to open serial device %s (baud rate " UINT32_FORMAT_SPEC ").\n", serName(), baudRate); + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Serial device %s not found.\n", devName()); + LogTime(MUSCLE_LOG_CRITICALERROR, "Available serial devices are:\n"); + for (uint32 i=0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/microchatclient.c b/test/microchatclient.c new file mode 100644 index 00000000..fc4a19d7 --- /dev/null +++ b/test/microchatclient.c @@ -0,0 +1,643 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __HAIKU__ +# include +#endif + +#include "micromessage/MicroMessageGateway.h" +#include "reflector/StorageReflectConstants.h" + +#define VERSION_STRING "1.05" + +/* stolen from SystemInfo.cpp */ +static const char * GetOSName() +{ + const char * ret = "Unknown"; + (void) ret; // just to shut the Borland compiler up + +#ifdef WIN32 + ret = "Windows"; +#endif + +#ifdef __CYGWIN__ + ret = "Windows (CygWin)"; +#endif + +#ifdef __APPLE__ + ret = "MacOS/X"; +#endif + +#ifdef __linux__ + ret = "Linux"; +#endif + +#ifdef __BEOS__ + ret = "BeOS"; +#endif + +#ifdef __HAIKU__ + ret = "Haiku"; +#endif + +#ifdef __ATHEOS__ + ret = "AtheOS"; +#endif + +#if defined(__QNX__) || defined(__QNXTO__) + ret = "QNX"; +#endif + +#ifdef __FreeBSD__ + ret = "FreeBSD"; +#endif + +#ifdef __OpenBSD__ + ret = "OpenBSD"; +#endif + +#ifdef __NetBSD__ + ret = "NetBSD"; +#endif + +#ifdef __osf__ + ret = "Tru64"; +#endif + +#if defined(IRIX) || defined(__sgi) + ret = "Irix"; +#endif + +#ifdef __OS400__ + ret = "AS400"; +#endif + +#ifdef __OS2__ + ret = "OS/2"; +#endif + +#ifdef _AIX + ret = "AIX"; +#endif + +#ifdef _SEQUENT_ + ret = "Sequent"; +#endif + +#ifdef _SCO_DS + ret = "OpenServer"; +#endif + +#if defined(_HP_UX) || defined(__hpux) || defined(_HPUX_SOURCE) + ret = "HPUX"; +#endif + +#if defined(SOLARIS) || defined(__SVR4) + ret = "Solaris"; +#endif + +#if defined(__UNIXWARE__) || defined(__USLC__) + ret = "UnixWare"; +#endif + + return ret; +} + +/* stolen from ShareNetClient.h */ +enum +{ + NET_CLIENT_CONNECTED_TO_SERVER = 0, + NET_CLIENT_DISCONNECTED_FROM_SERVER, + NET_CLIENT_NEW_CHAT_TEXT, + NET_CLIENT_CONNECT_BACK_REQUEST, + NET_CLIENT_CHECK_FILE_COUNT, + NET_CLIENT_PING, + NET_CLIENT_PONG, + NET_CLIENT_SCAN_THREAD_REPORT +}; + +/* ditto */ +enum +{ + ROOT_DEPTH = 0, /* root node */ + HOST_NAME_DEPTH, + SESSION_ID_DEPTH, + BESHARE_HOME_DEPTH, /* used to separate our stuff from other (non-BeShare) data on the same server */ + USER_NAME_DEPTH, /* user's handle node would be found here */ + FILE_INFO_DEPTH /* user's shared file list is here */ +}; + +static void Inet_NtoA(uint32 addr, char * ipbuf) +{ + sprintf(ipbuf, INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"", (addr>>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF); +} + +static int ConnectToIP(uint32 hostIP, uint16 port) +{ + char ipbuf[16]; + struct sockaddr_in saAddr; + int s; + + Inet_NtoA(hostIP, ipbuf); + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = htonl(hostIP); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) + { + if (connect(s, (struct sockaddr *) &saAddr, sizeof(saAddr)) >= 0) return s; + close(s); + } + return -1; +} + +static uint32 GetHostByName(const char * name) +{ + uint32 ret = inet_addr(name); /* first see if we can parse it as a numeric address */ + if ((ret == 0)||(ret == (uint32)-1)) + { + struct hostent * he = gethostbyname(name); + ret = ntohl(he ? *((uint32*)he->h_addr) : 0); + } + else ret = ntohl(ret); + + return ret; +} + +static int Connect(const char * hostName, uint16 port) +{ + uint32 hostIP = GetHostByName(hostName); + return (hostIP > 0) ? ConnectToIP(hostIP, port) : -1; +} + +static int32 SocketSendFunc(const uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = send(*((int *)arg), (const char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +static int32 SocketRecvFunc(uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = recv(*((int *)arg), (char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +/* Creates an UMessage to send to the server when the user types in a text chat string */ +static void SendChatMessage(UMessageGateway * gw, const char * targetSessionID, const char * messageText) +{ + UMessage chatMessage = UGGetOutgoingMessage(gw, NET_CLIENT_NEW_CHAT_TEXT); + if (UMIsMessageValid(&chatMessage)) + { + /* Specify which client(s) the Message should be forwarded to by muscled */ + char buf[1024]; sprintf(buf, "/*/%s/beshare", targetSessionID); + UMAddString(&chatMessage, PR_NAME_KEYS, buf); + + /* Add a "session" field so that the target clients will know who the */ + /* Message came from. Note that muscled will automatically set this field */ + /* to its proper value, so we don't have to; we just have to make sure it exists. */ + UMAddString(&chatMessage, "session", "blah"); + + /* Include the chat text that the user typed in to stdin */ + UMAddString(&chatMessage, "text", messageText); + + /* And lastly, if the Message wasn't meant to be a public chat message, */ + /* include a "private" keyword to let the receiver know it's a private */ + /* communication, so they can print it in a different color or whatever. */ + if (strcmp(targetSessionID, "*")) UMAddBool(&chatMessage, "private", UTrue); + + UGOutgoingMessagePrepared(gw, &chatMessage); + } +} + +/* Creates an UMessage to send to the server to set up our subscriptions */ +/* so that we get the proper notifications when other clients connect/disconnect, etc */ +static void SendServerSubscription(UMessageGateway * gw, const char * subscriptionString, UBool quietly) +{ + UMessage uploadMsg = UGGetOutgoingMessage(gw, PR_COMMAND_SETPARAMETERS); + if (UMIsMessageValid(&uploadMsg)) + { + UMAddBool(&uploadMsg, subscriptionString, UFalse); + if (quietly) UMAddBool(&uploadMsg, PR_NAME_SUBSCRIBE_QUIETLY, UTrue); + UGOutgoingMessagePrepared(gw, &uploadMsg); + } +} + +/* Generates a UMessage that tells the server to post some interesting information */ +/* about our client, for the other clients to see. */ +static void UploadLocalUserName(UMessageGateway * gw, const char * name) +{ + UMessage uploadMsg = UGGetOutgoingMessage(gw, PR_COMMAND_SETDATA); + if (UMIsMessageValid(&uploadMsg)) + { + UMessage nameMsg = UMInlineAddMessage(&uploadMsg, "beshare/name", 0); + if (UMIsMessageValid(&nameMsg)) + { + UMAddString(&nameMsg, "name", name); + UMAddInt32(&nameMsg, "port", 0); /* BeShare requires this field, even though we don't use it since we don't share files */ + UMAddString(&nameMsg, "version_name", "MUSCLE C micro chat client"); + UMAddString(&nameMsg, "version_num", VERSION_STRING); + UMAddString(&nameMsg, "version_num", GetOSName()); + UGOutgoingMessagePrepared(gw, &uploadMsg); + } + else UGOutgoingMessageCancelled(gw, &uploadMsg); + } +} + +/* Generates a message to set this client's user-status on the server (e.g. "Here" or "Away") */ +static void UploadLocalUserStatus(UMessageGateway * gw, const char * status) +{ + UMessage uploadMsg = UGGetOutgoingMessage(gw, PR_COMMAND_SETDATA); + if (UMIsMessageValid(&uploadMsg)) + { + UMessage statusMsg = UMInlineAddMessage(&uploadMsg, "beshare/userstatus", 0); + if (UMIsMessageValid(&statusMsg)) + { + UMAddString(&statusMsg, "userstatus", status); + UGOutgoingMessagePrepared(gw, &uploadMsg); + } + else UGOutgoingMessageCancelled(gw, &uploadMsg); + } +} + +/* Returns a pointer into (path) after the (depth)'th '/' char */ +static const char * GetPathClause(int depth, const char * path) +{ + int i; + for (i=0; i_sessionID == sid) return cur->_name; + cur = cur->_nextUser; + } + return ""; +} + +static UBool RemoveUserName(struct User ** users, int sid) +{ + struct User * cur = *users; + struct User * prev = NULL; + while(cur) + { + if (cur->_sessionID == sid) + { + if (prev) prev->_nextUser = cur->_nextUser; + else *users = cur->_nextUser; + free((char *) cur->_name); + free(cur); + return UTrue; + } + prev = cur; + cur = cur->_nextUser; + } + return UFalse; +} + +static void PutUserName(struct User ** users, int sid, const char * name) +{ + struct User * newUser = malloc(sizeof(struct User)); + if (newUser) + { + (void) RemoveUserName(users, sid); /* paranoia (ensure no duplicate sid's) */ + + newUser->_sessionID = sid; + newUser->_nextUser = *users; + newUser->_name = strdup(name); + if (newUser->_name) *users = newUser; + else free(newUser); + } +} + +/* This is a text based BeShare-compatible chat client for the muscled server. */ +/* It is useful as an example of a chat client written in C, and perhaps for other things also. */ +/* This implementation of the client uses only the C UMessage interface, for micronal executable size. */ +int main(int argc, char ** argv) +{ + const char * hostName = "beshare.tycomsystems.com"; + const char * userName = "microclyde"; + const char * userStatus = "here"; + const char * tempStr; + int s; + int port = 0; + struct User * users = NULL; /* doubly-linked-list! */ + uint8 inputBuffer[16*1024]; + uint8 outputBuffer[16*1024]; + + UMessageGateway gw; + UGGatewayInitialize(&gw, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); + +#ifdef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + printf("Warning: This program doesn't run very well on this OS, because the OS can't select() on stdin. You'll need to press return a lot.\n"); +#endif + + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 2960; /* default muscled port */ + + s = Connect(hostName, (uint16)port); + if (s >= 0) + { + char text[1000] = ""; + UBool keepGoing = UTrue; + fd_set readSet, writeSet; + + printf("Connection to [%s:%i] succeeded.\n", hostName, port); + (void) fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK); /* Set s to non-blocking I/O mode */ + + (void) UploadLocalUserName(&gw, userName); + (void) UploadLocalUserStatus(&gw, userStatus); + + /* Tell the server we want to be updated about the beshare-specific attributes of other clients */ + SendServerSubscription(&gw, "SUBSCRIBE:beshare/*", UFalse); + + /* the main event loop */ + while(keepGoing) + { + int maxfd = s; + struct timeval * timeout = NULL; + char buf[2048] = ""; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (UGHasBytesToOutput(&gw)) FD_SET(s, &writeSet); + +#ifdef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + /* This OS can't select() on stdin, so just make the user press enter or whatever */ + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + {char * ret = strchr(buf, '\n'); if (ret) *ret = '\0';} + struct timeval poll = {0, 0}; + timeout = &poll; /* prohibit blocking in select() */ +#else + if (STDIN_FILENO > maxfd) maxfd = STDIN_FILENO; + FD_SET(STDIN_FILENO, &readSet); +#endif + + while(keepGoing) + { + if (select(maxfd+1, &readSet, &writeSet, NULL, timeout) < 0) printf("microreflectclient: select() failed!\n"); + +#ifndef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + if (FD_ISSET(STDIN_FILENO, &readSet)) + { + if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + {char * ret; ret = strchr(buf, '\n'); if (ret) *ret = '\0'; } + } +#endif + + /* Handle what the user typed in to stdin */ + if (buf[0]) + { + if (strncmp(buf, "/msg ", 5) == 0) + { + char * nextSpace = strchr(buf+5, ' '); /* after the target ID */ + if (nextSpace) + { + *nextSpace = '\0'; + SendChatMessage(&gw, buf+5, nextSpace+1); + } + else printf("Can't send private /msg, no message text was specified!\n"); + } + else if (strncmp(buf, "/nick ", 6) == 0) + { + printf("Setting local user name to [%s]\n", buf+6); + UploadLocalUserName(&gw, buf+6); + } + else if (strncmp(buf, "/status ", 8) == 0) + { + printf("Setting local user status to [%s]\n", buf+8); + UploadLocalUserStatus(&gw, buf+8); + } + else if (strncmp(buf, "/help", 5) == 0) printf("Available commands are: /nick, /msg, /status, /help, and /quit\n"); + else if (strncmp(buf, "/quit", 5) == 0) keepGoing = UFalse; + else SendChatMessage(&gw, "*", buf); + + buf[0] = '\0'; + } + + { + UMessage msg = {0}; + UBool reading = FD_ISSET(s, &readSet); + UBool writing = FD_ISSET(s, &writeSet); + UBool writeError = ((writing)&&(UGDoOutput(&gw, ~0, SocketSendFunc, &s) < 0)); + UBool readError = ((reading)&&(UGDoInput( &gw, ~0, SocketRecvFunc, &s, &msg) < 0)); /* sets (msg) if one is available */ + if (UMIsMessageValid(&msg)) + { +/* printf("Heard message from server:-----------------------------------\n"); */ +/* UMPrintToStream(&msg, stdout); */ +/* printf("-------------------------------------------------------------\n"); */ + switch(UMGetWhatCode(&msg)) + { + case NET_CLIENT_PING: /* respond to other clients' pings */ + { + const char * replyTo = UMGetString(&msg, "session", 0); + if (replyTo) + { + UMessage pongMsg = UGGetOutgoingMessage(&gw, NET_CLIENT_PONG); + if (UMIsMessageValid(&pongMsg)) + { + UMAddString(&pongMsg, "session", replyTo); + + char * keyBuf = malloc(strlen(replyTo)+3+8+1); + if (keyBuf) + { + keyBuf[0] = '\0'; + strcat(keyBuf, "/*/"); + strcat(keyBuf, replyTo); + strcat(keyBuf, "/beshare"); + UMAddString(&pongMsg, PR_NAME_KEYS, keyBuf); + free(keyBuf); + } + UMAddString(&pongMsg, "version", "MUSCLE C micro chat client v" VERSION_STRING); + UGOutgoingMessagePrepared(&gw, &pongMsg); + } + } + } + break; + + case NET_CLIENT_NEW_CHAT_TEXT: + { + /* Someone has sent a line of chat text to display */ + const char * text = UMGetString(&msg, "text", 0); + const char * session = UMGetString(&msg, "session", 0); + if ((text)&&(session)) + { + int sid = atoi(session); + if (strncmp(text, "/me ", 4) == 0) printf(": %s %s\n", GetUserName(users, sid), &text[4]); + else printf("%s(%s): %s\n", UMGetBool(&msg, "private", 0) ? ": ":"", GetUserName(users, sid), text); + } + } + break; + + case PR_RESULT_DATAITEMS: + { + /* Look for sub-messages that indicate that nodes were removed from the tree */ + uint32 removeCount = 0; + uint32 i; + const char * nodepath; + for (i=0; ((nodepath = UMGetString(&msg, PR_NAME_REMOVED_DATAITEMS, i)) != NULL); i++) + { + int pathDepth = GetPathDepth(nodepath); + if (pathDepth >= USER_NAME_DEPTH) + { + const char * sessionID = GetPathClause(SESSION_ID_DEPTH, nodepath); + if (sessionID) + { + int sid = atol(sessionID); + switch(GetPathDepth(nodepath)) + { + case USER_NAME_DEPTH: + if (strncmp(GetPathClause(USER_NAME_DEPTH, nodepath), "name", 4) == 0) + { + const char * userName = GetUserName(users, sid); + if (userName) + { + printf("User [%s] has disconnected.\n", userName); + RemoveUserName(&users, sid); + } + } + break; + } + } + } + } + + /* Look for sub-messages that indicate that nodes were added to the tree */ + { + UMessageFieldNameIterator iter; UMIteratorInitialize(&iter, &msg, B_MESSAGE_TYPE); + const char * fieldName; + while((fieldName = UMIteratorGetCurrentFieldName(&iter, NULL, NULL)) != NULL) + { + int pathDepth = GetPathDepth(fieldName); + if (pathDepth == USER_NAME_DEPTH) + { + uint32 msgCount = 0; + uint32 i = 0; + while(1) + { + UMessage subMsg = UMGetMessage(&msg, fieldName, i); + if (UMIsMessageValid(&subMsg) == false) break; + + const char * sessionIDStr = GetPathClause(SESSION_ID_DEPTH, fieldName); + if (sessionIDStr) + { + int sid = atol(sessionIDStr); + switch(pathDepth) + { + case USER_NAME_DEPTH: + { + const char * nodeName = GetPathClause(USER_NAME_DEPTH, fieldName); + if (strncmp(nodeName, "name", 4) == 0) + { + const char * userName = UMGetString(&subMsg, "name", 0); + if (userName) + { + if (GetUserName(users, sid) == NULL) printf("User #%i has connected\n", sid); + PutUserName(&users, sid, userName); + printf("User #%i is now known as %s\n", sid, userName); + } + } + else if (strncmp(nodeName, "userstatus", 9) == 0) + { + const char * userStatus = UMGetString(&subMsg, "userstatus", 0); + if (userStatus) printf("%s is now [%s]\n", GetUserName(users, sid), userStatus); + } + } + break; + } + } + i++; + } + } + UMIteratorAdvance(&iter); + } + } + } + } + } + + if ((readError)||(writeError)) + { + printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error"); + close(s); + keepGoing = UFalse; + } + + if ((reading == UFalse)&&(writing == UFalse)) break; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (UGHasBytesToOutput(&gw)) FD_SET(s, &writeSet); + FD_SET(STDIN_FILENO, &readSet); + } + } + } + close(s); + } + else printf("Connection to [%s:%i] failed!\n", hostName, port); + + while(users) + { + struct User * next = users->_nextUser; + free(users); + users = next; + } + + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/microreflectclient.c b/test/microreflectclient.c new file mode 100644 index 00000000..8236403c --- /dev/null +++ b/test/microreflectclient.c @@ -0,0 +1,281 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __HAIKU__ +# include +#endif + +#include "micromessage/MicroMessageGateway.h" +#include "reflector/StorageReflectConstants.h" + +static void Inet_NtoA(uint32 addr, char * ipbuf) +{ + sprintf(ipbuf, INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"", (addr>>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF); +} + +static int ConnectToIP(uint32 hostIP, uint16 port) +{ + char ipbuf[16]; + struct sockaddr_in saAddr; + int s; + + Inet_NtoA(hostIP, ipbuf); + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = htonl(hostIP); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) + { + if (connect(s, (struct sockaddr *) &saAddr, sizeof(saAddr)) >= 0) return s; + close(s); + } + return -1; +} + +static uint32 GetHostByName(const char * name) +{ + uint32 ret = inet_addr(name); // first see if we can parse it as a numeric address + if ((ret == 0)||(ret == (uint32)-1)) + { + struct hostent * he = gethostbyname(name); + ret = ntohl(he ? *((uint32*)he->h_addr) : 0); + } + else ret = ntohl(ret); + + return ret; +} + +static int Connect(const char * hostName, uint16 port) +{ + uint32 hostIP = GetHostByName(hostName); + return (hostIP > 0) ? ConnectToIP(hostIP, port) : -1; +} + +static int32 SocketSendFunc(const uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = send(*((int *)arg), (const char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +static int32 SocketRecvFunc(uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = recv(*((int *)arg), (char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +// This is a text based test client for the muscled server. It is useful for testing +// the server, and could possibly be useful for other things, I don't know. +// This implementation of the client uses only the C UMessage interface, for micronal executable size. +int main(int argc, char ** argv) +{ + char * hostName = "localhost"; + int port = 0; + int s; + uint8 inputBuffer[16*1024]; + uint8 outputBuffer[32*1024]; + + UMessageGateway gw; + UGGatewayInitialize(&gw, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); + + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 2960; + + s = Connect(hostName, (uint16)port); + if (s >= 0) + { + char text[1000] = ""; + UBool keepGoing = UTrue; + fd_set readSet, writeSet; + + printf("Connection to [%s:%i] succeeded.\n", hostName, port); + (void) fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK); /* Set s to non-blocking I/O mode */ + while(keepGoing) + { + int maxfd = s; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (UGHasBytesToOutput(&gw)) FD_SET(s, &writeSet); + + if (STDIN_FILENO > maxfd) maxfd = STDIN_FILENO; + FD_SET(STDIN_FILENO, &readSet); + + while(keepGoing) + { + if (select(maxfd+1, &readSet, &writeSet, NULL, NULL) < 0) printf("microreflectclient: select() failed!\n"); + + if (FD_ISSET(STDIN_FILENO, &readSet)) + { + char * ret; + if (fgets(text, sizeof(text), stdin) == NULL) text[0] = '\0'; + ret = strchr(text, '\n'); + if (ret) *ret = '\0'; + } + + if (text[0]) + { + UBool send = UTrue; + UMessage msg = UGGetOutgoingMessage(&gw, 0); + if (!UMIsMessageValid(&msg)) + { + printf("Error allocating UMessage to send!\n"); + break; + } + + printf("You typed: [%s]\n",text); + switch(text[0]) + { + case 'm': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMAddString(&msg, "info", "This is a user message"); + UMSetWhatCode(&msg, MAKETYPE("umsg")); + break; + + case 's': + { + UMessage subMsg = UMInlineAddMessage(&msg, &text[2], MAKETYPE("HELO")); + UMAddString(&subMsg, "test", "this is a sub message"); + UMSetWhatCode(&msg, PR_COMMAND_SETDATA); + } + break; + + case 'k': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_KICK); + break; + + case 'b': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_ADDBANS); + break; + + case 'B': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_REMOVEBANS); + break; + + case 'g': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_GETDATA); + break; + + case 'G': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMAddString(&msg, PR_NAME_TREE_REQUEST_ID, "Tree ID!"); + UMSetWhatCode(&msg, PR_COMMAND_GETDATATREES); + break; + + case 'q': + keepGoing = send = UFalse; + break; + + case 'p': + UMAddString(&msg, &text[2], ""); + UMSetWhatCode(&msg, PR_COMMAND_SETPARAMETERS); + break; + + case 'P': + UMSetWhatCode(&msg, PR_COMMAND_GETPARAMETERS); + break; + + case 'd': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_REMOVEDATA); + break; + + case 'D': + UMAddString(&msg, PR_NAME_KEYS, &text[2]); + UMSetWhatCode(&msg, PR_COMMAND_REMOVEPARAMETERS); + break; + + case 't': + { + const uint8 data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + UMAddString( &msg, "String", "this is a string"); + UMAddInt8( &msg, "Int8", 8); + UMAddInt8( &msg, "Int8", 9); + UMAddInt16( &msg, "Int16", 16); + UMAddInt16( &msg, "Int16", 17); + UMAddInt32( &msg, "Int32", 32); + UMAddInt32( &msg, "Int32", 33); + UMAddInt64( &msg, "Int64", 64); + UMAddInt64( &msg, "Int64", 65); + UMAddBool( &msg, "Bool", UTrue); + UMAddFloat( &msg, "Float", 3.14159f); + UMAddDouble( &msg, "Double", 6.28318f); + UMAddData( &msg, "Flat", B_RAW_TYPE, data, sizeof(data)); + UMSetWhatCode(&msg, 1234); + } + break; + + default: + printf("Sorry, wot?\n"); + send = UFalse; + break; + } + + if (send) + { + printf("Sending message...\n"); + UMPrintToStream(&msg, stdout); + UGOutgoingMessagePrepared(&gw, &msg); + } + else UGOutgoingMessageCancelled(&gw, &msg); + + text[0] = '\0'; + } + + { + UMessage incomingMsg; UMInitializeToInvalid(&incomingMsg); + UBool reading = FD_ISSET(s, &readSet); + UBool writing = FD_ISSET(s, &writeSet); + UBool writeError = ((writing)&&(UGDoOutput(&gw, ~0, SocketSendFunc, &s) < 0)); + UBool readError = ((reading)&&(UGDoInput( &gw, ~0, SocketRecvFunc, &s, &incomingMsg) < 0)); + + if (UMIsMessageValid(&incomingMsg)) + { + printf("Heard message from server:-----------------------------------\n"); + UMPrintToStream(&incomingMsg, stdout); + printf("-------------------------------------------------------------\n"); + } + + if ((readError)||(writeError)) + { + printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error"); + close(s); + keepGoing = UFalse; + } + + if ((reading == UFalse)&&(writing == UFalse)) break; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (UGHasBytesToOutput(&gw)) FD_SET(s, &writeSet); + FD_SET(STDIN_FILENO, &readSet); + } + } + } + close(s); + } + else printf("Connection to [%s:%i] failed!\n", hostName, port); + + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/minichatclient.c b/test/minichatclient.c new file mode 100644 index 00000000..25b78a27 --- /dev/null +++ b/test/minichatclient.c @@ -0,0 +1,750 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __HAIKU__ +# include +#endif + +#include "minimessage/MiniMessageGateway.h" +#include "reflector/StorageReflectConstants.h" + +#define VERSION_STRING "1.05" + +/* stolen from SystemInfo.cpp */ +static const char * GetOSName() +{ + const char * ret = "Unknown"; + (void) ret; // just to shut the Borland compiler up + +#ifdef WIN32 + ret = "Windows"; +#endif + +#ifdef __CYGWIN__ + ret = "Windows (CygWin)"; +#endif + +#ifdef __APPLE__ + ret = "MacOS/X"; +#endif + +#ifdef __linux__ + ret = "Linux"; +#endif + +#ifdef __BEOS__ + ret = "BeOS"; +#endif + +#ifdef __HAIKU__ + ret = "Haiku"; +#endif + +#ifdef __ATHEOS__ + ret = "AtheOS"; +#endif + +#if defined(__QNX__) || defined(__QNXTO__) + ret = "QNX"; +#endif + +#ifdef __FreeBSD__ + ret = "FreeBSD"; +#endif + +#ifdef __OpenBSD__ + ret = "OpenBSD"; +#endif + +#ifdef __NetBSD__ + ret = "NetBSD"; +#endif + +#ifdef __osf__ + ret = "Tru64"; +#endif + +#if defined(IRIX) || defined(__sgi) + ret = "Irix"; +#endif + +#ifdef __OS400__ + ret = "AS400"; +#endif + +#ifdef __OS2__ + ret = "OS/2"; +#endif + +#ifdef _AIX + ret = "AIX"; +#endif + +#ifdef _SEQUENT_ + ret = "Sequent"; +#endif + +#ifdef _SCO_DS + ret = "OpenServer"; +#endif + +#if defined(_HP_UX) || defined(__hpux) || defined(_HPUX_SOURCE) + ret = "HPUX"; +#endif + +#if defined(SOLARIS) || defined(__SVR4) + ret = "Solaris"; +#endif + +#if defined(__UNIXWARE__) || defined(__USLC__) + ret = "UnixWare"; +#endif + + return ret; +} + +/* stolen from ShareNetClient.h */ +enum +{ + NET_CLIENT_CONNECTED_TO_SERVER = 0, + NET_CLIENT_DISCONNECTED_FROM_SERVER, + NET_CLIENT_NEW_CHAT_TEXT, + NET_CLIENT_CONNECT_BACK_REQUEST, + NET_CLIENT_CHECK_FILE_COUNT, + NET_CLIENT_PING, + NET_CLIENT_PONG, + NET_CLIENT_SCAN_THREAD_REPORT +}; + +/* ditto */ +enum +{ + ROOT_DEPTH = 0, /* root node */ + HOST_NAME_DEPTH, + SESSION_ID_DEPTH, + BESHARE_HOME_DEPTH, /* used to separate our stuff from other (non-BeShare) data on the same server */ + USER_NAME_DEPTH, /* user's handle node would be found here */ + FILE_INFO_DEPTH /* user's shared file list is here */ +}; + +static void Inet_NtoA(uint32 addr, char * ipbuf) +{ + sprintf(ipbuf, INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"", (addr>>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF); +} + +static int ConnectToIP(uint32 hostIP, uint16 port) +{ + char ipbuf[16]; + struct sockaddr_in saAddr; + int s; + + Inet_NtoA(hostIP, ipbuf); + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = htonl(hostIP); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) + { + if (connect(s, (struct sockaddr *) &saAddr, sizeof(saAddr)) >= 0) return s; + close(s); + } + return -1; +} + +static uint32 GetHostByName(const char * name) +{ + uint32 ret = inet_addr(name); /* first see if we can parse it as a numeric address */ + if ((ret == 0)||(ret == (uint32)-1)) + { + struct hostent * he = gethostbyname(name); + ret = ntohl(he ? *((uint32*)he->h_addr) : 0); + } + else ret = ntohl(ret); + + return ret; +} + +static int Connect(const char * hostName, uint16 port) +{ + uint32 hostIP = GetHostByName(hostName); + return (hostIP > 0) ? ConnectToIP(hostIP, port) : -1; +} + +static int32 SocketSendFunc(const uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = send(*((int *)arg), (const char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +static int32 SocketRecvFunc(uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = recv(*((int *)arg), (char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +/* Creates an MMessage to send to the server when the user types in a text chat string */ +static MMessage * GenerateChatMessage(const char * targetSessionID, const char * messageText) +{ + MMessage * chatMessage = MMAllocMessage(NET_CLIENT_NEW_CHAT_TEXT); + if (chatMessage) + { + /* Specify which client(s) the Message should be forwarded to by muscled */ + MByteBuffer ** sb = MMPutStringField(chatMessage, MFalse, PR_NAME_KEYS, 1); + if (sb) + { + char buf[1024]; + sprintf(buf, "/*/%s/beshare", targetSessionID); + sb[0] = MBStrdupByteBuffer(buf); + } + + /* Add a "session" field so that the target clients will know who the */ + /* Message came from. Note that muscled will automatically set this field */ + /* to its proper value, so we don't have to; we just have to make sure it exists. */ + { + MByteBuffer ** sb = MMPutStringField(chatMessage, MFalse, "session", 1); + if (sb) sb[0] = MBStrdupByteBuffer("blah"); /* will be set by server */ + } + + /* Include the chat text that the user typed in to stdin */ + { + MByteBuffer ** sb = MMPutStringField(chatMessage, MFalse, "text", 1); + if (sb) sb[0] = MBStrdupByteBuffer(messageText); /* will be set by server */ + } + + /* And lastly, if the Message wasn't meant to be a public chat message, */ + /* include a "private" keyword to let the receiver know it's a private */ + /* communication, so they can print it in a different color or whatever. */ + if (strcmp(targetSessionID, "*")) + { + MBool * b = MMPutBoolField(chatMessage, MFalse, "private", 1); + if (b) *b = MTrue; + } + } + return chatMessage; +} + +/* Creates an MMessage to send to the server to set up our subscriptions */ +/* so that we get the proper notifications when other clients connect/disconnect, etc */ +static MMessage * GenerateServerSubscription(const char * subscriptionString, MBool quietly) +{ + MMessage * queryMsg = MMAllocMessage(PR_COMMAND_SETPARAMETERS); + if (queryMsg) + { + /* Tell muscled what we want to subscribe to */ + MBool * b = MMPutBoolField(queryMsg, MFalse, subscriptionString, 1); + if (b) *b = MTrue; /* the value doesn't signify anything */ + + if (quietly) + { + MBool * b = MMPutBoolField(queryMsg, MFalse, PR_NAME_SUBSCRIBE_QUIETLY, 1); + if (b) *b = MTrue; /* suppress the initial-state response from muscled */ + } + } + return queryMsg; +} + +/* Generates a MMessage that tells the server to post some interesting information */ +/* about our client, for the other clients to see. */ +static MMessage * GenerateSetLocalUserName(const char * name) +{ + MMessage * uploadMsg = MMAllocMessage(PR_COMMAND_SETDATA); + if (uploadMsg) + { + MMessage * nameMsg = MMAllocMessage(0); + if (nameMsg) + { + { + MByteBuffer ** sb = MMPutStringField(nameMsg, MFalse, "name", 1); + if (sb) sb[0] = MBStrdupByteBuffer(name); + } + + { + /* BeShare requires this field, even though we don't use it since we don't share files */ + int32 * pf = MMPutInt32Field(nameMsg, MFalse, "port", 1); + if (pf) *pf = 0; + } + + { + MByteBuffer ** sb = MMPutStringField(nameMsg, MFalse, "version_name", 1); + if (sb) sb[0] = MBStrdupByteBuffer("MUSCLE C mini chat client"); + } + + { + MByteBuffer ** sb = MMPutStringField(nameMsg, MFalse, "version_num", 1); + if (sb) sb[0] = MBStrdupByteBuffer(VERSION_STRING); + } + + { + MByteBuffer ** sb = MMPutStringField(nameMsg, MFalse, "host_os", 1); + if (sb) sb[0] = MBStrdupByteBuffer(GetOSName()); + } + + { + MMessage ** mf = MMPutMessageField(uploadMsg, MFalse, "beshare/name", 1); + if (mf) mf[0] = nameMsg; + } + } + else + { + MMFreeMessage(uploadMsg); + uploadMsg = NULL; + } + } + return uploadMsg; +} + +/* Generates a message to set this client's user-status on the server (e.g. "Here" or "Away") */ +static MMessage * GenerateSetLocalUserStatus(const char * status) +{ + MMessage * uploadMsg = MMAllocMessage(PR_COMMAND_SETDATA); + if (uploadMsg) + { + MMessage * statusMsg = MMAllocMessage(0); + if (statusMsg) + { + { + MByteBuffer ** sb = MMPutStringField(statusMsg, MFalse, "userstatus", 1); + if (sb) sb[0] = MBStrdupByteBuffer(status); + } + { + MMessage ** mf = MMPutMessageField(uploadMsg, MFalse, "beshare/userstatus", 1); + if (mf) mf[0] = statusMsg; + } + } + else + { + MMFreeMessage(uploadMsg); + uploadMsg = NULL; + } + } + return uploadMsg; +} + +/* Returns a pointer into (path) after the (depth)'th '/' char */ +static const char * GetPathClause(int depth, const char * path) +{ + int i; + for (i=0; i_sessionID == sid) return cur->_name; + cur = cur->_nextUser; + } + return ""; +} + +static MBool RemoveUserName(struct User ** users, int sid) +{ + struct User * cur = *users; + struct User * prev = NULL; + while(cur) + { + if (cur->_sessionID == sid) + { + if (prev) prev->_nextUser = cur->_nextUser; + else *users = cur->_nextUser; + free((char *) cur->_name); + free(cur); + return MTrue; + } + prev = cur; + cur = cur->_nextUser; + } + return MFalse; +} + +static void PutUserName(struct User ** users, int sid, const char * name) +{ + struct User * newUser = malloc(sizeof(struct User)); + if (newUser) + { + (void) RemoveUserName(users, sid); /* paranoia (ensure no duplicate sid's) */ + + newUser->_sessionID = sid; + newUser->_nextUser = *users; + newUser->_name = strdup(name); + if (newUser->_name) *users = newUser; + else free(newUser); + } +} + +/* This is a text based BeShare-compatible chat client for the muscled server. */ +/* It is useful as an example of a chat client written in C, and perhaps for other things also. */ +/* This implementation of the client uses only the C MMessage interface, for mininal executable size. */ +int main(int argc, char ** argv) +{ + const char * hostName = "beshare.tycomsystems.com"; + const char * userName = "miniclyde"; + const char * userStatus = "here"; + const char * tempStr; + int s; + int port = 0; + struct User * users = NULL; /* doubly-linked-list! */ + + MMessageGateway * gw = MGAllocMessageGateway(); + if (gw == NULL) + { + printf("Error allocating MMessageGateway, aborting!\n"); + exit(10); + } + +#ifdef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + printf("Warning: This program doesn't run very well on this OS, because the OS can't select() on stdin. You'll need to press return a lot.\n"); +#endif + + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 2960; /* default muscled port */ + + s = Connect(hostName, (uint16)port); + if (s >= 0) + { + char text[1000] = ""; + MBool keepGoing = MTrue; + fd_set readSet, writeSet; + + printf("Connection to [%s:%i] succeeded.\n", hostName, port); + (void) fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK); /* Set s to non-blocking I/O mode */ + + { + /* Tell the server our user's name (etc) */ + MMessage * msg = GenerateSetLocalUserName(userName); + if (msg) + { + MGAddOutgoingMessage(gw, msg); + MMFreeMessage(msg); + } + } + + { + /* Tell the server our status */ + MMessage * msg = GenerateSetLocalUserStatus(userStatus); + if (msg) + { + MGAddOutgoingMessage(gw, msg); + MMFreeMessage(msg); + } + } + + { + /* Tell the server we want to be updated about the beshare-specific attributes of other clients */ + MMessage * msg = GenerateServerSubscription("SUBSCRIBE:beshare/*", MFalse); + if (msg) + { + MGAddOutgoingMessage(gw, msg); + MMFreeMessage(msg); + } + } + + /* the main event loop */ + while(keepGoing) + { + int maxfd = s; + struct timeval * timeout = NULL; + char buf[2048] = ""; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + + if (MGHasBytesToOutput(gw)) FD_SET(s, &writeSet); + +#ifdef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + /* This OS can't select() on stdin, so just make the user press enter or whatever */ + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + {char * ret = strchr(buf, '\n'); if (ret) *ret = '\0';} + struct timeval poll = {0, 0}; + timeout = &poll; /* prohibit blocking in select() */ +#else + if (STDIN_FILENO > maxfd) maxfd = STDIN_FILENO; + FD_SET(STDIN_FILENO, &readSet); +#endif + + while(keepGoing) + { + if (select(maxfd+1, &readSet, &writeSet, NULL, timeout) < 0) printf("minireflectclient: select() failed!\n"); + +#ifndef SELECT_ON_FILE_DESCRIPTORS_NOT_AVAILABLE + if (FD_ISSET(STDIN_FILENO, &readSet)) + { + if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + {char * ret; ret = strchr(buf, '\n'); if (ret) *ret = '\0'; } + } +#endif + + /* Handle what the user typed in to stdin */ + if (buf[0]) + { + MMessage * sendMsg = NULL; + if (strncmp(buf, "/msg ", 5) == 0) + { + char * nextSpace = strchr(buf+5, ' '); /* after the target ID */ + if (nextSpace) + { + *nextSpace = '\0'; + sendMsg = GenerateChatMessage(buf+5, nextSpace+1); + } + else printf("Can't send private /msg, no message text was specified!\n"); + } + else if (strncmp(buf, "/nick ", 6) == 0) + { + printf("Setting local user name to [%s]\n", buf+6); + sendMsg = GenerateSetLocalUserName(buf+6); + } + else if (strncmp(buf, "/status ", 8) == 0) + { + printf("Setting local user status to [%s]\n", buf+8); + sendMsg = GenerateSetLocalUserStatus(buf+8); + } + else if (strncmp(buf, "/help", 5) == 0) printf("Available commands are: /nick, /msg, /status, /help, and /quit\n"); + else if (strncmp(buf, "/quit", 5) == 0) keepGoing = MFalse; + else sendMsg = GenerateChatMessage("*", buf); + + if (sendMsg) + { +/* printf("Sending message...\n"); */ +/* MMPrintToStream(sendMsg); */ + MGAddOutgoingMessage(gw, sendMsg); + MMFreeMessage(sendMsg); /* yes, we are still responsible for freeing sendMsg! */ + } + buf[0] = '\0'; + } + + { + MMessage * msg = NULL; + MBool reading = FD_ISSET(s, &readSet); + MBool writing = FD_ISSET(s, &writeSet); + MBool writeError = ((writing)&&(MGDoOutput(gw, ~0, SocketSendFunc, &s) < 0)); + MBool readError = ((reading)&&(MGDoInput( gw, ~0, SocketRecvFunc, &s, &msg) < 0)); /* sets (msg) if one is available */ + if (msg) + { +/* printf("Heard message from server:-----------------------------------\n"); */ +/* MMPrintToStream(msg); */ +/* printf("-------------------------------------------------------------\n"); */ + switch(MMGetWhat(msg)) + { + case NET_CLIENT_PING: /* respond to other clients' pings */ + { + MByteBuffer ** sf = MMGetStringField(msg, "session", NULL); + if (sf) + { + const char * replyTo = (const char *) &sf[0]->bytes; + int replyToLen = 3+((int)strlen(replyTo))+8+1; + MByteBuffer * temp = MBAllocByteBuffer(replyToLen, MTrue); + + MMSetWhat(msg, NET_CLIENT_PONG); + MMRemoveField(msg, PR_NAME_KEYS); /* out with the old address */ + + if (temp) + { + char * buf = (char *) &temp->bytes; + MByteBuffer ** sb = MMPutStringField(msg, MFalse, PR_NAME_KEYS, 1); + if (sb) + { + strncat(buf, "/*/", replyToLen); + strncat(buf, replyTo, replyToLen); + strncat(buf, "/beshare", replyToLen); + sb[0] = temp; + } + else MBFreeByteBuffer(temp); + } + + MMRemoveField(msg, "version"); + { + MByteBuffer ** sb = MMPutStringField(msg, MFalse, "version", 1); + if (sb) sb[0] = MBStrdupByteBuffer("MUSCLE C mini chat client v" VERSION_STRING); + } + MGAddOutgoingMessage(gw, msg); /* send the pong back to the server */ + } + } + break; + + case NET_CLIENT_NEW_CHAT_TEXT: + { + /* Someone has sent a line of chat text to display */ + MByteBuffer ** textField = MMGetStringField(msg, "text", NULL); + MByteBuffer ** sessionField = MMGetStringField(msg, "session", NULL); + if ((textField)&&(sessionField)) + { + const char * text = (const char *) &textField[0]->bytes; + int session = atoi((const char *)&sessionField[0]->bytes); + if (strncmp(text, "/me ", 4) == 0) printf(": %s %s\n", GetUserName(users, session), &text[4]); + else printf("%s(%s): %s\n", MMGetBoolField(msg, "private", NULL) ? ": ":"", GetUserName(users, session), text); + } + } + break; + + case PR_RESULT_DATAITEMS: + { + /* Look for sub-messages that indicate that nodes were removed from the tree */ + uint32 removeCount = 0; + uint32 i; + MByteBuffer ** removedField = MMGetStringField(msg, PR_NAME_REMOVED_DATAITEMS, &removeCount); + for (i=0; ibytes; + int pathDepth = GetPathDepth(nodepath); + if (pathDepth >= USER_NAME_DEPTH) + { + const char * sessionID = GetPathClause(SESSION_ID_DEPTH, nodepath); + if (sessionID) + { + int sid = atol(sessionID); + switch(GetPathDepth(nodepath)) + { + case USER_NAME_DEPTH: + if (strncmp(GetPathClause(USER_NAME_DEPTH, nodepath), "name", 4) == 0) + { + const char * userName = GetUserName(users, sid); + if (userName) + { + printf("User [%s] has disconnected.\n", userName); + RemoveUserName(&users, sid); + } + } + break; + } + } + } + } + + /* Look for sub-messages that indicate that nodes were added to the tree */ + { + MMessageIterator iter = MMGetFieldNameIterator(msg, B_MESSAGE_TYPE); + const char * nodepath; + while((nodepath = MMGetNextFieldName(&iter, NULL)) != NULL) + { + int pathDepth = GetPathDepth(nodepath); + if (pathDepth == USER_NAME_DEPTH) + { + uint32 msgCount = 0; + uint32 i; + MMessage ** mf = MMGetMessageField(msg, nodepath, &msgCount); + for (i=0; ibytes); + printf("User #%i is now known as %s\n", sid, (const char *) (&nf[0]->bytes)); + } + } + else if (strncmp(nodeName, "userstatus", 9) == 0) + { + MByteBuffer ** sf = MMGetStringField(pmsg, "userstatus", NULL); + if (sf) printf("%s is now [%s]\n", GetUserName(users, sid), (const char *) &sf[0]->bytes); + } + } + break; + } + } + } + } + } + } + } + } + MMFreeMessage(msg); + } + + if ((readError)||(writeError)) + { + printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error"); + close(s); + keepGoing = MFalse; + } + + if ((reading == MFalse)&&(writing == MFalse)) break; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (MGHasBytesToOutput(gw)) FD_SET(s, &writeSet); + FD_SET(STDIN_FILENO, &readSet); + } + } + } + close(s); + } + else printf("Connection to [%s:%i] failed!\n", hostName, port); + + MGFreeMessageGateway(gw); + + while(users) + { + struct User * next = users->_nextUser; + free(users); + users = next; + } + + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/minireflectclient.c b/test/minireflectclient.c new file mode 100644 index 00000000..a405e2c2 --- /dev/null +++ b/test/minireflectclient.c @@ -0,0 +1,325 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __HAIKU__ +# include +#endif + +#include "minimessage/MiniMessageGateway.h" +#include "reflector/StorageReflectConstants.h" + +static void Inet_NtoA(uint32 addr, char * ipbuf) +{ + sprintf(ipbuf, INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"."INT32_FORMAT_SPEC"", (addr>>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF); +} + +static int ConnectToIP(uint32 hostIP, uint16 port) +{ + char ipbuf[16]; + struct sockaddr_in saAddr; + int s; + + Inet_NtoA(hostIP, ipbuf); + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = htonl(hostIP); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) + { + if (connect(s, (struct sockaddr *) &saAddr, sizeof(saAddr)) >= 0) return s; + close(s); + } + return -1; +} + +static uint32 GetHostByName(const char * name) +{ + uint32 ret = inet_addr(name); // first see if we can parse it as a numeric address + if ((ret == 0)||(ret == (uint32)-1)) + { + struct hostent * he = gethostbyname(name); + ret = ntohl(he ? *((uint32*)he->h_addr) : 0); + } + else ret = ntohl(ret); + + return ret; +} + +static int Connect(const char * hostName, uint16 port) +{ + uint32 hostIP = GetHostByName(hostName); + return (hostIP > 0) ? ConnectToIP(hostIP, port) : -1; +} + +static int32 SocketSendFunc(const uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = send(*((int *)arg), (const char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +static int32 SocketRecvFunc(uint8 * buf, uint32 numBytes, void * arg) +{ + int ret = recv(*((int *)arg), (char *)buf, numBytes, 0L); + return (ret == -1) ? ((errno == EWOULDBLOCK)?0:-1) : ret; +} + +// This is a text based test client for the muscled server. It is useful for testing +// the server, and could possibly be useful for other things, I don't know. +// This implementation of the client uses only the C MMessage interface, for mininal executable size. +int main(int argc, char ** argv) +{ + char * hostName = "localhost"; + int port = 0; + int s; + + MMessageGateway * gw = MGAllocMessageGateway(); + if (gw == NULL) + { + printf("Error allocating MMessageGateway, aborting!\n"); + exit(10); + } + + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 2960; + + s = Connect(hostName, (uint16)port); + if (s >= 0) + { + char text[1000] = ""; + MBool keepGoing = MTrue; + fd_set readSet, writeSet; + + printf("Connection to [%s:%i] succeeded.\n", hostName, port); + (void) fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK); /* Set s to non-blocking I/O mode */ + while(keepGoing) + { + int maxfd = s; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + + if (MGHasBytesToOutput(gw)) FD_SET(s, &writeSet); + + if (STDIN_FILENO > maxfd) maxfd = STDIN_FILENO; + FD_SET(STDIN_FILENO, &readSet); + + while(keepGoing) + { + if (select(maxfd+1, &readSet, &writeSet, NULL, NULL) < 0) printf("minireflectclient: select() failed!\n"); + + if (FD_ISSET(STDIN_FILENO, &readSet)) + { + char * ret; + if (fgets(text, sizeof(text), stdin) == NULL) text[0] = '\0'; + ret = strchr(text, '\n'); + if (ret) *ret = '\0'; + } + + if (text[0]) + { + MBool send = MTrue; + MMessage * msg = MMAllocMessage(0); + if (msg == NULL) + { + printf("Error allocating MMessage!\n"); + break; + } + + printf("You typed: [%s]\n",text); + + switch(text[0]) + { + case 'm': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + MByteBuffer ** ib = MMPutStringField(msg, false, "info", 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + if (ib) ib[0] = MBStrdupByteBuffer("This is a user message"); + MMSetWhat(msg, MAKETYPE("umsg")); + } + break; + + case 's': + { + MMessage ** mb = MMPutMessageField(msg, false, &text[2], 1); + if (mb) mb[0] = MMAllocMessage(MAKETYPE("HELO")); + MMSetWhat(msg, PR_COMMAND_SETDATA); + } + break; + + case 'k': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_KICK); + } + break; + + case 'b': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_ADDBANS); + } + break; + + case 'B': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_REMOVEBANS); + } + break; + + case 'g': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_GETDATA); + } + break; + + case 'G': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + MByteBuffer ** ib = MMPutStringField(msg, false, PR_NAME_TREE_REQUEST_ID, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + if (ib) ib[0] = MBStrdupByteBuffer("Tree ID!"); + MMSetWhat(msg, PR_COMMAND_GETDATATREES); + } + break; + + case 'q': + keepGoing = send = MFalse; + break; + + case 'p': + { + MByteBuffer ** sb = MMPutStringField(msg, false, &text[2], 1); + if (sb) sb[0] = MBStrdupByteBuffer(""); + MMSetWhat(msg, PR_COMMAND_SETPARAMETERS); + } + break; + + case 'P': + MMSetWhat(msg, PR_COMMAND_GETPARAMETERS); + break; + + case 'd': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_REMOVEDATA); + } + break; + + case 'D': + { + MByteBuffer ** sb = MMPutStringField(msg, false, PR_NAME_KEYS, 1); + if (sb) sb[0] = MBStrdupByteBuffer(&text[2]); + MMSetWhat(msg, PR_COMMAND_REMOVEPARAMETERS); + } + break; + + case 't': + { + MByteBuffer ** sb = MMPutStringField(msg, false, "String", 1); + int8 * i8 = MMPutInt8Field(msg, false, "Int8", 2); + int16 * i16 = MMPutInt16Field(msg, false, "Int16", 2); + int32 * i32 = MMPutInt32Field(msg, false, "Int32", 2); + int64 * i64 = MMPutInt64Field(msg, false, "Int64", 2); + MBool * ib = MMPutBoolField(msg, false, "Bool", 2); + float * fb = MMPutFloatField(msg, false, "Float", 2); + double * db = MMPutDoubleField(msg, false, "Double", 2); + void ** pb = MMPutPointerField(msg, false, "Pointer", 1); + MByteBuffer ** rb = MMPutDataField(msg, false, B_RAW_TYPE, "Flat", 1); + char data[] = "This is some data"; + + if (sb) sb[0] = MBStrdupByteBuffer("this is a string"); + if (i8) {i8[0] = 123; i8[1] = -123;} + if (i16) {i16[0] = 1234; i16[1] = -1234;} + if (i32) {i32[0] = 12345; i32[1] = -12345;} + if (i64) {i64[0] = 123456789; i64[1] = -123456789;} + if (ib) {ib[0] = MFalse; ib[1] = MTrue;} + if (fb) {fb[0] = 1234.56789f; fb[1] = -1234.56789f;} + if (db) {db[0] = 1234.56789; db[1] = -1234.56789;} + if (pb) {pb[0] = NULL;} + if (rb) {rb[0] = MBStrdupByteBuffer(data);} + MMSetWhat(msg, 1234); + } + break; + + default: + printf("Sorry, wot?\n"); + send = MFalse; + break; + } + + if (send) + { + printf("Sending message...\n"); + MMPrintToStream(msg, stdout); + MGAddOutgoingMessage(gw, msg); + } + MMFreeMessage(msg); + + text[0] = '\0'; + } + + { + MMessage * incomingMsg = NULL; + MBool reading = FD_ISSET(s, &readSet); + MBool writing = FD_ISSET(s, &writeSet); + MBool writeError = ((writing)&&(MGDoOutput(gw, ~0, SocketSendFunc, &s) < 0)); + MBool readError = ((reading)&&(MGDoInput( gw, ~0, SocketRecvFunc, &s, &incomingMsg) < 0)); + + if (incomingMsg) + { + printf("Heard message from server:-----------------------------------\n"); + MMPrintToStream(incomingMsg, stdout); + printf("-------------------------------------------------------------\n"); + MMFreeMessage(incomingMsg); + } + + if ((readError)||(writeError)) + { + printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error"); + close(s); + keepGoing = MFalse; + } + + if ((reading == MFalse)&&(writing == MFalse)) break; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(s, &readSet); + if (MGHasBytesToOutput(gw)) FD_SET(s, &writeSet); + FD_SET(STDIN_FILENO, &readSet); + } + } + } + close(s); + } + else printf("Connection to [%s:%i] failed!\n", hostName, port); + + MGFreeMessageGateway(gw); + + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/portableplaintextclient.cpp b/test/portableplaintextclient.cpp new file mode 100644 index 00000000..cbd14ff9 --- /dev/null +++ b/test/portableplaintextclient.cpp @@ -0,0 +1,94 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +// This is a text based test client to test the PlainTextMessageIOGateway class. +// It should be able to communicate with any server that sends and receives +// lines of ASCII text (e.g. Web servers, XML, eCommerce, etc). +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + const char * hostName = "localhost"; + int port = 0; + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 80; + + ConstSocketRef s = Connect(hostName, (uint16)port, "portableplaintextclient", false); + if (s() == NULL) return 10; + + SocketMultiplexer multiplexer; + PlainTextMessageIOGateway gw; + gw.SetDataIO(DataIORef(new TCPSocketDataIO(s, false))); + char text[1000] = ""; + while(s()) + { + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + multiplexer.RegisterSocketForReadReady(STDIN_FILENO); + + QueueGatewayMessageReceiver inQueue; + while(s()) + { + if (multiplexer.WaitForEvents() < 0) printf("portablereflectclient: WaitForEvents() failed!\n"); + if (multiplexer.IsSocketReadyForRead(STDIN_FILENO)) + { + if (fgets(text, sizeof(text), stdin) == NULL) text[0] = '\0'; + char * ret = strchr(text, '\n'); if (ret) *ret = '\0'; + } + + if (text[0]) + { + printf("Sending: [%s]\n",text); + MessageRef msg = GetMessageFromPool(); + msg()->AddString(PR_NAME_TEXT_LINE, text); + gw.AddOutgoingMessage(msg); + text[0] = '\0'; + } + + bool reading = multiplexer.IsSocketReadyForRead(fd); + bool writing = multiplexer.IsSocketReadyForWrite(fd); + bool writeError = ((writing)&&(gw.DoOutput() < 0)); + bool readError = ((reading)&&(gw.DoInput(inQueue) < 0)); + if ((readError)||(writeError)) + { + printf("Connection closed, exiting.\n"); + s.Reset(); + } + + MessageRef incoming; + while(inQueue.RemoveHead(incoming) == B_NO_ERROR) + { + printf("Heard message from server:-----------------------------------\n"); + const char * inStr; + for (int i=0; (incoming()->FindString(PR_NAME_TEXT_LINE, i, &inStr) == B_NO_ERROR); i++) printf("Line %i: [%s]\n", i, inStr); + + printf("-------------------------------------------------------------\n"); + } + + if ((reading == false)&&(writing == false)) break; + + multiplexer.RegisterSocketForReadReady(fd); + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + } + } + + if (gw.HasBytesToOutput()) + { + printf("Waiting for all pending messages to be sent...\n"); + while((gw.HasBytesToOutput())&&(gw.DoOutput() >= 0)) {printf ("."); fflush(stdout);} + } + printf("\n\nBye!\n"); + return 0; +} diff --git a/test/portablereflectclient.cpp b/test/portablereflectclient.cpp new file mode 100644 index 00000000..796f65fe --- /dev/null +++ b/test/portablereflectclient.cpp @@ -0,0 +1,287 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_SSL +# include "dataio/SSLSocketDataIO.h" +# include "iogateway/SSLSocketAdapterGateway.h" +#endif + +#include "dataio/StdinDataIO.h" +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "regex/QueryFilter.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +// This is a text based test client for the muscled server. It is useful for testing +// the server, and could possibly be useful for other things, I don't know. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + String hostName; + uint16 port = 2960; + if (argc > 1) ParseConnectArg(argv[1], hostName, port, false); + ConstSocketRef sock = Connect(hostName(), port, "portablereflectclient", false); + if (sock() == NULL) return 10; + + // We'll receive plain text over stdin + StdinDataIO stdinIO(false); + PlainTextMessageIOGateway stdinGateway; + stdinGateway.SetDataIO(DataIORef(&stdinIO, false)); + + // And send and receive flattened Message objects over our TCP socket + TCPSocketDataIO tcpIO(sock, false); + MessageIOGateway tcpGateway; + tcpGateway.SetDataIO(DataIORef(&tcpIO, false)); + + DataIORef networkIORef(&tcpIO, false); + AbstractMessageIOGatewayRef gatewayRef(&tcpGateway, false); + +#ifdef MUSCLE_ENABLE_SSL + for (int i=1; iSetDataIO(networkIORef); + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't load public key certificate file [%s] (file not found?)\n", a); + return 10; + } + } + } +#endif + + SocketMultiplexer multiplexer; + QueueGatewayMessageReceiver stdinInQueue, tcpInQueue; + bool keepGoing = true; + uint64 nextTimeoutTime = MUSCLE_TIME_NEVER; + while(keepGoing) + { + int stdinFD = stdinIO.GetReadSelectSocket().GetFileDescriptor(); + int socketReadFD = networkIORef()->GetReadSelectSocket().GetFileDescriptor(); + int socketWriteFD = networkIORef()->GetWriteSelectSocket().GetFileDescriptor(); + + multiplexer.RegisterSocketForReadReady(stdinFD); + multiplexer.RegisterSocketForReadReady(socketReadFD); + if (gatewayRef()->HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(socketWriteFD); + if (multiplexer.WaitForEvents(nextTimeoutTime) < 0) printf("portablereflectclient: WaitForEvents() failed!\n"); + + uint64 now = GetRunTime64(); + if (now >= nextTimeoutTime) + { + // For OpenSSL testing: Generate some traffic to the server every 50mS + printf("Uploading timed OpenSSL-tester update at time " UINT64_FORMAT_SPEC "\n", now); + MessageRef stateMsg = GetMessageFromPool(); + stateMsg()->AddString("username", "portablereflectclient"); + stateMsg()->AddPoint("position", Point((rand()%100)/100.0f, (rand()%100)/100.0f)); + stateMsg()->AddInt32("color", -1); + + MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA); + uploadMsg()->AddMessage("qt_example/state", stateMsg); + gatewayRef()->AddOutgoingMessage(uploadMsg); + + nextTimeoutTime = now + MillisToMicros(50); + } + + // Receive data from stdin + if (multiplexer.IsSocketReadyForRead(stdinFD)) + { + while(1) + { + int32 bytesRead = stdinGateway.DoInput(stdinInQueue); + if (bytesRead < 0) + { + printf("Stdin closed, exiting!\n"); + keepGoing = false; + break; + } + else if (bytesRead == 0) break; // no more to read + } + } + + // Handle any input lines that were received from stdin + MessageRef msgFromStdin; + while(stdinInQueue.RemoveHead(msgFromStdin) == B_NO_ERROR) + { + const String * st; + for (int32 i=0; msgFromStdin()->FindString(PR_NAME_TEXT_LINE, i, &st) == B_NO_ERROR; i++) + { + const char * text = st->Cstr(); + + printf("You typed: [%s]\n", text); + bool send = true; + MessageRef ref = GetMessageFromPool(); + + switch(text[0]) + { + case 'm': + ref()->what = MAKETYPE("umsg"); + ref()->AddString(PR_NAME_KEYS, &text[2]); + ref()->AddString("info", "This is a user message"); + break; + + case 's': + { + ref()->what = PR_COMMAND_SETDATA; + MessageRef uploadMsg = GetMessageFromPool(MAKETYPE("HELO")); + uploadMsg()->AddString("This node was posted at: ", GetHumanReadableTimeString(GetRunTime64())); + ref()->AddMessage(&text[2], uploadMsg); + } + break; + + case 'k': + ref()->what = PR_COMMAND_KICK; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'b': + ref()->what = PR_COMMAND_ADDBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'B': + ref()->what = PR_COMMAND_REMOVEBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'g': + ref()->what = PR_COMMAND_GETDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'G': + ref()->what = PR_COMMAND_GETDATATREES; + ref()->AddString(PR_NAME_KEYS, &text[2]); + ref()->AddString(PR_NAME_TREE_REQUEST_ID, "Tree ID!"); + break; + + case 'q': + keepGoing = send = false; + break; + + case 'p': + ref()->what = PR_COMMAND_SETPARAMETERS; + ref()->AddString(&text[2], ""); + break; + + case 'P': + ref()->what = PR_COMMAND_GETPARAMETERS; + break; + + case 'L': + { + // simulate the behavior of qt_example, for testing OpenSSL problem + ref()->what = PR_COMMAND_SETPARAMETERS; + ref()->AddBool("SUBSCRIBE:qt_example/state", true); + printf("Starting OpenSSL problem test...\n"); + nextTimeoutTime = 0; + } + break; + + case 'x': + { + ref()->what = PR_COMMAND_SETPARAMETERS; + StringQueryFilter sqf("sc_tstr", StringQueryFilter::OP_SIMPLE_WILDCARD_MATCH, "*Output*"); + ref()->AddArchiveMessage("SUBSCRIBE:/*/*/csproj/default/subcues/*", sqf); + } + break; + + case 'd': + ref()->what = PR_COMMAND_REMOVEDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'D': + ref()->what = PR_COMMAND_REMOVEPARAMETERS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 't': + { + // test all data types + ref()->what = 1234; + ref()->AddString("String", "this is a string"); + ref()->AddInt8("Int8", 123); + ref()->AddInt8("-Int8", -123); + ref()->AddInt16("Int16", 1234); + ref()->AddInt16("-Int16", -1234); + ref()->AddInt32("Int32", 12345); + ref()->AddInt32("-Int32", -12345); + ref()->AddInt64("Int64", 123456789); + ref()->AddInt64("-Int64", -123456789); + ref()->AddBool("Bool", true); + ref()->AddBool("-Bool", false); + ref()->AddFloat("Float", 1234.56789f); + ref()->AddFloat("-Float", -1234.56789f); + ref()->AddDouble("Double", 1234.56789); + ref()->AddDouble("-Double", -1234.56789); + ref()->AddPointer("Pointer", ref()); + ref()->AddFlat("Flat", *ref()); + char data[] = "This is some data"; + ref()->AddData("Flat", B_RAW_TYPE, data, sizeof(data)); + } + break; + + default: + printf("Sorry, wot?\n"); + send = false; + break; + } + + if (send) + { + printf("Sending message...\n"); + ref()->PrintToStream(); + gatewayRef()->AddOutgoingMessage(ref); + } + } + } + + // Handle input and output on the TCP socket + bool reading = multiplexer.IsSocketReadyForRead(socketReadFD); + bool writing = multiplexer.IsSocketReadyForWrite(socketWriteFD); + bool writeError = ((writing)&&(gatewayRef()->DoOutput() < 0)); + bool readError = ((reading)&&(gatewayRef()->DoInput(tcpInQueue) < 0)); + if ((readError)||(writeError)) + { + printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error"); + keepGoing = false; + } + + MessageRef msgFromTCP; + while(tcpInQueue.RemoveHead(msgFromTCP) == B_NO_ERROR) + { + printf("Heard message from server:-----------------------------------\n"); + msgFromTCP()->PrintToStream(); + printf("-------------------------------------------------------------\n"); + } + } + + if (gatewayRef()->HasBytesToOutput()) + { + printf("Waiting for all pending messages to be sent...\n"); + while((gatewayRef()->HasBytesToOutput())&&(gatewayRef()->DoOutput() >= 0)) {printf ("."); fflush(stdout);} + } + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/portablereflectclient.vcproj b/test/portablereflectclient.vcproj new file mode 100644 index 00000000..5c3da74c --- /dev/null +++ b/test/portablereflectclient.vcproj @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/portscan.cpp b/test/portscan.cpp new file mode 100644 index 00000000..45943d0d --- /dev/null +++ b/test/portscan.cpp @@ -0,0 +1,56 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "util/NetworkUtilityFunctions.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + if (argc <= 1) + { + LogTime(MUSCLE_LOG_INFO, "Usage: portscan [baseport] [numports]\n"); + return 5; + } + + const char * hostName = "localhost"; + if (argc > 1) hostName = argv[1]; + + int base = 0; + if (argc > 2) base = atoi(argv[2]); + + int count = 65536; + if (argc > 3) count = atoi(argv[3]); + + Queue foundPorts; + + ip_address ip = GetHostByName(hostName); + if (ip == invalidIP) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Unable to resolve hostname [%s]\n", hostName); + return 10; + } + + char buf[64]; Inet_NtoA(ip, buf); + int final = muscleMin(base+count, 65536); + LogTime(MUSCLE_LOG_INFO, "Beginning scan of ports %i-%i at %s...\n", base, final-1, buf); + uint64 lastTime = 0; + for (int i=base; i +#include "dataio/FileDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "util/Directory.h" +#include "util/FilePathInfo.h" +#include "syslog/SysLog.h" +#include "system/SetupSystem.h" +#include "system/SystemInfo.h" + +using namespace muscle; + +static void CheckFile(const String & path, Queue & codes) +{ + FileDataIO dio(fopen(path(), "r")); + if (dio.GetFile()) + { + String fileName = path.Substring(GetFilePathSeparator()); + + PlainTextMessageIOGateway gw; + gw.SetDataIO(DataIORef(&dio, false)); + + // Read in the file + QueueGatewayMessageReceiver q; + while(gw.DoInput(q) > 0) {/* empty */} + + // Now parse the lines, and for any that have LogTime() in them, print out the line and the corresponding code key + uint32 lineNumber = 1; + MessageRef msg; + while(q.RemoveHead(msg) == B_NO_ERROR) + { + const String * line; + for (uint32 i=0; msg()->FindString(PR_NAME_TEXT_LINE, i, &line) == B_NO_ERROR; i++) + { + int32 ltIdx = line->IndexOf("LogTime("); + if (ltIdx >= 0) + { + int32 commentIdx = line->IndexOf("//"); // don't include LogTime() calls that are commented out + if ((commentIdx < 0)||(commentIdx > ltIdx)) + { + char buf[128]; + sprintf(buf, "[%s] %s:" UINT32_FORMAT_SPEC": ", SourceCodeLocationKeyToString(GenerateSourceCodeLocationKey(fileName(), lineNumber))(), path(), lineNumber); + codes.AddTail(line->Prepend(buf)); + } + } + lineNumber++; + } + } + } +} + +static void DoSearch(const String & path, Queue & codes) +{ + Directory d(path()); + if (d.IsValid()) + { + const char * nextName; + while((nextName = d.GetCurrentFileName()) != NULL) + { + if (nextName[0] != '.') + { + String subPath = path + GetFilePathSeparator() + nextName; + FilePathInfo fpi(subPath()); + if (fpi.IsDirectory()) DoSearch(subPath, codes); + else if (fpi.IsRegularFile()) + { + String iName = nextName; iName = iName.ToLowerCase(); + if ((iName.EndsWith(".c"))||(iName.EndsWith(".cpp"))||(iName.EndsWith(".h"))||(iName.EndsWith(".hpp"))||(iName.EndsWith(".cc"))) CheckFile(subPath, codes); + } + } + d++; + } + } +} + +// This program accepts a directory name as the argument, and will find and print all LogTime() +// calls in and underneath that directory, along with their source-code-location keys. (e.g. "FB72") +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + if (argc < 2) + { + printf("Usage: printsourcelocations dirname\n"); + return 10; + } + + Directory d(argv[1]); + if (d.IsValid() == false) + { + printf("[%s] is not a valid directory path.\n", argv[1]); + return 10; + } + + Queue codes; + DoSearch(argv[1], codes); + codes.Sort(); + for (uint32 i=0; i +#include "support/MuscleSupport.h" + +// Just a silly program to print the alphabetical equivalent +// of the passed in integer value. +int main(int argc, char ** argv) +{ + if ((argc == 2)&&(muscleInRange(argv[1][0], '0', '9'))) + { + char * code = argv[1]; + uint32 val = strtod(code, NULL); + + for (int i=3; i>=0; i--) + { + char c = (char) ((val>>(i*8))&0xFF); + putc(c, stdout); + } + printf("\n"); + } + else printf("Usage Example: printtypecode (integer_value)\n"); + return 0; +} diff --git a/test/readmessage.cpp b/test/readmessage.cpp new file mode 100644 index 00000000..4a0ac89c --- /dev/null +++ b/test/readmessage.cpp @@ -0,0 +1,55 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "system/SetupSystem.h" +#include "message/Message.h" +#include "zlib/ZLibUtilityFunctions.h" + +using namespace muscle; + +// A simple utility to read in a flattened Message file from disk, and print it out. +int main(int argc, char ** argv) +{ + int retVal = 0; + + CompleteSetupSystem css; + + const char * fileName = (argc > 1) ? argv[1] : "test.msg"; + FILE * fpIn = fopen(fileName, "rb"); + if (fpIn) + { + uint32 bufSize = 100*1024; // if your message is >100KB flattened, then tough luck + uint8 * buf = new uint8[bufSize]; + int numBytesRead = fread(buf, 1, bufSize, fpIn); + printf("Read %i bytes from [%s]\n", numBytesRead, fileName); + + Message msg; + if (msg.Unflatten(buf, numBytesRead) == B_NO_ERROR) + { + printf("Message is:\n"); + msg.PrintToStream(); + + MessageRef infMsg = InflateMessage(MessageRef(&msg, false)); + if (infMsg()) + { + printf("Inflated Message is:\n"); + infMsg()->PrintToStream(); + } + } + else + { + printf("Error unflattening message! (%i bytes read)\n", numBytesRead); + retVal = 10; + } + fclose(fpIn); + delete [] buf; + } + else + { + printf("Could not read input flattened-message file [%s]\n", fileName); + retVal = 10; + } + + return retVal; +} diff --git a/test/serialproxy.cpp b/test/serialproxy.cpp new file mode 100644 index 00000000..c6477d8c --- /dev/null +++ b/test/serialproxy.cpp @@ -0,0 +1,186 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "dataio/StdinDataIO.h" +#include "dataio/TCPSocketDataIO.h" +#include "dataio/RS232DataIO.h" +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/MiscUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +static const int DEFAULT_PORT = 5274; // What CueStation 2.5 connects to by deafult + +static status_t ReadIncomingData(const char * desc, DataIO & readIO, const SocketMultiplexer & multiplexer, Queue & outQ) +{ + if (multiplexer.IsSocketReadyForRead(readIO.GetReadSelectSocket().GetFileDescriptor())) + { + uint8 buf[4096]; + int32 ret = readIO.Read(buf, sizeof(buf)); + if (ret > 0) + { + LogTime(MUSCLE_LOG_TRACE, "Read " INT32_FORMAT_SPEC" bytes from %s:\n", ret, desc); + LogHexBytes(MUSCLE_LOG_TRACE, buf, ret); + + ByteBufferRef toNetworkBuf = GetByteBufferFromPool(ret, buf); + if (toNetworkBuf()) (void) outQ.AddTail(toNetworkBuf); + } + else if (ret < 0) {LogTime(MUSCLE_LOG_ERROR, "Error, readIO.Read() returned %i\n", ret); return B_ERROR;} + } + return B_NO_ERROR; +} + +static status_t WriteOutgoingData(const char * desc, DataIO & writeIO, const SocketMultiplexer & multiplexer, Queue & outQ, uint32 & writeIdx) +{ + if (multiplexer.IsSocketReadyForWrite(writeIO.GetWriteSelectSocket().GetFileDescriptor())) + { + while(outQ.HasItems()) + { + ByteBufferRef & firstBuf = outQ.Head(); + uint32 bufSize = firstBuf()->GetNumBytes(); + if (writeIdx >= bufSize) + { + outQ.RemoveHead(); + writeIdx = 0; + } + else + { + int32 ret = writeIO.Write(firstBuf()->GetBuffer()+writeIdx, firstBuf()->GetNumBytes()-writeIdx); + if (ret > 0) + { + writeIO.FlushOutput(); + LogTime(MUSCLE_LOG_TRACE, "Wrote " INT32_FORMAT_SPEC" bytes to %s:\n", ret, desc); + LogHexBytes(MUSCLE_LOG_TRACE, firstBuf()->GetBuffer()+writeIdx, ret); + writeIdx += ret; + } + else if (ret < 0) {LogTime(MUSCLE_LOG_ERROR, "Error, writeIO.Write() returned %i\n", ret); return B_ERROR;} + } + } + } + return B_NO_ERROR; +} + +static status_t DoSession(DataIO & networkIO, DataIO & serialIO) +{ + Queue outgoingSerialData; + Queue outgoingNetworkData; + uint32 serialIndex = 0, networkIndex = 0; + SocketMultiplexer multiplexer; + while(true) + { + int networkReadFD = networkIO.GetReadSelectSocket().GetFileDescriptor(); + int serialReadFD = serialIO.GetReadSelectSocket().GetFileDescriptor(); + int networkWriteFD = networkIO.GetWriteSelectSocket().GetFileDescriptor(); + int serialWriteFD = serialIO.GetWriteSelectSocket().GetFileDescriptor(); + + multiplexer.RegisterSocketForReadReady(networkReadFD); + multiplexer.RegisterSocketForReadReady(serialReadFD); + + if (outgoingNetworkData.HasItems()) multiplexer.RegisterSocketForWriteReady(networkWriteFD); + if (outgoingSerialData.HasItems()) multiplexer.RegisterSocketForWriteReady(serialWriteFD); + + if (multiplexer.WaitForEvents() >= 0) + { + if (ReadIncomingData("network", networkIO, multiplexer, outgoingSerialData) != B_NO_ERROR) return B_NO_ERROR; // tells main() to wait for the next TCP connection + if (ReadIncomingData("serial", serialIO, multiplexer, outgoingNetworkData) != B_NO_ERROR) return B_ERROR; // tells main() to exit + if (WriteOutgoingData("network", networkIO, multiplexer, outgoingNetworkData, networkIndex) != B_NO_ERROR) return B_NO_ERROR; // tells main() to wait for the next TCP connection + if (WriteOutgoingData("serial", serialIO, multiplexer, outgoingSerialData, serialIndex) != B_NO_ERROR) return B_ERROR; // tells main() to exit + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Error, WaitForEvents() failed!\n"); + return B_ERROR; + } + } +} + +static void LogUsage() +{ + Log(MUSCLE_LOG_INFO, "Usage: serialproxy serial=: [port=5274] (send/receive via a serial device, e.g. /dev/ttyS0)\n"); +} + +// This program acts as a proxy to forward serial data to a TCP stream (and back) +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message args; (void) ParseArgs(argc, argv, args); + (void) HandleStandardDaemonArgs(args); + + if (args.HasName("help")) + { + LogUsage(); + return 0; + } + + uint16 port = 0; + { + const String * ps = args.GetStringPointer("port"); + if (ps) port = atoi(ps->Cstr()); + } + if (port == 0) port = DEFAULT_PORT; + + String arg; + if (args.FindString("serial", arg) == B_NO_ERROR) + { + const char * colon = strchr(arg(), ':'); + uint32 baudRate = colon ? atoi(colon+1) : 0; if (baudRate == 0) baudRate = 38400; + String devName = arg.Substring(0, ":"); + Queue devs; + if (RS232DataIO::GetAvailableSerialPortNames(devs) == B_NO_ERROR) + { + String serName; + for (int32 i=devs.GetNumItems()-1; i>=0; i--) + { + if (devs[i] == devName) + { + serName = devs[i]; + break; + } + } + if (serName.HasChars()) + { + RS232DataIO serialIO(devName(), baudRate, false); + if (serialIO.IsPortAvailable()) + { + LogTime(MUSCLE_LOG_INFO, "Using serial port %s (baud rate " UINT32_FORMAT_SPEC")\n", serName(), baudRate); + + ConstSocketRef serverSock = CreateAcceptingSocket(port, 1); + if (serverSock()) + { + // Now we just wait here until a TCP connection comes along on our port... + bool keepGoing = true; + while(keepGoing) + { + LogTime(MUSCLE_LOG_INFO, "Awaiting incoming TCP connection on port %u...\n", port); + ConstSocketRef tcpSock = Accept(serverSock); + if (tcpSock()) + { + LogTime(MUSCLE_LOG_INFO, "Beginning serial proxy session!\n"); + TCPSocketDataIO networkIO(tcpSock, false); + keepGoing = (DoSession(networkIO, serialIO) == B_NO_ERROR); + LogTime(MUSCLE_LOG_INFO, "Serial proxy session ended%s!\n", keepGoing?", awaiting new connection":", aborting!"); + } + } + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Unable to listen on TCP port %u\n", port); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Unable to open serial device %s (baud rate " UINT32_FORMAT_SPEC").\n", serName(), baudRate); + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Serial device %s not found.\n", devName()); + LogTime(MUSCLE_LOG_CRITICALERROR, "Available serial devices are:\n"); + for (uint32 i=0; i +#include "util/Queue.h" +#include "util/String.h" + +using namespace muscle; + +static void FlushAdds(String & s) +{ + if (s.HasChars()) + { + printf("svn add %s\n", s()); + s.Clear(); + } +} + +/** This program takes a list of file paths on stdin and an input directory as an argument, + * and creates a set of SVN commands that will update a SVN repository to contain the files + * and directories listed on stdin. + * The list of files can be created via "tar tf archive.tar", etc. + * + * Note that for new repositories, svn import can do the same job as this utility; probably + * better. But this utility is useful when you need to bulk-upgrade an existing SVN + * repository from a non-SVN archive (e.g. if you are keeping 3rd party code in SVN + * and want to upgrade your SVN repository to the new release) + * + * Note that this script does NOT handle the removal of obsolete files from your SVN + * repository. If you care about that, you'll have to do it by hand. + * + * -Jeremy Friesner (jaf@meyersound.com) + * + */ +int main(int argc, char ** argv) +{ + if (argc < 2) + { + printf("Usage: svncopy input_folder mkdirs; + Queue copies; + + char buf[2048]; + while(fgets(buf, sizeof(buf), stdin)) + { + String next(buf); + next=next.Trim(); + next.Replace("\'", "\\\'"); + if (next.EndsWith("/")) mkdirs.AddTail(next); + else copies.AddTail(next); + } + + for (uint32 i=0; i= MAX_LINE_LENGTH-10) FlushAdds(temp); + temp += String("\"./%1\" ").Arg(next); + } + FlushAdds(temp); + + return 0; +} diff --git a/test/testatheossupport.cpp b/test/testatheossupport.cpp new file mode 100644 index 00000000..3205ea4c --- /dev/null +++ b/test/testatheossupport.cpp @@ -0,0 +1,86 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "message/Message.h" +#include "atheossupport/ConvertMessages.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Operation failed, line " INT32_FORMAT_SPEC"\n", __LINE__); +#define NEGATIVETEST(x) if ((x) == B_NO_ERROR) printf("Operation succeeded when it should not have, line " INT32_FORMAT_SPEC "\n", __LINE__); + +// This program tests the Message <-> Message conversion functions in the atheossupport directory. +int main(void) +{ + CompleteSetupSystem css; + + const int HELO = 1234; + Message msg(HELO); + TEST(msg.AddString("Hi", "there")); + TEST(msg.AddString("Friesner", "Jeremy")); + TEST(msg.AddString("Friesner", "Joanna")); + TEST(msg.AddString("Friesner", "Joellen")); + TEST(msg.AddString("Chicken", "Soup")); + TEST(msg.AddString("Chicken", "Vegetable")); + TEST(msg.AddString("Chicken", "Lips")); + TEST(msg.AddString("Fred", "Flintstone")); + TEST(msg.AddPoint("pointMe", Point(1,2))); + TEST(msg.AddRect("rectMe", Rect(1,2,3,4))); + TEST(msg.AddRect("rectMe", Rect(2,3,4,5))); + TEST(msg.AddData("Data", B_RAW_TYPE, "Keyboard", 9)); + TEST(msg.AddData("Data", B_RAW_TYPE, "BLACKJACK", 2)); + + Message subMsg(2345); + Message deeper(4567); + subMsg.AddMessage("Russian Dolls", deeper); + TEST(msg.AddMessage("TestMessage", subMsg)); + + for (int i=0; i<10; i++) TEST(msg.AddInt8("TestInt8", i)); + for (int i=0; i<12; i++) TEST(msg.AddInt16("TestInt16", i)); + for (int i=0; i<13; i++) TEST(msg.AddInt32("TestInt32", i)); + for (int i=0; i<11; i++) TEST(msg.AddInt64("TestInt64", i)); + for (int i=0; i<5; i++) TEST(msg.AddDouble("TestDouble", i)); + for (int i=0; i<6; i++) TEST(msg.AddFloat("TestFloat", i)); + for (int i=0; i<25; i++) TEST(msg.AddBool("TestBool", i)); + + printf("ORIGINAL MESSAGE:\n"); + msg.PrintToStream(); + + os::Message aMsg; + printf("CONVERTING TO OS::MESSAGE...\n"); + TEST(ConvertToAMessage(msg, aMsg)); +// aMsg.PrintToStream(); + + printf("CONVERTING BACK TO MUSCLEMESSAGE...\n"); + Message mmsg; + TEST(ConvertFromAMessage(aMsg, mmsg)); + mmsg.PrintToStream(); + + Message rSub, rDeep; + if ((mmsg.FindMessage("TestMessage", rSub) == B_NO_ERROR)&& + (rSub.FindMessage("Russian Dolls", rDeep) == B_NO_ERROR)) + { + printf("Nested messages are:\n"); + rSub.PrintToStream(); + rDeep.PrintToStream(); + } + else printf("ERROR RE-READING NESTED MESSAGES!\n"); + + printf("TORTURE TEST!\n"); + int i=1000; + uint32 origSize = mmsg.FlattenedSize(); + while(i--) + { + TEST(ConvertFromAMessage(aMsg, mmsg)); + TEST(ConvertToAMessage(mmsg, aMsg)); + uint32 flatSize = mmsg.FlattenedSize(); + if (flatSize != origSize) printf("ERROR, FLATTENED SIZE CHANGED " UINT32_FORMAT_SPEC" -> " UINT32_FORMAT_SPEC"\n", origSize, flatSize); + uint8 * buf = new uint8[flatSize]; + mmsg.Flatten(buf); + TEST(mmsg.Unflatten(buf, flatSize)); + delete [] buf; + } + return 0; +} diff --git a/test/testbatchguard.cpp b/test/testbatchguard.cpp new file mode 100644 index 00000000..fc70df44 --- /dev/null +++ b/test/testbatchguard.cpp @@ -0,0 +1,181 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "system/SetupSystem.h" +#include "util/BatchOperator.h" + +using namespace muscle; + +class SimpleBatchOperator : public BatchOperator<> +{ +public: + SimpleBatchOperator() {printf("SimpleBatchOperator ctor %p\n", this);} + virtual ~SimpleBatchOperator() {printf("SimpleBatchOperator dtor %p\n", this);} + +protected: + virtual void BatchBegins() {printf("SimpleBatchOperator::BatchBegins %p\n", this);} + virtual void BatchEnds() {printf("SimpleBatchOperator::BatchEnds %p\n", this);} +}; + +class IntBatchOperator : public BatchOperator +{ +public: + IntBatchOperator() {printf("IntBatchOperator ctor %p\n", this);} + virtual ~IntBatchOperator() {printf("IntBatchOperator dtor %p\n", this);} + +protected: + virtual void BatchBegins(const int & i) {printf("IntBatchOperator::BatchBegins %p i=%i\n", this, i);} + virtual void BatchEnds(const int & i) {printf("IntBatchOperator::BatchEnds %p i=%i\n", this, i);} +}; + +class TestArgsA +{ +public: + TestArgsA() : _s(NULL), _i(0) {/* empty */} + TestArgsA(const char * s, int i) : _s(s), _i(i) {/* empty */} + + void PrintToStream() const {printf("TestArgsA: [%s] %i\n", _s, _i);} + +private: + const char * _s; + int _i; +}; + +class TestArgsABatchOperator : public BatchOperator +{ +public: + TestArgsABatchOperator() {printf("TestArgsABatchOperator ctor %p\n", this);} + virtual ~TestArgsABatchOperator() {printf("TestArgsABatchOperator dtor %p\n", this);} + +protected: + virtual void BatchBegins(const TestArgsA & args) {printf("TestArgsABatchOperator::BatchBegins %p args=", this); args.PrintToStream();} + virtual void BatchEnds(const TestArgsA & args) {printf("TestArgsABatchOperator::BatchEnds %p args=", this); args.PrintToStream();} +}; + +class TestArgsB +{ +public: + TestArgsB() : _s(NULL), _i(0) {/* empty */} + TestArgsB(const char * s, int i) : _s(s), _i(i) {/* empty */} + + void PrintToStream() const {printf("TestArgsB: [%s] %i\n", _s, _i);} + +private: + const char * _s; + int _i; +}; + +class TestArgsBBatchOperator : public BatchOperator +{ +public: + TestArgsBBatchOperator() {printf("TestArgsBBatchOperator ctor %p\n", this);} + virtual ~TestArgsBBatchOperator() {printf("TestArgsBBatchOperator dtor %p\n", this);} + +protected: + virtual void BatchBegins(const TestArgsB & args) {printf("TestArgsBBatchOperator::BatchBegins %p args=", this); args.PrintToStream();} + virtual void BatchEnds(const TestArgsB & args) {printf("TestArgsBBatchOperator::BatchEnds %p args=", this); args.PrintToStream();} +}; + +class CombinedBatchOperator : public TestArgsABatchOperator, public TestArgsBBatchOperator +{ +public: + CombinedBatchOperator() {printf("CombinedBatchOperator %p\n", this);} + virtual ~CombinedBatchOperator() {printf("~CombinedBatchOperator %p\n", this);} + + // Why are these necessary? Because C++ is lame, that's why! + using TestArgsABatchOperator::GetBatchGuard; + using TestArgsBBatchOperator::GetBatchGuard; + using TestArgsABatchOperator::BeginOperationBatch; + using TestArgsBBatchOperator::BeginOperationBatch; + using TestArgsABatchOperator::EndOperationBatch; + using TestArgsBBatchOperator::EndOperationBatch; +}; + +// This program tests the relative speeds of various object allocation strategies. +int main(int, char **) +{ + CompleteSetupSystem css; // required! + + printf("\n\nSimpleBatchOperator Test --------------\n"); + { + SimpleBatchOperator bo; + printf("First guard...\n"); + DECLARE_BATCHGUARD(bo); + { + printf(" Second guard...\n"); + DECLARE_BATCHGUARD(bo); + { + printf(" Third guard...\n"); + DECLARE_BATCHGUARD(bo); + printf(" End Third guard...\n"); + } + printf(" End Second guard...\n"); + } + printf("End First guard...\n"); + } + + printf("\n\nIntBatchOperator Test --------------\n"); + + { + IntBatchOperator bo; + printf("First guard...\n"); + DECLARE_BATCHGUARD(bo); + { + printf(" Second guard...\n"); + DECLARE_BATCHGUARD(bo); + { + printf(" Third guard...\n"); + DECLARE_BATCHGUARD(bo); + printf(" End Third guard...\n"); + } + printf(" End Second guard...\n"); + } + printf("End First guard...\n"); + } + + printf("\n\nTestArgsABatchOperator Test --------------\n"); + + { + TestArgsABatchOperator bo; + printf("First guard...\n"); + DECLARE_BATCHGUARD(bo, TestArgsA("Hi", 666)); + { + printf(" Second guard...\n"); + DECLARE_BATCHGUARD(bo, TestArgsA("Bye", 667)); + { + printf(" Third guard...\n"); + DECLARE_BATCHGUARD(bo); + printf(" End Third guard...\n"); + } + printf(" End Second guard...\n"); + } + printf("End First guard...\n"); + } + + printf("\n\nCombinedBatchOperator Test --------------\n"); + + { + CombinedBatchOperator bo; + printf("First guard (TestArgsA)...\n"); + + printf(" Manual begin (TestArgsA)...\n"); + bo.BeginOperationBatch(TestArgsA("xxx", 123)); + + DECLARE_BATCHGUARD(bo, TestArgsA("Hi", 666)); + { + printf(" Second guard (TestArgsA)...\n"); + DECLARE_BATCHGUARD(bo, TestArgsA("Bye", 667)); + { + printf(" Third guard (TestArgsB)...\n"); + DECLARE_BATCHGUARD(bo, TestArgsB("CCC", 999)); + + printf(" Manual end (TestArgsA)...\n"); + bo.EndOperationBatch(TestArgsA("xxx", 123)); + + printf(" End Third guard (TestArgsB)...\n"); + } + printf(" End Second guard... (TestArgsA)\n"); + } + printf("End First guard... (TestArgsA)\n"); + } + return 0; +} diff --git a/test/testbesupport.cpp b/test/testbesupport.cpp new file mode 100644 index 00000000..8f343b87 --- /dev/null +++ b/test/testbesupport.cpp @@ -0,0 +1,82 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "besupport/ConvertMessages.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Operation failed, line " INT32_FORMAT_SPEC"\n", __LINE__); +#define NEGATIVETEST(x) if ((x) == B_NO_ERROR) printf("Operation succeeded when it should not have, line " INT32_FORMAT_SPEC"\n", __LINE__); + +// This program tests the Message <-> BMessage conversion functions in the besupport directory. +int main(void) +{ + CompleteSetupSystem css; + + Message msg('HELO'); + TEST(msg.AddString("Hi", "there")); + TEST(msg.AddString("Friesner", "Jeremy")); + TEST(msg.AddString("Friesner", "Joanna")); + TEST(msg.AddString("Friesner", "Joellen")); + TEST(msg.AddString("Chicken", "Soup")); + TEST(msg.AddString("Chicken", "Vegetable")); + TEST(msg.AddString("Chicken", "Lips")); + TEST(msg.AddString("Fred", "Flintstone")); + TEST(msg.AddPoint("pointMe", Point(1,2))); + TEST(msg.AddRect("rectMe", Rect(1,2,3,4))); + TEST(msg.AddRect("rectMe", Rect(2,3,4,5))); + TEST(msg.AddData("Data", B_RAW_TYPE, "Keyboard", 9)); + TEST(msg.AddData("Data", B_RAW_TYPE, "BLACKJACK", 2)); + + Message subMsg('SUBm'); + Message deeper('Deep'); + subMsg.AddMessage("Russian Dolls", deeper); + TEST(msg.AddMessage("TestMessage", subMsg)); + + for (int i=0; i<10; i++) TEST(msg.AddInt8("TestInt8", i)); + for (int i=0; i<12; i++) TEST(msg.AddInt16("TestInt16", i)); + for (int i=0; i<13; i++) TEST(msg.AddInt32("TestInt32", i)); + for (int i=0; i<11; i++) TEST(msg.AddInt64("TestInt64", i)); + for (int i=0; i<5; i++) TEST(msg.AddDouble("TestDouble", i)); + for (int i=0; i<6; i++) TEST(msg.AddFloat("TestFloat", i)); + for (int i=0; i<25; i++) TEST(msg.AddBool("TestBool", i)); + + printf("ORIGINAL MESSAGE:\n"); + msg.PrintToStream(); + + BMessage bMsg; + printf("CONVERTING TO BMESSAGE...\n"); + TEST(ConvertToBMessage(msg, bMsg)); + bMsg.PrintToStream(); + + printf("CONVERTING BACK TO MUSCLEMESSAGE...\n"); + Message mmsg; + TEST(ConvertFromBMessage(bMsg, mmsg)); + mmsg.PrintToStream(); + + Message rSub, rDeep; + if ((mmsg.FindMessage("TestMessage", rSub) == B_NO_ERROR)&& + (rSub.FindMessage("Russian Dolls", rDeep) == B_NO_ERROR)) + { + printf("Nested messages are:\n"); + rSub.PrintToStream(); + rDeep.PrintToStream(); + } + else printf("ERROR RE-READING NESTED MESSAGES!\n"); + + printf("TORTURE TEST!\n"); + int i=1000; + uint32 origSize = mmsg.FlattenedSize(); + while(i--) + { + TEST(ConvertFromBMessage(bMsg, mmsg)); + TEST(ConvertToBMessage(mmsg, bMsg)); + uint32 flatSize = mmsg.FlattenedSize(); + if (flatSize != origSize) printf("ERROR, FLATTENED SIZE CHANGED " UINT32_FORMAT_SPEC" -> " UINT32_FORMAT_SPEC"\n", origSize, flatSize); + uint8 * buf = new uint8[flatSize]; + mmsg.Flatten(buf); + TEST(mmsg.Unflatten(buf, flatSize)); + delete [] buf; + } + return 0; +} diff --git a/test/testbytebuffer.cpp b/test/testbytebuffer.cpp new file mode 100644 index 00000000..564963c1 --- /dev/null +++ b/test/testbytebuffer.cpp @@ -0,0 +1,80 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "util/MiscUtilityFunctions.h" +#include "util/ByteBuffer.h" + +using namespace muscle; + +static void Test(uint32 dataFlags) +{ + ByteBuffer b; + b.SetDataFlags(dataFlags); + printf("Test dataFlags=" XINT32_FORMAT_SPEC" ----- sizeof(ByteBuffer)=%i, endian-swap is %s\n", dataFlags, (int) sizeof(b), b.IsEndianSwapEnabled()?"Enabled":"Disabled"); + { + b.AppendInt8(0x01); + b.AppendInt16(0x0405); + b.AppendInt32(0x0708090a); + b.AppendInt64(0x1122334455667788LL); + b.AppendFloat(3.14159f); + b.AppendDouble(6.28); + b.AppendString("Howdy"); + b.AppendString("Pardner"); + b.AppendPoint(Point(-1.1f, -2.2f)); + b.AppendRect(Rect(10.1f, 20.2f, 30.3f, 40.4f)); + b.AppendString("----"); + const int8 i8s[] = {1,2,3,4}; b.AppendInt8s(i8s, ARRAYITEMS(i8s)); + const int16 i16s[] = {5,6,7,8}; b.AppendInt16s(i16s, ARRAYITEMS(i16s)); + const int32 i32s[] = {9,10,11,12}; b.AppendInt32s(i32s, ARRAYITEMS(i32s)); + const int64 i64s[] = {13,14,15,16}; b.AppendInt64s(i64s, ARRAYITEMS(i64s)); + const float ifls[] = {17.9,18.9,19.9,20.9}; b.AppendFloats(ifls, ARRAYITEMS(ifls)); + const double idbs[] = {21.9,22.9,23.9,24.9}; b.AppendDoubles(idbs, ARRAYITEMS(idbs)); + const String strs[] = {"25", "26", "27", "28"}; b.AppendStrings(strs, ARRAYITEMS(strs)); + const Point pts[] = {Point(29,30),Point(31,32),Point(32,33),Point(33,34)}; b.AppendPoints(pts, ARRAYITEMS(pts)); + const Rect rcs[] = {Rect(35,36,37,38),Rect(39,40,41,42)}; b.AppendRects(rcs, ARRAYITEMS(rcs)); + } + + PrintHexBytes(b); + + uint32 offset = 0, nr; + printf("int8=0x%x\n", b.ReadInt8(offset)); + printf("int16=0x%x\n", b.ReadInt16(offset)); + printf("int32=0x" XINT32_FORMAT_SPEC"\n", b.ReadInt32(offset)); + printf("int64=0x" XINT64_FORMAT_SPEC"\n", b.ReadInt64(offset)); + printf("float=%f\n", b.ReadFloat(offset)); + printf("double=%f\n", b.ReadDouble(offset)); + printf("string1=[%s]\n", b.ReadString(offset)()); + printf("string2=[%s]\n", b.ReadString(offset)()); + Point p = b.ReadPoint(offset); printf("Point=%f,%f\n", p.x(), p.y()); + Rect r = b.ReadRect(offset); printf("Rect=%f,%f,%f,%f\n", r.left(), r.top(), r.Width(), r.Height()); + printf("string3=[%s]\n", b.ReadString(offset)()); // should be "----" + int8 i8s[4] = {0}; nr = b.ReadInt8s(i8s, ARRAYITEMS(i8s), offset); + printf("i8s="); for (uint32 i=0; i +#include +#include +#include + +#include "dataio/ChildProcessDataIO.h" +#include "dataio/StdinDataIO.h" +#include "iogateway/PlainTextMessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +static void PrintUsageAndExit() +{ + LogTime(MUSCLE_LOG_INFO, "Usage: ./testchildprocess [args]\n"); + exit(10); +} + +// This program is equivalent to the portableplaintext client, except +// that we communicate with a child process instead of a socket. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + if (argc < 3) PrintUsageAndExit(); + + uint32 numProcesses = atol(argv[1]); + if (numProcesses == 0) PrintUsageAndExit(); + + const char * cmd = argv[2]; + + Queue refs; + for (uint32 i=0; iLaunchChildProcess(argc-2, ((const char **) argv)+2) == B_NO_ERROR) ? dio->GetReadSelectSocket() : ConstSocketRef(); + printf("Finished Launching child process #" UINT32_FORMAT_SPEC": [%s]\n", i+1, cmd); fflush(stdout); + if (s() == NULL) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Error launching child process #" UINT32_FORMAT_SPEC" [%s]!\n", i+1, cmd); + return 10; + } + } + + StdinDataIO stdinIO(false); + PlainTextMessageIOGateway stdinGateway; + QueueGatewayMessageReceiver stdinInputQueue; + stdinGateway.SetDataIO(DataIORef(&stdinIO, false)); + + SocketMultiplexer multiplexer; + + for (uint32 i=0; iGetReadSelectSocket(); + QueueGatewayMessageReceiver ioInputQueue; + while(1) + { + int readFD = readSock.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(readFD); + + int writeFD = ioGateway.HasBytesToOutput() ? refs[i]()->GetWriteSelectSocket().GetFileDescriptor() : -1; + if (writeFD >= 0) multiplexer.RegisterSocketForWriteReady(writeFD); + int stdinFD = stdinIO.GetReadSelectSocket().GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(stdinFD); + if (multiplexer.WaitForEvents() < 0) printf("testchildprocess: WaitForEvents() failed!\n"); + + // First, deliver any lines of text from stdin to the child process + if ((multiplexer.IsSocketReadyForRead(stdinFD))&&(stdinGateway.DoInput(ioGateway) < 0)) + { + printf("Error reading from stdin, aborting!\n"); + break; + } + + bool reading = multiplexer.IsSocketReadyForRead(readFD); + bool writing = ((writeFD >= 0)&&(multiplexer.IsSocketReadyForWrite(writeFD))); + bool writeError = ((writing)&&(ioGateway.DoOutput() < 0)); + bool readError = ((reading)&&(ioGateway.DoInput(ioInputQueue) < 0)); + if ((readError)||(writeError)) + { + printf("Connection closed, exiting.\n"); + break; + } + + MessageRef incoming; + while(ioInputQueue.RemoveHead(incoming) == B_NO_ERROR) + { + printf("Heard message from server:-----------------------------------\n"); + const char * inStr; + for (int i=0; (incoming()->FindString(PR_NAME_TEXT_LINE, i, &inStr) == B_NO_ERROR); i++) printf("Line %i: [%s]\n", i, inStr); + + printf("-------------------------------------------------------------\n"); + } + + if ((reading == false)&&(writing == false)) break; + + multiplexer.RegisterSocketForReadReady(readFD); + if (ioGateway.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(writeFD); + } + + if (ioGateway.HasBytesToOutput()) + { + printf("Waiting for all pending messages to be sent...\n"); + while((ioGateway.HasBytesToOutput())&&(ioGateway.DoOutput() >= 0)) {printf ("."); fflush(stdout);} + } + } + printf("\n\nBye!\n"); + return 0; +} diff --git a/test/testendian.cpp b/test/testendian.cpp new file mode 100644 index 00000000..e5f5f99e --- /dev/null +++ b/test/testendian.cpp @@ -0,0 +1,391 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "support/MuscleSupport.h" +#include "util/MiscUtilityFunctions.h" + +using namespace muscle; + +#define TEST(x) if (!(x)) printf("Test failed, line %i\n",__LINE__) + +static const uint32 ARRAYLEN=640000; + +static int16 * origArray16; +static int32 * origArray32; +static int64 * origArray64; +static float * origArrayFloat; +static double * origArrayDouble; + +static int16 * bigArray16; +static int32 * bigArray32; +static int64 * bigArray64; +static float * bigArrayFloat; +static double * bigArrayDouble; + +static void PrintBytes(const void * b, int num) +{ + const uint8 * b8 = (const uint8 *) b; + for (int i=0; i +#include "syslog/SysLog.h" +#include "util/String.h" +#include "util/FilePathInfo.h" + +using namespace muscle; + +static const char * GetBoolString(bool b) {return b ? "YES" : "NO";} + +// This program exercises the FilePathInfo class. +int main(int argc, char ** argv) +{ + if (argc < 2) + { + printf("Usage: testfilepathinfo \n"); + return 10; + } + + FilePathInfo fpi(argv[1]); + + printf("FilePathInfo for [%s]:\n", argv[1]); + printf("Exists:\t\t\t%s\n", GetBoolString(fpi.Exists())); + printf("IsRegularFile:\t\t%s\n", GetBoolString(fpi.IsRegularFile())); + printf("IsDirectory:\t\t%s\n", GetBoolString(fpi.IsDirectory())); + printf("IsSymLink:\t\t%s\n", GetBoolString(fpi.IsSymLink())); + printf("FileSize (bytes):\t" UINT64_FORMAT_SPEC "\n", fpi.GetFileSize()); + printf("AccessTime:\t\t" UINT64_FORMAT_SPEC " (%s)\n", fpi.GetAccessTime(), GetHumanReadableTimeString(fpi.GetAccessTime())()); + printf("ModificationTime:\t" UINT64_FORMAT_SPEC " (%s)\n", fpi.GetModificationTime(), GetHumanReadableTimeString(fpi.GetModificationTime())()); + printf("CreationTime:\t\t" UINT64_FORMAT_SPEC " (%s)\n", fpi.GetCreationTime(), GetHumanReadableTimeString(fpi.GetCreationTime())()); + return 0; +} diff --git a/test/testgateway.cpp b/test/testgateway.cpp new file mode 100644 index 00000000..8be38ea8 --- /dev/null +++ b/test/testgateway.cpp @@ -0,0 +1,88 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "iogateway/MessageIOGateway.h" +#include "dataio/FileDataIO.h" +#include "system/SetupSystem.h" +#include "zlib/ZLibDataIO.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) +#define TESTSIZE(x) if ((x) < 0) printf("Test failed, line %i\n",__LINE__) + +DataIORef GetFileRef(FILE * file) +{ + return DataIORef(new ZLibDataIO(DataIORef(new FileDataIO(file)))); // enable transparent file compression! +// return DataIORef(new FileDataIO(file)); +} + +// This program tests the functionality of the MessageIOGateway by writing a Message +// out to a file, then reading it back in. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + QueueGatewayMessageReceiver inQueue; + if (argc == 1) + { + FILE * f = fopen("test.dat", "wb"); + if (f) + { + printf("Outputting test messages to test.dat...\n"); + MessageIOGateway g; + g.SetDataIO(GetFileRef(f)); + for (int i=0; i<100; i++) + { + MessageRef m = GetMessageFromPool(MAKETYPE("TeSt")); + TEST(m()->AddString("Jo", "Mama")); + TEST(m()->AddInt32("Age", 90+i)); + TEST(m()->AddBool("Ugly", (i%2)!=0)); + TEST(g.AddOutgoingMessage(m)); + } + while(g.HasBytesToOutput()) TESTSIZE(g.DoOutput()); + //g.GetDataIO()()->FlushOutput(); + printf("Done Writing!\n"); + } + else printf("Error, could not open test file!\n"); + + // Now try to read it back in + FILE * in = fopen("test.dat", "rb"); + if (in) + { + printf("Reading test messages from test.dat...\n"); + MessageIOGateway g; + g.SetDataIO(GetFileRef(in)); + while(g.DoInput(inQueue) >= 0) + { + MessageRef msgRef; + while(inQueue.RemoveHead(msgRef) == B_NO_ERROR) msgRef()->PrintToStream(); + } + + printf("Done Reading!\n"); + } + else printf("Error, could not re-open test file!\n"); + } + else if (argc > 1) + { + for (int i=1; i= 0) + { + printf("Read " UINT32_FORMAT_SPEC" bytes...\n", readBytes); + MessageRef msgRef; + while(inQueue.RemoveHead(msgRef) == B_NO_ERROR) msgRef()->PrintToStream(); + } + printf("Done Reading file [%s]!\n", argv[i]); + } + else printf("Error, could not open file [%s]!\n", argv[i]); + } + } + return 0; +} diff --git a/test/testhashtable.cpp b/test/testhashtable.cpp new file mode 100644 index 00000000..3e52ef40 --- /dev/null +++ b/test/testhashtable.cpp @@ -0,0 +1,750 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include + +#include "message/Message.h" +#include "system/SetupSystem.h" +#include "system/Thread.h" +#include "util/Hashtable.h" +#include "util/MiscUtilityFunctions.h" +#include "util/String.h" +#include "util/StringTokenizer.h" +#include "util/TimeUtilityFunctions.h" + +using namespace muscle; + +int _state = 0; + +static void bomb(const char * fmt, ...); +void bomb(const char * fmt, ...) +{ + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + LogTime(MUSCLE_LOG_CRITICALERROR, "EXITING DUE TO ERROR (state = %i)!\n", _state); + ExitWithoutCleanup(10); +} + +// This class tests the thread-safety of multiple Threads iterating over the fields of a Message simultaneously. +class TestThread : public Thread +{ +public: + TestThread(const MessageRef & msg) : _msg(msg) {/* empty */} + +protected: + virtual void InternalThreadEntry() + { + uint32 totalCount = 0; + uint64 endTime = GetRunTime64()+SecondsToMicros(10); + while(GetRunTime64()AddInt32(String("field-%1").Arg(i), i); + + printf("BEGIN THREAD-SAFETY TEST!\n"); + + const int NUM_THREADS=30; + Thread * threads[NUM_THREADS]; + for (uint32 i=0; iStartInternalThread() != B_NO_ERROR) printf("Error starting thread!\n"); + } + for (uint32 i=0; iWaitForInternalThreadToExit(); + delete threads[i]; + } + + printf("END THREAD-SAFETY TEST!\n"); + + return 0; +} + +static int DoInteractiveTest(); +static int DoInteractiveTest() +{ + OrderedKeysHashtable table; + + // Prepopulate the table, for convenience + for (int i=9; i>=0; i--) + { + char buf[32]; + sprintf(buf, "%i", i); + table.Put(i, buf); + } + + // Let's see if this reverses it + for (HashtableIterator rev(table); rev.HasData(); rev++) table.MoveToFront(rev.GetKey()); + + while(true) + { + { + bool first = true; + printf("\nCurrent contents: "); + for (HashtableIterator iter(table); iter.HasData(); iter++) + { + const String & nextValue = iter.GetValue(); + MASSERT((atoi(nextValue()) == iter.GetKey()), "value/key mismatch A!\n"); + printf("%s%i", first?"":", ", iter.GetKey()); + first = false; + } + printf(" (size=" UINT32_FORMAT_SPEC ")\nEnter command: ", table.GetNumItems()); fflush(stdout); + } + + char buf[512]; + if (fgets(buf, sizeof(buf), stdin)) + { + StringTokenizer tok(buf, " "); + const char * arg0 = tok(); + const char * arg1 = tok(); + const char * arg2 = tok(); + + char command = arg0 ? arg0[0] : '\0'; + + // For extra fun, let's put a half-way-done iterator here to see what happens + printf("Concurrent: "); + HashtableIterator iter(table); + bool first = true; + if (table.HasItems()) + { + int32 offset = table.GetNumItems()/2; + for (int i=offset-1; i>=0; i--) + { + MASSERT(iter.HasData(), "Not enough keys in table!?!?\n"); + MASSERT((atoi(iter.GetValue()()) == iter.GetKey()), "value/key mismatch B!\n"); + printf("%s%i", first?"":", ", iter.GetKey()); + first = false; + iter++; + } + } + + switch(command) + { + case 'F': + if ((arg1)&&(arg2)) + { + printf("%s(%s %i before %i)", first?"":", ", (table.MoveToBefore(atoi(arg1),atoi(arg2)) == B_NO_ERROR) ? "Befored" : "FailedToBefored", atoi(arg1), atoi(arg2)); + first = false; + } + else printf("(No arg1 or arg2!)"); + break; + + case 'B': + if ((arg1)&&(arg2)) + { + printf("%s(%s %i behind %i)", first?"":", ", (table.MoveToBehind(atoi(arg1),atoi(arg2)) == B_NO_ERROR) ? "Behinded" : "FailedToBehind", atoi(arg1), atoi(arg2)); + first = false; + } + else printf("(No arg1 or arg2!)"); + break; + + case 'm': + if ((arg1)&&(arg2)) + { + printf("%s(%s %i to position %i)", first?"":", ", (table.MoveToPosition(atoi(arg1),atoi(arg2)) == B_NO_ERROR) ? "Positioned" : "FailedToPosition", atoi(arg1), atoi(arg2)); + first = false; + } + else printf("(No arg1 or arg2!)"); + break; + + case 'f': + if (arg1) + { + printf("%s(%s %i)", first?"":", ", (table.MoveToFront(atoi(arg1)) == B_NO_ERROR) ? "Fronted" : "FailedToFront", atoi(arg1)); + first = false; + } + else printf("(No arg1!)"); + break; + + case 'b': + if (arg1) + { + printf("%s(%s %i)", first?"":", ", (table.MoveToBack(atoi(arg1)) == B_NO_ERROR) ? "Backed" : "FailedToBack", atoi(arg1)); + first = false; + } + else printf("(No arg1!)"); + break; + + case 'p': + if (arg1) + { + printf("%s(%s %i)", first?"":", ", (table.Put(atoi(arg1), arg1) == B_NO_ERROR) ? "Put" : "FailedToPut", atoi(arg1)); + first = false; + } + else printf("(No arg1!)"); + break; + + case 'r': + if (arg1) + { + printf("%s(%s %i)", first?"":", ", (table.Remove(atoi(arg1)) == B_NO_ERROR) ? "Removed" : "FailedToRemove", atoi(arg1)); + first = false; + } + else printf("(No arg1!)"); + break; + + case 'c': + printf("%s(Clearing table)", first?"":", "); + table.Clear(); + first = false; + break; + + case 's': + printf("%s(Sorting table)", first?"":", "); + table.SortByKey(); + first = false; + break; + + case 'q': + printf("%sQuitting\n", first?"":", "); + return 0; + break; + + default: + printf("%s(Unknown command)", first?"":", "); + break; + } + + while(iter.HasData()) + { + MASSERT((atoi(iter.GetValue()()) == iter.GetKey()), "value/key mismatch C!\n"); + printf("%s%i", first?"":", ", iter.GetKey()); + first = false; + iter++; + } + printf("\n"); + } + else return 0; + } +} + +static void CheckTable(Hashtable & table, uint32 numItems, bool backwards) +{ + uint32 count = 0; + int last = backwards ? RAND_MAX : 0; + for (HashtableIterator iter(table,backwards?HTIT_FLAG_BACKWARDS:0); iter.HasData(); iter++) + { + if (backwards ? (last < iter.GetKey()) : (last > iter.GetKey())) printf("ERROR! Sort out of order in %s traversal!!!!\n", backwards?"backwards":"forwards"); + last = iter.GetKey(); + count++; + } + if (count != numItems) printf("ERROR! Count is different! " UINT32_FORMAT_SPEC " vs " UINT32_FORMAT_SPEC "\n", count, numItems); +} + +static void TestIteratorSanityOnRemoval(bool backwards) +{ + LogTime(MUSCLE_LOG_INFO, "Testing iterator sanity (direction=%s)\n", backwards?"backwards":"forwards"); + + static const uint32 COUNT = 100; + for (uint32 i=0; i table; + for (uint32 j=0; j iter(table, backwards?HTIT_FLAG_BACKWARDS:0); iter.HasData(); iter++) + { + int32 expectedKey = (backwards?(prevKey-1):(prevKey+1)); + int32 gotKey = iter.GetKey(); + int32 gotValue = iter.GetValue(); + + LogTime(MUSCLE_LOG_TRACE, " Iter returned " INT32_FORMAT_SPEC" -> " INT32_FORMAT_SPEC"\n", iter.GetKey(), iter.GetValue()); + if (gotKey != expectedKey) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Expected key=" INT32_FORMAT_SPEC", got key=" INT32_FORMAT_SPEC" (value=" UINT32_FORMAT_SPEC")\n", expectedKey, gotKey, gotValue); + ExitWithoutCleanup(10); + } + + if ((gotKey%(i+1))==0) {LogTime(MUSCLE_LOG_TRACE, " -> Deleting key=" INT32_FORMAT_SPEC"\n", gotKey); table.Remove(gotKey);} + + numPairsFound++; + prevKey = gotKey; + } + if (numPairsFound != COUNT) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Expected to iterate across " UINT32_FORMAT_SPEC" pairs, only saw " UINT32_FORMAT_SPEC"!\n", COUNT, numPairsFound); + ExitWithoutCleanup(10); + } + } +} + +template void TestMuscleSwap(const char * desc) +{ + T m1, m2; + m1.Put("m1", "m1"); + m2.Put("m2", "m2"); + + char buf[256]; + sprintf(buf, "Before muscleSwap[%s] test", desc); + PrintAndClearStringCopyCounts(buf); + + muscleSwap(m1, m2); + + sprintf(buf, "After muscleSwap[%s] test", desc); + PrintAndClearStringCopyCounts(buf); + + if ((m1.GetWithDefault("m2") != "m2")||(m2.GetWithDefault("m1") != "m1")||(m1.GetNumItems() != 1)||(m2.GetNumItems() != 1)) + { + printf("Oh no, muscleSwap is broken for %s objects!\n", desc); + exit(10); + } + + printf("muscleSwap() test for [%s] passed!\n", desc); +} + +static void AddTally(Hashtable & tallies, const char * verb, uint64 startTime, uint32 numItems) +{ + uint64 elapsed = (GetRunTime64()-startTime); + double itemsPerSecond = ((double)numItems*((double)MICROS_PER_SECOND))/elapsed; + printf(" It took " UINT64_FORMAT_SPEC" microseconds to %s " UINT32_FORMAT_SPEC" items, so we %s %.0f items per second\n", elapsed, verb, numItems, verb, itemsPerSecond); + *(tallies.GetOrPut(verb)) += itemsPerSecond; +} + + +// This program exercises the Hashtable class. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message temp; if (ParseArgs(argc, argv, temp) == B_NO_ERROR) HandleStandardDaemonArgs(temp); + + if (temp.HasName("inter")) return DoInteractiveTest(); + + // Test C++11 move semantics to make sure they aren't stealing + { + String key = "key"; + String value = "value"; + Hashtable table; + table.Put(key, value); + if (key != "key") {printf("ERROR, Hashtable stole my key!\n"); exit(10);} + if (value != "value") {printf("ERROR, Hashtable stole my value!\n"); exit(10);} + } + + // Test muscleSwap() + TestMuscleSwap >("Hashtable"); + TestMuscleSwap >("OrderedKeysHashtable"); + TestMuscleSwap >("OrderedValuesHashtable"); + + // Test iterator behaviour when deleting keys + TestIteratorSanityOnRemoval(false); + TestIteratorSanityOnRemoval(true); + + { + LogTime(MUSCLE_LOG_INFO, "Testing a keys-only Hashtable value...\n"); + + Hashtable keysOnly; + printf("sizeof(keysOnly)=%u\n", (unsigned int) sizeof(keysOnly)); + keysOnly.PutWithDefault(1); + keysOnly.PutWithDefault(2); + keysOnly.PutWithDefault(5); + keysOnly.PutWithDefault(10); + for (HashtableIterator iter(keysOnly); iter.HasData(); iter++) printf("key=%i\n", iter.GetKey()); + } + + { + LogTime(MUSCLE_LOG_INFO, "Testing Tuple as a Hashtable key...\n"); + + // A quick test of the Tuple class as a Hashtable key + typedef Tuple<2,int> MyType; + Hashtable tupleTable; + + MyType a; a[0] = 5; a[1] = 6; + MyType b; b[0] = 7; b[1] = 8; + tupleTable.Put(a, 1); + tupleTable.Put(b, 2); + for (HashtableIterator iter(tupleTable); iter.HasData(); iter++) + { + const MyType & key = iter.GetKey(); + printf("key=%i,%i val=%i\n", key[0], key[1], iter.GetValue()); + } + int * ra = tupleTable.Get(a); + int * rb = tupleTable.Get(b); + printf("tuple: ra=[%i] rb=[%i]\n", ra?*ra:666, rb?*rb:666); + } + + { + LogTime(MUSCLE_LOG_INFO, "Testing Rect as a Hashtable key...\n"); + + // A quick test of the Tuple class as a Hashtable key + Hashtable tupleTable; + + Rect a(1,2,3,4); + Rect b(5,6,7,8); + tupleTable.Put(a, 1); + tupleTable.Put(b, 2); + for (HashtableIterator iter(tupleTable); iter.HasData(); iter++) + { + const Rect & key = iter.GetKey(); + printf("key=%f,%f,%f,%f val=%i\n", key.left(), key.top(), key.right(), key.bottom(), iter.GetValue()); + } + int * ra = tupleTable.Get(a); + int * rb = tupleTable.Get(b); + printf("Rect: ra=[%p] rb=[%p]\n", ra, rb); + } + + { + LogTime(MUSCLE_LOG_INFO, "Testing Point as a Hashtable key...\n"); + + // A quick test of the Tuple class as a Hashtable key + Hashtable tupleTable; + + Point a(9,10); + Point b(-11,-12); + tupleTable.Put(a, 1); + tupleTable.Put(b, 2); + for (HashtableIterator iter(tupleTable); iter.HasData(); iter++) + { + const Point & key = iter.GetKey(); + printf("key=%f,%f val=%i\n", key.x(), key.y(), iter.GetValue()); + } + int * ra = tupleTable.Get(a); + int * rb = tupleTable.Get(b); + printf("Point: ra=[%p] rb=[%p]\n", ra, rb); + } + + { + LogTime(MUSCLE_LOG_INFO, "Preparing large table for sort...\n"); + + const uint32 numItems = 100000; + Hashtable table; (void) table.EnsureSize(100000); + for (uint32 i=0; i table; + for (uint32 i=0; i table; + { + table.Put("Hello", "World"); + table.Put("Peanut Butter", "Jelly"); + table.Put("Ham", "Eggs"); + table.Put("Pork", "Beans"); + table.Put("Slash", "Dot"); + table.Put("Data", "Mining"); + table.Put("TestDouble", "Play"); + table.Put("Abbot", "Costello"); + table.Put("Laurel", "Hardy"); + table.Put("Thick", "Thin"); + table.Put("Butter", "Parkay"); + table.Put("Total", "Carnage"); + table.Put("Summer", "Time"); + table.Put("Terrible", "Twos"); + table.CountAverageLookupComparisons(true); + + printf("table[\"Summer\"] = [%s]\n", table["Summer"]()); + printf("table[\"Butter\"] = [%s]\n", table["Butter"]()); + printf("table[\"Total\"] = [%s]\n", table["Total"]()); + printf("table[\"Winter\"] = [%s] (should be blank!)\n", table["Winter"]()); + + if (table.GetNumItems() != 14) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "String table has %i entries in it, expected 14!\n", table.GetNumItems()); + ExitWithoutCleanup(10); + } + + { + LogTime(MUSCLE_LOG_INFO, "Test partial backwards iteration\n"); + for (HashtableIterator iter(table, "Slash", HTIT_FLAG_BACKWARDS); iter.HasData(); iter++) LogTime(MUSCLE_LOG_INFO,"[%s] <-> [%s]\n", iter.GetKey()(), iter.GetValue()()); + } + + String lookup; + if (table.Get(String("Hello"), lookup) == B_NO_ERROR) LogTime(MUSCLE_LOG_DEBUG, "Hello -> %s\n", lookup()); + else bomb("Lookup 1 failed.\n"); + if (table.Get(String("Peanut Butter"), lookup) == B_NO_ERROR) LogTime(MUSCLE_LOG_DEBUG, "Peanut Butter -> %s\n", lookup()); + else bomb("Lookup 2 failed.\n"); + + + LogTime(MUSCLE_LOG_INFO, "Testing delete-as-you-go traveral\n"); + for (HashtableIterator st(table); st.HasData(); st++) + { + LogTime(MUSCLE_LOG_INFO, "t3 = %s -> %s (tableSize=" UINT32_FORMAT_SPEC")\n", st.GetKey()(), st.GetValue()(), table.GetNumItems()); + if (table.Remove(st.GetKey()) != B_NO_ERROR) bomb("Could not remove string!\n"); +#if 0 + for (HashtableIterator st2(table); st2.HasData(); st2++) printf(" tx = %s -> %s\n", nextKeyString(), nextValueString()); +#endif + } + + Hashtable sillyTable; + sillyTable.Put(15, "Fifteen"); + sillyTable.Put(100, "One Hundred"); + sillyTable.Put(150, "One Hundred and Fifty"); + sillyTable.Put(200, "Two Hundred"); + sillyTable.Put((uint32)-1, "2^32 - 1!"); + if (sillyTable.ContainsKey((uint32)-1) == false) bomb("large value failed!"); + + const char * temp = NULL; + sillyTable.Get(100, temp); + sillyTable.Get(101, temp); // will fail + printf("100 -> %s\n",temp); + + printf("Entries in sillyTable:\n"); + for (HashtableIterator it(sillyTable); it.HasData(); it++) + { + const char * nextValue = NULL; + status_t ret = sillyTable.Get(it.GetKey(), nextValue); + printf("%i %s: " UINT32_FORMAT_SPEC" -> %s\n", it.HasData(), (ret == B_NO_ERROR) ? "OK" : "ERROR", it.GetKey(), nextValue); + } + } + table.Clear(); + + { + const uint32 NUM_ITEMS = 1000000; + const uint32 NUM_RUNS = 3; + Hashtable testCopy; + Hashtable tallies; + for (uint32 t=0; t table; (void) table.EnsureSize(NUM_ITEMS); + printf("SORT SPEED TEST ROUND " UINT32_FORMAT_SPEC"/" UINT32_FORMAT_SPEC":\n", t+1, NUM_RUNS); + + uint64 startTime = GetRunTime64(); + srand(0); for (uint32 i=0; i iter(tallies); iter.HasData(); iter++) printf(" %f items/second for %s\n", iter.GetValue()/NUM_RUNS, iter.GetKey()()); + } + + // Now some timing test with String keys and values, for testing of the C++11 move semantics + PrintAndClearStringCopyCounts("Before String Sort test"); + { + const uint32 NUM_ITEMS = 1000000; + const uint32 NUM_RUNS = 3; + Hashtable testCopy; + Hashtable tallies; + for (uint32 t=0; t table; (void) table.EnsureSize(NUM_ITEMS); + printf("STRING SORT SPEED TEST ROUND " UINT32_FORMAT_SPEC"/" UINT32_FORMAT_SPEC":\n", t+1, NUM_RUNS); + + uint64 startTime = GetRunTime64(); + srand(0); for (uint32 i=0; i iter(tallies); iter.HasData(); iter++) printf(" STRING %f items/second for %s\n", iter.GetValue()/NUM_RUNS, iter.GetKey()()); + } + PrintAndClearStringCopyCounts("After String Sort test"); + + printf("Begin torture test!\n"); + _state = 4; + bool fastClear = false; + { + Hashtable t; + for (uint32 numEntries=1; numEntries < 1000; numEntries++) + { + uint32 half = numEntries/2; + bool ok = true; + + printf(UINT32_FORMAT_SPEC" ", numEntries); fflush(stdout); + _state = 5; + { + for(uint32 i=0; i=0; i--) + { + char temp[300]; + sprintf(temp, UINT32_FORMAT_SPEC, i); + uint32 tv = 0; + if (t.Get(temp, tv) != B_NO_ERROR) bomb("ERROR, MISSING KEY [%s]\n", temp); + if (tv != ((uint32)i)) bomb("ERROR, WRONG KEY %s != " UINT32_FORMAT_SPEC"!\n", temp, tv); + } + } + + //printf("Iterating through table...\n"); + _state = 7; + { + uint32 count = 0; + for (HashtableIterator iter(t); iter.HasData(); iter++) + { + char buf[300]; + sprintf(buf, UINT32_FORMAT_SPEC, count); + if (iter.GetKey() != buf) bomb("ERROR: iteration was wrong, item " UINT32_FORMAT_SPEC" was [%s] not [%s]!\n", count, iter.GetKey()(), buf); + if (iter.GetValue() != count) bomb("ERROR: iteration value was wrong, item " UINT32_FORMAT_SPEC" was " UINT32_FORMAT_SPEC" not " UINT32_FORMAT_SPEC"!i!\n", count, iter.GetValue(), count); + count++; + } + } + + //printf("Removing the second half of the entries...\n"); + _state = 8; + { + for (uint32 i=half; i iter(t); iter.HasData(); iter++) + { + count++; + checkSum += iter.GetValue(); + } + if (count != half) bomb("ERROR: Count mismatch " UINT32_FORMAT_SPEC" vs " UINT32_FORMAT_SPEC"!\n", count, numEntries); + if (checkSum != sum) bomb("ERROR: Sum mismatch " UINT32_FORMAT_SPEC" vs " UINT32_FORMAT_SPEC"!\n", sum, checkSum); + } + } + + //printf("Clearing Table (%s)\n", fastClear ? "Quickly" : "Slowly"); + _state = 10; + if (fastClear) t.Clear(); + else + { + for (uint32 i=0; i paranoia(t); + if (paranoia.HasData()) bomb("ERROR, ITERATOR CONTAINED ITEMS AFTER CLEAR!\n"); + + if (t.HasItems()) bomb("ERROR, SIZE WAS NON-ZERO (" UINT32_FORMAT_SPEC") AFTER CLEAR!\n", t.GetNumItems()); + fastClear = !fastClear; + } + printf("Finished torture test successfully!\n"); + } + +#ifdef MUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + printf("Thread-safe hashtable iterators were disabled at compile time, so I won't test them!\n"); + return 0; +#else + return DoThreadTest(); +#endif +} diff --git a/test/testmatchfiles.cpp b/test/testmatchfiles.cpp new file mode 100644 index 00000000..14964ee9 --- /dev/null +++ b/test/testmatchfiles.cpp @@ -0,0 +1,27 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "regex/FilePathExpander.h" +#include "util/String.h" + +using namespace muscle; + +int main(void) +{ + char buf[1024]; + while(fgets(buf, sizeof(buf), stdin)) + { + String s(buf); s = s.Trim(); + + Queue q; + if (ExpandFilePathWildCards(s, q) == B_NO_ERROR) + { + printf("File path [%s] expanded to " UINT32_FORMAT_SPEC" paths:\n", s(), q.GetNumItems()); + for (uint32 i=0; i + +#include "message/Message.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Operation failed, line %i\n", __LINE__); +#define NEGATIVETEST(x) if ((x) == B_NO_ERROR) printf("Operation succeeded when it should not have, line %i\n", __LINE__); + +void printSep(const char * title); +void printSep(const char * title) +{ + printf("\n----------------- %s -------------------\n", title); +} + +#define COMMAND_HELLO 0x1234 +#define COMMAND_GOODBYE 0x4321 + +// Just a dummy class to test AddFlat()/FindFlat() against +class TestFlatCountable : public FlatCountable +{ +public: + TestFlatCountable() : _val(-1) {/* empty */} + TestFlatCountable(const String & s, int32 val) : _string(s), _val(val) {/* empty */} + + bool operator != (const TestFlatCountable & rhs) const {return ((_val != rhs._val)||(_string != rhs._string));} + + virtual bool IsFixedSize() const {return false;} + virtual uint32 TypeCode() const {return 123456;} + virtual uint32 FlattenedSize() const {return sizeof(_val)+_string.FlattenedSize();} + + virtual void Flatten(uint8 *buffer) const + { + *((int32 *)buffer) = B_HOST_TO_LENDIAN_INT32(_val); buffer += sizeof(_val); + _string.Flatten(buffer); + } + + virtual status_t Unflatten(const uint8 *buf, uint32 size) + { + if (size < sizeof(_val)) return B_ERROR; + _val = B_LENDIAN_TO_HOST_INT32(*((const int32 *)buf)); buf += sizeof(_val); size -= sizeof(_val); + return _string.Unflatten(buf, size); + } + + String ToString() const {return String("TFC: [%1] %2").Arg(_string).Arg(_val);} + +private: + String _string; + int32 _val; +}; +DECLARE_REFTYPES(TestFlatCountable); + +// This program exercises the Message class. +int main(int, char **) +{ + CompleteSetupSystem css; // required! + + // Test muscleSwap() + { + Message m1(1), m2(2); + m1.AddString("blah", "m1"); + m2.AddString("blah", "m2"); + PrintAndClearStringCopyCounts("Before muscleSwap()"); + muscleSwap(m1, m2); + PrintAndClearStringCopyCounts("After muscleSwap()"); + if ((m1.what != 2)||(m2.what != 1)||(m1.GetString("blah") != "m2")||(m2.GetString("blah") != "m1")) + { + printf("Oh no, muscleSwap is broken for Message objects!\n"); + exit(10); + } + } + + Message m1; + m1.AddFloat("va", 1.0f); + printf("m1=" UINT32_FORMAT_SPEC"\n", m1.FlattenedSize()); + m1.AddInt32("co", 32); + printf("m2=" UINT32_FORMAT_SPEC"\n", m1.FlattenedSize()); + + printSep("Testing Replace*() with okayToAdd..."); + Message butter; + butter.ReplaceInt8(true, "int8", 8); + butter.ReplaceInt16(true, "int16", 16); + butter.ReplaceInt32(true, "int32", 32); + butter.ReplaceInt64(true, "int64", 64); + butter.ReplaceFloat(true, "float", 3.14f); + butter.ReplaceDouble(true, "double", 6.28); + butter.ReplacePoint(true, "point", Point(5,4)); + butter.ReplaceRect(true, "rect", Rect(5,6,7,8)); + butter.ReplacePointer(true, "pointer", &butter); + butter.PrintToStream(); + + butter.ReplaceInt16(true, "int16", 0, 17); + butter.ReplaceInt16(true, "int16", 1, 18); + butter.ReplaceInt8(true, "int8", 25, 25); // should work the same as AddInt8("int8", 25); + + butter.AddTag("Tag", RefCountableRef(GetMessageFromPool(6666)())); + butter.AddTag("Tag", RefCountableRef(GetMessageFromPool(7777)())); + butter.AddPointer("pointer", &butter); + butter.PrintToStream(); + + void * t; + if ((butter.FindPointer("pointer", t) != B_NO_ERROR)||(t != &butter)) printf("Error retrieving pointer!\n"); + + printf("(butter==m1) == %i\n", butter == m1); + printf("(butter==butter) == %i\n", butter == butter); + + printSep("Testing Add*()..."); + + Message msg(COMMAND_HELLO); + TEST(msg.AddString("Friesner", "Jeremy")); + TEST(msg.AddString("Friesner", "Joanna")); + TEST(msg.AddString("Friesner", "Joellen")); + TEST(msg.AddString("Chicken", "Soup")); + TEST(msg.AddString("Chicken", "Vegetable")); + TEST(msg.AddString("Chicken", "Lips")); + TEST(msg.AddString("Fred", "Flintstone")); + TEST(msg.AddString("Buddha", "Bark")); + TEST(msg.AddPoint("point12", Point(1,2))); + TEST(msg.AddPoint("point12", Point(2,1))); + TEST(msg.AddRect("rect1234", Rect(1,2,3,4))); + TEST(msg.AddRect("rect2345", Rect(2,3,4,5))); + TEST(msg.AddInt8("int8", 45)); + TEST(msg.AddInt16("int16", 123)); + TEST(msg.AddInt32("int32", 89)); + TEST(msg.AddInt64("int64", 99999)); + TEST(msg.AddFloat("float", 3.14159)); + TEST(msg.AddDouble("double", 6.28)); + TEST(msg.AddDouble("double", 6.66)); + TEST(msg.AddPointer("ptr", &msg)); + TEST(msg.AddPointer("ptr", &butter)); + TEST(msg.AddMessage("msg", butter)); + TEST(msg.AddData("Data", B_RAW_TYPE, "ABCDEFGHIJKLMNOPQRS", 12)); + TEST(msg.AddData("Data", B_RAW_TYPE, "Mouse", 3)); + + printf("Testing the Get*() functions...\n"); + printf("GetCstr(\"Friesner\") =%s\n", msg.GetCstr( "Friesner", "")); + printf("GetCstr(\"Friesner(0)\") =%s\n", msg.GetCstr( "Friesner", "", 0)); + printf("GetCstr(\"Friesner(1)\") =%s\n", msg.GetCstr( "Friesner", "", 1)); + printf("GetCstr(\"Friesner(2)\") =%s\n", msg.GetCstr( "Friesner", "", 2)); + printf("GetCstr(\"Friesner(3)\") =%s\n", msg.GetCstr( "Friesner", "", 3)); + printf("GetString(\"Friesner\") =%s\n", msg.GetString("Friesner", "")()); + printf("GetString(\"Friesner(0)\")=%s\n", msg.GetString("Friesner", "", 0)()); + printf("GetString(\"Friesner(1)\")=%s\n", msg.GetString("Friesner", "", 1)()); + printf("GetString(\"Friesner(2)\")=%s\n", msg.GetString("Friesner", "", 2)()); + printf("GetString(\"Friesner(3)\")=%s\n", msg.GetString("Friesner", "", 3)()); + printf("GetInt8=%i\n", msg.GetInt8("int8")); + printf("GetInt16=%i\n", msg.GetInt16("int16")); + printf("GetInt32=" INT32_FORMAT_SPEC"\n", msg.GetInt32("int32")); + printf("GetInt64=" INT64_FORMAT_SPEC"\n", msg.GetInt64("int64")); + printf("GetInt64_XXX=" INT64_FORMAT_SPEC"\n", msg.GetInt64("not_present")); + printf("GetInt64_666=" INT64_FORMAT_SPEC"\n", msg.GetInt64("not_present", 666)); + printf("GetDouble(0)=%f\n", msg.GetDouble("double", 0, 0)); + printf("GetDouble(1)=%f\n", msg.GetDouble("double", 0, 1)); + printf("GetDouble(2)=%f\n", msg.GetDouble("double", 0, 2)); + printf("GetDouble(3)=%f\n", msg.GetDouble("double", 8888.0, 3)); + printf("GetPointer(0)=%p\n", msg.GetPointer("ptr", NULL, 0)); + printf("GetPointer(1)=%p\n", msg.GetPointer("ptr", NULL, 1)); + MessageRef getButter = msg.GetMessage("msg"); + if (getButter()) printf("GetMessage = [%s]\n", getButter()->ToString()()); + else printf("GetMessage = NULL\n"); + + Message subMessage(1); + TEST(subMessage.AddString("I am a", "sub message!")); + TEST(subMessage.AddInt32("My age is", 32)); + + Message subsubMessage(2); + TEST(subsubMessage.AddBool("Wow, that's deep!", true)); + TEST(subsubMessage.AddMessage("This is actually okay to do!", subsubMessage)); + TEST(subMessage.AddMessage("subsubMessage", subsubMessage)); + + TEST(msg.AddMessage("subMessage", subMessage)); + + {for (int i=0; i<10; i++) TEST(msg.AddInt8("TestInt8", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt16("TestInt16", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt32("TestInt32", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt64("TestInt64", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddDouble("TestDouble", i));} + {for (int i=0; i<10; i++) TEST(msg.AddFloat("TestFloat", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddBool("TestBool", i)); } + + printf("Finished message:\n"); + msg.PrintToStream(); + + printSep("Testing RemoveName, RemoveData, Replace*()..."); + TEST(msg.RemoveData("TestInt8", 5)); + TEST(msg.RemoveName("Buddha")); + TEST(msg.RemoveData("Fred", 0)); + TEST(msg.RemoveData("Friesner", 1)); + NEGATIVETEST(msg.RemoveData("Glorp", 0)); + NEGATIVETEST(msg.RemoveData("Chicken", 5)); + TEST(msg.ReplaceString(false, "Friesner", 0, "Jorge")); + TEST(msg.ReplaceString(false, "Chicken", 1, "Feet")); + TEST(msg.ReplaceString(false, "Chicken", 2, "Breast")); + NEGATIVETEST(msg.ReplaceString(false, "Chicken", 3, "Soul")); + TEST(msg.ReplaceDouble(true, "TestDouble", 2, 222.222)); + TEST(msg.ReplaceFloat(true, "TestFloat", 3, 333.333)); + NEGATIVETEST(msg.ReplaceFloat(false, "RootBeerFloat", 0, 444.444f)); + TEST(msg.ReplaceBool(false, "TestBool", 5)); + TEST(msg.ReplaceRect(false, "rect2345", Rect(2,3,4,5))); + + Message eqMsg = msg; + printf("EQMSG=msg == %i\n", eqMsg==msg); + + printf("Replaced message:\n"); + msg.PrintToStream(); + + printSep("Testing the Find() commands..."); + String strResult; + TEST(msg.FindString("Friesner", strResult)); + printf("Friesner(0) = %s\n", strResult.Cstr()); + const char * res = NULL; + TEST(msg.FindString("Friesner", 1, res)); + printf("Friesner(1) = %s\n", res); + NEGATIVETEST(msg.FindString("Friesner", 2, strResult)); + NEGATIVETEST(msg.FindString("Friesner", 3, strResult)); + + int8 int8Result; + TEST(msg.FindInt8("TestInt8", 5, int8Result)); + printf("TestInt8(5b) = %i\n",int8Result); + TEST(msg.FindInt8("TestInt8", 5, &int8Result)); + printf("TestInt8(5a) = %i\n", int8Result); + + int16 int16Result; + TEST(msg.FindInt16("TestInt16", 4, int16Result)); + printf("TestInt16(4a) = %i\n",int16Result); + TEST(msg.FindInt16("TestInt16", 4, &int16Result)); + printf("TestInt16(4b) = %i\n",int16Result); + + int32 int32Result; + TEST(msg.FindInt32("TestInt32", 4, int32Result)); + printf("TestInt32(4) = " INT32_FORMAT_SPEC"\n",int32Result); + + uint32 uint32Result; + TEST(msg.FindInt32("TestInt32", 4, uint32Result)); + printf("TestUInt32(4) = " INT32_FORMAT_SPEC"\n",uint32Result); + + int64 int64Result; + TEST(msg.FindInt64("TestInt64", 4, int64Result)); + printf("TestInt64(4a) = " INT64_FORMAT_SPEC "\n",int64Result); + TEST(msg.FindInt64("TestInt64", 4, &int64Result)); + printf("TestInt64(4b) = " INT64_FORMAT_SPEC "\n",int64Result); + + uint64 uint64Result; + TEST(msg.FindInt64("TestInt64", 4, uint64Result)); + printf("TestUInt64(4a) = " INT64_FORMAT_SPEC "\n",uint64Result); + TEST(msg.FindInt64("TestInt64", 4, &uint64Result)); + printf("TestUInt64(4b) = " INT64_FORMAT_SPEC "\n",uint64Result); + + float floatResult; + TEST(msg.FindFloat("TestFloat", 4, floatResult)); + printf("TestFloat(4) = %f\n",floatResult); + + double doubleResult; + TEST(msg.FindDouble("TestDouble", 4, doubleResult)); + printf("TestDouble(4) = %f\n",doubleResult); + + Rect rectResult; + TEST(msg.FindRect("rect2345", rectResult)); + printf("TestRect: "); rectResult.PrintToStream(); + + Point pointResult; + TEST(msg.FindPoint("point12", 1, pointResult)); + printf("TestPoint: "); pointResult.PrintToStream(); + + msg.AddTag("ThisShouldn'tBeBackAfterUnflatten", RefCountableRef(NULL)); + + const void * gd; + uint32 getDataSize; + TEST(msg.FindData("Data", B_RAW_TYPE, &gd, &getDataSize)); + String dataStr((const char *) gd, getDataSize); + printf("data=[%s], size=" UINT32_FORMAT_SPEC"\n", dataStr(), getDataSize); + + TEST(msg.FindData("Data", B_RAW_TYPE, 1, &gd, &getDataSize)); + dataStr.SetCstr((const char *) gd, getDataSize); + printf("data(1)=[%s], size=" UINT32_FORMAT_SPEC"\n", dataStr(), getDataSize); + + printSep("Testing misc"); + + printf("There are " UINT32_FORMAT_SPEC" string entries\n", msg.GetNumNames(B_STRING_TYPE)); + msg.PrintToStream(); + Message tryMe = msg; + printf("Msg is " UINT32_FORMAT_SPEC" bytes.\n",msg.FlattenedSize()); + msg.AddTag("anothertag", RefCountableRef(GetMessageFromPool()())); + printf("After adding tag, msg is (hopefully still) " UINT32_FORMAT_SPEC" bytes.\n",msg.FlattenedSize()); + tryMe.PrintToStream(); + + printf("Extracting...\n"); + Message extract; + TEST(tryMe.FindMessage("subMessage", extract)); + printSep("Extracted subMessage!\n"); + extract.PrintToStream(); + + Message subExtract; + TEST(extract.FindMessage("subsubMessage", subExtract)); + printSep("Extracted subsubMessage!\n"); + subExtract.PrintToStream(); + + uint32 flatSize = msg.FlattenedSize(); + printf("FlatSize=" UINT32_FORMAT_SPEC"\n",flatSize); + uint8 * buf = new uint8[flatSize*10]; + {for (uint32 i=flatSize; i [%s]\n", it.GetFieldName()()); + + printf("Testing field name iterator... B_ANY_TYPE\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_ANY_TYPE); it.HasData(); it++) printf("--> [%s]\n", it.GetFieldName()()); + + printf("Testing field name iterator... B_STRING_TYPE\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_STRING_TYPE); it.HasData(); it++) printf("--> [%s]\n", it.GetFieldName()()); + + printf("Testing field name iterator... B_INT8_TYPE\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_INT8_TYPE); it.HasData(); it++) printf("--> [%s]\n", it.GetFieldName()()); + + printf("Testing field name iterator... B_OBJECT_TYPE (should have no results)\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_OBJECT_TYPE); it.HasData(); it++) printf("--> [%s]\n", it.GetFieldName()()); + + // Test the adding and retrieval of FlatCountableRefs + TestFlatCountableRef tfcRef(new TestFlatCountable("Hello", 5)); + if (msg.AddFlat("tfc", FlatCountableRef(tfcRef.GetRefCountableRef(), false)) == B_NO_ERROR) + { + TestFlatCountable tfc2; + if (msg.FindFlat("tfc", tfc2) == B_NO_ERROR) + { + printf("FindFlat() found: [%s]\n", tfc2.ToString()()); + if (tfc2 != *tfcRef()) printf("Error, found TFC [%s] doesn't match original [%s]\n", tfc2.ToString()(), tfcRef()->ToString()()); + } + else printf("Error, FindFlat() by value failed!\n"); + } + + return 0; +} + diff --git a/test/testmicro.cpp b/test/testmicro.cpp new file mode 100644 index 00000000..af46ff19 --- /dev/null +++ b/test/testmicro.cpp @@ -0,0 +1,199 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "message/Message.h" +#include "micromessage/MicroMessage.h" +#include "util/MiscUtilityFunctions.h" // for PrintHexBytes() + +using namespace muscle; + +static void CreateTestMessage(uint32 recurseCount, Message & m, UMessage * um) +{ + const uint32 ITEM_COUNT = 5; + uint32 i; + + // Test every type.... + { + for (i=0; i 0) + { + Message subMsg(i); + uint8 subBuf[16384]; UMessage uSubMsg; UMInitializeToEmptyMessage(&uSubMsg, subBuf, sizeof(subBuf), i); + CreateTestMessage(recurseCount-1, subMsg, &uSubMsg); + if (UMAddMessage(um, "testMessages", uSubMsg) != B_NO_ERROR) printf("UMAddMessage() failed!\n"); + m.AddMessage("testMessages", subMsg); + } + else + { + uint8 subBuf[12]; UMessage uSubMsg; UMInitializeToEmptyMessage(&uSubMsg, subBuf, sizeof(subBuf), i); + if (UMAddMessage(um, "testMessages", uSubMsg) != B_NO_ERROR) printf("Trivial UMAddMessage() failed!\n"); + m.AddMessage("testMessages", Message(i)); + } + } + } + { + // Test of in-line Message adddition + for (i=0; i 0) + { + Message subMsg(i+100); + UMessage uSubMsg = UMInlineAddMessage(um, "inline_Messages", i+100); + if (UMIsMessageReadOnly(&uSubMsg)) printf("Error, UMInlineAddMessage() failed!\n"); + else + { + CreateTestMessage(recurseCount-1, subMsg, &uSubMsg); + m.AddMessage("inline_Messages", subMsg); + } + } + else + { + UMessage uSubMsg = UMInlineAddMessage(um, "inline_Messages", i+1000); + if (UMIsMessageReadOnly(&uSubMsg)) printf("Error, trivial UMInlineAddMessage() failed!\n"); + else m.AddMessage("inline_Messages", Message(i+1000)); + } + } + } + { + for (i=0; iGetBuffer(); + uint32 mFlatSize = bufRef()->GetNumBytes(); + m.Flatten(mPtr); + printf("\n---------------------------------Msg:\n"); + PrintHexBytes(mPtr, mFlatSize); + + if (umFlatSize != mFlatSize) printf("Flattened buffer sizes didn't match! UMessage=" UINT32_FORMAT_SPEC" Message=" UINT32_FORMAT_SPEC"\n", umFlatSize, mFlatSize); + else if (memcmp(mPtr, umPtr, mFlatSize) != 0) + { + for (uint32 i=0; i + +#include "message/Message.h" +#include "minimessage/MiniMessage.h" + +using namespace muscle; + +static MMessage * CreateTestMessage(uint32 recurseCount, Message & m) +{ + MMessage * msg = MMAllocMessage(0x1234); + if (msg) + { + m.what = 0x1234; + + const uint32 ITEM_COUNT = 10; + uint32 i; + + // Test every type.... + { + MByteBuffer ** data = MMPutStringField(msg, false, "testStrings", ITEM_COUNT); + if (data) + { + for (i=0; i 0) + { + Message subMsg; + data[i] = CreateTestMessage(recurseCount-1, subMsg); + m.AddMessage("testMessages", subMsg); + } + else + { + data[i] = MMAllocMessage(i); + m.AddMessage("testMessages", Message(i)); + } + } + } + else printf("Error allocating message field!\n"); + } + { + void ** data = MMPutPointerField(msg, false, "testPointers", ITEM_COUNT); + if (data) + { + char * c = (char *) data; // doesn't matter what it is, really... this is just for testing + for (i=0; i 0) + { + uint64 elapsedTime = GetRunTime64() - lastThrowTime; + count++; + total += elapsedTime; + min = muscleMin(min, elapsedTime); + max = muscleMax(max, elapsedTime); + if (OnceEvery(MICROS_PER_SECOND, lastPrintTime)) LogTime(MUSCLE_LOG_INFO, "count=" UINT64_FORMAT_SPEC" min=" UINT64_FORMAT_SPEC "us max=" UINT64_FORMAT_SPEC "us avg=" UINT64_FORMAT_SPEC "us\n", count, min, max, total/count); + } + myTurnToThrow = true; // we caught the ball, now throw it back! + } + } + else if (bytesRead < 0) + { + LogTime(MUSCLE_LOG_ERROR, "Error reading ball, aborting!\n"); + break; + } + } + } +} + +// This program helps me test whether or not the host OS supports TCPSocketDataIO::FlushOutput() properly or not. +// The two instances of it play "catch" with a byte over a TCP socket, and it measures how fast it takes the +// byte to make each round-trip, and prints statistics about it. +int main(int argc, char ** argv) +{ + bool doFlush = (strcmp(argv[argc-1], "flush") == 0); + if (doFlush) argc--; + + const uint16 TEST_PORT = 15000; + CompleteSetupSystem css; + if (argc > 1) + { + ConstSocketRef s = Connect(argv[1], TEST_PORT, "testnagle"); + if (s()) HandleSession(s, true, doFlush); + } + else + { + const uint16 port = TEST_PORT; + ConstSocketRef as = CreateAcceptingSocket(port); + if (as()) + { + LogTime(MUSCLE_LOG_INFO, "testnagle awaiting incoming TCP connections on port %u.\n", port); + ConstSocketRef s = Accept(as); + if (s()) HandleSession(s, false, doFlush); + } + else LogTime(MUSCLE_LOG_CRITICALERROR, "Could not bind to TCP port %u (already in use?)\n", port); + } + return 0; +} diff --git a/test/testnetconfigdetect.cpp b/test/testnetconfigdetect.cpp new file mode 100644 index 00000000..8b585f6c --- /dev/null +++ b/test/testnetconfigdetect.cpp @@ -0,0 +1,40 @@ +#include "system/DetectNetworkConfigChangesSession.h" +#include "reflector/ReflectServer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +class TestSession : public DetectNetworkConfigChangesSession +{ +public: + TestSession() {/* empty */} + + virtual void NetworkInterfacesChanged(const Hashtable & interfaceNames) + { + String s; + if (interfaceNames.HasItems()) + { + s = " on these interfaces: "; + for (HashtableIterator iter(interfaceNames); iter.HasData(); iter++) s += iter.GetKey().Prepend(' '); + } + LogTime(MUSCLE_LOG_INFO, "Network configuration change detected%s\n", s()); + } +}; + +int main(int /*argc*/, char ** /*argv*/) +{ + CompleteSetupSystem css; // set up our environment + + ReflectServer server; + TestSession session; + + if (server.AddNewSession(AbstractReflectSessionRef(&session, false)) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Beginning Network-Configuration-Change-Detector test... try changing your network config, or plugging/unplugging an Ethernet cable.\n"); + if (server.ServerProcessLoop() == B_NO_ERROR) LogTime(MUSCLE_LOG_INFO, "testnetconfigdetect event loop exiting.\n"); + else LogTime(MUSCLE_LOG_CRITICALERROR, "testnetconfigdetect event loop exiting with an error condition.\n"); + } + server.Cleanup(); + + return 0; +} diff --git a/test/testnetutil.cpp b/test/testnetutil.cpp new file mode 100644 index 00000000..a635bcd6 --- /dev/null +++ b/test/testnetutil.cpp @@ -0,0 +1,42 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/Socket.h" + +using namespace muscle; + +int main(int, char **) +{ + CompleteSetupSystem css; + + printf("Querying local host's IP addresses:\n"); + + printf("\n"); + + Queue ifs; + if (GetNetworkInterfaceInfos(ifs) == B_NO_ERROR) + { + printf("Found " UINT32_FORMAT_SPEC " local network interfaces:\n", ifs.GetNumItems()); + for (uint32 i=0; i +#include +#include +#include + +#include "dataio/UDPSocketDataIO.h" +#include "dataio/TCPSocketDataIO.h" +#include "dataio/PacketizedDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "iogateway/PacketTunnelIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +static uint32 _recvWhatCounter = 0; +static uint32 _sendWhatCounter = 0; + +// Similar to the QueueGatewayMessageReceiver class, but records the from address also +class TestPacketGatewayMessageReceiver : public AbstractGatewayMessageReceiver +{ +public: + /** Default constructor */ + TestPacketGatewayMessageReceiver() {/* empty */} + +protected: + virtual void MessageReceivedFromGateway(const MessageRef & msg, void * userData) + { + const IPAddressAndPort & iap = *((const IPAddressAndPort *)userData); + LogTime(MUSCLE_LOG_TRACE, "RECEIVED MESSAGE from [%s]: (flatSize=" UINT32_FORMAT_SPEC ") (what=" UINT32_FORMAT_SPEC ") ---\n", iap.ToString()(), msg()->FlattenedSize(), msg()->what); + if (msg()->what == _recvWhatCounter) + { + uint32 spamLen = 0; + String temp; + if ((msg()->FindString("spam", temp) == B_NO_ERROR)&&(msg()->FindInt32("spamlen", spamLen) == B_NO_ERROR)) + { + if (spamLen == temp.Length()) + { + for (uint32 i=0; iwhat); + ExitWithoutCleanup(10); + } + } +}; + + +// This is a text based test of the PacketTunnelIOGateway class. With this test we +// should be able to broadcast Messages of any size over UDP, and (barring UDP lossage) +// they should be received and properly re-assembled by the listeners. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message args; + (void) ParseArgs(argc, argv, args); + HandleStandardDaemonArgs(args); + + const char * temp; + + uint16 port = 0; + if (args.FindString("port", &temp) == B_NO_ERROR) port = atol(temp); + if (port == 0) port = 9999; + + uint32 mtu = 0; + if (args.FindString("mtu", &temp) == B_NO_ERROR) mtu = atol(temp); + if (mtu == 0) mtu = MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET; + + uint32 magic = 0; + if (args.FindString("magic", &temp) == B_NO_ERROR) magic = atol(temp); + if (magic == 0) magic = 666; + + uint64 spamInterval = 0; + if (args.FindString("spam", &temp) == B_NO_ERROR) + { + int spamHz = atol(temp); + spamInterval = (spamHz > 0) ? MICROS_PER_SECOND/spamHz : 1; + } + + bool useTCP = false; + DataIORef dio; + if (args.FindString("tcp", &temp) == B_NO_ERROR) + { + useTCP = true; + ConstSocketRef s; + ip_address connectTo = GetHostByName(temp); + if (connectTo != invalidIP) + { + s = Connect(connectTo, port, NULL, "testpackettunnel", false); + if (s() == NULL) return 10; + } + else + { + ConstSocketRef as = CreateAcceptingSocket(port); + if (as() == NULL) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Could not create TCP socket on port %u!\n", port); + return 10; + } + LogTime(MUSCLE_LOG_INFO, "Awaiting TCP connection on port %u...\n", port); + s = Accept(as); + if (s() == NULL) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Accept() failed!\n"); + return 10; + } + } + dio.SetRef(new PacketizedDataIO(DataIORef(new TCPSocketDataIO(s, false)), mtu)); + } + else + { + ConstSocketRef s = CreateUDPSocket(); + if ((s() == NULL)||(SetUDPSocketBroadcastEnabled(s, true) != B_NO_ERROR)||(BindUDPSocket(s, port, NULL, invalidIP, true) != B_NO_ERROR)) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Error setting up UDP broadcast Socket on port %i!\n", port); + return 10; + } + + UDPSocketDataIO * udpDio = new UDPSocketDataIO(s, false); + udpDio->SetSendDestination(IPAddressAndPort(broadcastIP, port)); // gotta do it this way because SetUDPSocketTarget() would break our incoming messages! + dio.SetRef(udpDio); + } + + LogTime(MUSCLE_LOG_INFO, "Packet test running on port %u, mtu=" UINT32_FORMAT_SPEC " magic=" UINT32_FORMAT_SPEC "\n", port, mtu, magic); + + AbstractMessageIOGatewayRef slaveGatewayRef; + if (args.HasName("usegw")) slaveGatewayRef.SetRef(new MessageIOGateway(MUSCLE_MESSAGE_ENCODING_ZLIB_9)); + + PacketTunnelIOGateway gw(slaveGatewayRef, mtu, magic); + gw.SetDataIO(dio); + TestPacketGatewayMessageReceiver receiver; + + SocketMultiplexer multiplexer; + + uint64 nextSpamTime = 0; + LogTime(MUSCLE_LOG_INFO, "%s Event loop starting [%s]...\n", useTCP?"TCP":"UDP", (spamInterval>0) ? "Broadcast mode" : "Receive mode"); + int readFD = dio()->GetReadSelectSocket().GetFileDescriptor(); + int writeFD = dio()->GetWriteSelectSocket().GetFileDescriptor(); + uint64 lastTime = 0; + while(1) + { + if (OnceEvery(MICROS_PER_SECOND, lastTime)) LogTime(MUSCLE_LOG_INFO, "Send counter is currently at " UINT32_FORMAT_SPEC ", Receive counter is currently at " UINT32_FORMAT_SPEC "\n", _sendWhatCounter, _recvWhatCounter); + multiplexer.RegisterSocketForReadReady(readFD); + if (gw.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(writeFD); + if (multiplexer.WaitForEvents((spamInterval>0)?nextSpamTime:MUSCLE_TIME_NEVER) < 0) LogTime(MUSCLE_LOG_CRITICALERROR, "testpackettunnel: WaitForEvents() failed!\n"); + + bool reading = multiplexer.IsSocketReadyForRead(readFD); + bool writing = multiplexer.IsSocketReadyForWrite(writeFD); + bool writeError = ((writing)&&(gw.DoOutput() < 0)); + bool readError = ((reading)&&(gw.DoInput(receiver) < 0)); + if ((readError)||(writeError)) + { + LogTime(MUSCLE_LOG_INFO, "%s: Connection closed, exiting (%i,%i).\n", readError?"Read Error":"Write Error",readError,writeError); + break; + } + + if (spamInterval > 0) + { + uint64 now = GetRunTime64(); + if (now >= nextSpamTime) + { + nextSpamTime = now + spamInterval; + uint32 numMessages = rand() % 10; + + LogTime(MUSCLE_LOG_TRACE, "Spam! (" UINT32_FORMAT_SPEC " messages, counter=" UINT32_FORMAT_SPEC ")\n", numMessages, _sendWhatCounter); + + uint32 byteCount = 0; + while(byteCount < mtu*5) + { + MessageRef m = GetMessageFromPool(_sendWhatCounter++); + if (m()) + { + uint32 spamLen = ((rand()%5)==0)?(rand()%(mtu*50)):(rand()%(mtu/5)); + char * tmp = new char[spamLen+1]; + for (uint32 j=0; jAddString("spam", tmp) != B_NO_ERROR) WARN_OUT_OF_MEMORY; + if (m()->AddInt32("spamlen", spamLen) != B_NO_ERROR) WARN_OUT_OF_MEMORY; + LogTime(MUSCLE_LOG_TRACE, "ADDING OUTGOING MESSAGE what=" UINT32_FORMAT_SPEC " size=" UINT32_FORMAT_SPEC "\n", m()->what, m()->FlattenedSize()); + if (gw.AddOutgoingMessage(m) != B_NO_ERROR) WARN_OUT_OF_MEMORY; + delete [] tmp; + byteCount += m()->FlattenedSize(); + } + else break; + } + } + } + } + + return 0; +} diff --git a/test/testparsefile.cpp b/test/testparsefile.cpp new file mode 100644 index 00000000..2e9a44b9 --- /dev/null +++ b/test/testparsefile.cpp @@ -0,0 +1,73 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "dataio/FileDataIO.h" +#include "util/MiscUtilityFunctions.h" + +using namespace muscle; + +// This program exercises the ParseFile() function. +int main(int argc, char ** argv) +{ + if (argc < 2) + { + printf("Usage: parsefile [filename] [...]\n"); + return 5; + } + else + { + for (int i=1; i= 0)&&(bb.SetNumBytes((uint32)fileLen, false) == B_NO_ERROR)&&(fdio.ReadFully(bb.GetBuffer(), fileLen) == fileLen)) + { + String s((const char *) bb.GetBuffer(), bb.GetNumBytes()); + Message msg; + if (ParseFile(s, msg) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Parsed contents of file [%s]:\n", argv[i]); + msg.PrintToStream(); + printf("\n"); + + String s = UnparseFile(msg); + printf(" UnparseFile(msg) output is below: -------------\n[%s]", s()); + } + else LogTime(MUSCLE_LOG_ERROR, "Error parsing file [%s]\n", argv[i]); + } + else LogTime(MUSCLE_LOG_ERROR, "Unable to read file [%s]\n", argv[i]); + } + else LogTime(MUSCLE_LOG_ERROR, "Unable to open file [%s]\n", argv[i]); + } + } + return 0; + } +} diff --git a/test/testpool.cpp b/test/testpool.cpp new file mode 100644 index 00000000..070fe8d0 --- /dev/null +++ b/test/testpool.cpp @@ -0,0 +1,90 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "message/Message.h" +#include "system/SetupSystem.h" +#include "util/TimeUtilityFunctions.h" // for Snooze64() + +using namespace muscle; + +// This program tests the relative speeds of various object allocation strategies. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; // required! + + const uint32 NUM_OBJECTS = 10000000; + Queue tempQ; + if (tempQ.EnsureSize(NUM_OBJECTS, true) != B_NO_ERROR) return 10; + + int whichTest = (argc>1) ? atoi(argv[1]) : -1; + uint64 startTime = GetRunTime64(); + switch(whichTest) + { + case 1: + { + // See how long it takes to allocate an array of objects + (void) new Message[NUM_OBJECTS]; + } + break; + + case 2: + { + // As above, but with deletion also + delete [] new Message[NUM_OBJECTS]; + } + break; + + case 3: + { + // See how long it takes to allocate each object individually + for (uint32 i=0; i (where testnum is between 1 and 6)\n"); + break; + } + + uint64 endTime = GetRunTime64(); + printf("Test duration for " UINT32_FORMAT_SPEC " objects was " UINT64_FORMAT_SPEC "ms \n", NUM_OBJECTS, (endTime-startTime)/1000); + + if ((argc > 2)&&(strcmp(argv[2], "hold") == 0)) + { + printf("Holding indefinitely, so that you can look at OS reported memory usage...\n"); + while(1) Snooze64(SecondsToMicros(10)); + } + + return 0; +} diff --git a/test/testpulsenode.cpp b/test/testpulsenode.cpp new file mode 100644 index 00000000..713c147d --- /dev/null +++ b/test/testpulsenode.cpp @@ -0,0 +1,98 @@ +#include +#include +#include "dataio/NullDataIO.h" +#include "reflector/AbstractReflectSession.h" +#include "reflector/ReflectServer.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/PulseNode.h" +#include "util/String.h" + +using namespace muscle; + +// This test creates and exercises a large number of PulseNodes, just to be sure that +// such a thing can be done without too much inefficiency. +static const int NUM_PULSE_CHILDREN = 100000; // an unreasonable number too be sure, but we want to be scalable... :^) +static const uint64 PULSE_INTERVAL = 100000; // have one child fire every 100ms + +class TestPulseChild : public PulseNode +{ +public: + TestPulseChild(uint64 baseTime, int idx) : _fireTime(baseTime+(idx*PULSE_INTERVAL)), _idx(idx) + { + LogTime(MUSCLE_LOG_INFO, "TestPulseChild %i (%p) Initially scheduled for " UINT64_FORMAT_SPEC " (time until = " INT64_FORMAT_SPEC ")\n", idx, this, _fireTime, _fireTime-GetRunTime64()); + } + + virtual uint64 GetPulseTime(const PulseArgs &) {return _fireTime;} + + virtual void Pulse(const PulseArgs & args) + { + _fireTime = args.GetScheduledTime() + (NUM_PULSE_CHILDREN*PULSE_INTERVAL); + LogTime(MUSCLE_LOG_INFO, "TestPulseChild %i (%p) Pulsed at " UINT64_FORMAT_SPEC "/" UINT64_FORMAT_SPEC " (diff=" INT64_FORMAT_SPEC "), next pulse time will be " UINT64_FORMAT_SPEC "\n", _idx, this, args.GetCallbackTime(), args.GetScheduledTime(), args.GetCallbackTime()-args.GetScheduledTime(), _fireTime); + } + +private: + uint64 _fireTime; + int _idx; +}; + +class TestSession : public AbstractReflectSession +{ +public: + TestSession() + { + // empty + } + + virtual status_t AttachedToServer() + { + LogTime(MUSCLE_LOG_INFO, "TestSession::AttachedToServer() called...\n"); + + if (AbstractReflectSession::AttachedToServer() != B_NO_ERROR) return B_ERROR; + + uint64 baseTime = GetRunTime64(); + for (int i=0; i=0; i--) delete _tpcs[i]; + _tpcs.Clear(); + } + + virtual void MessageReceivedFromGateway(const MessageRef &, void *) {/* empty */} + virtual const char * GetTypeName() const {return "TestPulseChild";} + +private: + Queue _tpcs; +}; + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; // set up our environment + + Message args; (void) ParseArgs(argc, argv, args); + HandleStandardDaemonArgs(args); + + ReflectServer server; + TestSession session; + + if (server.AddNewSession(AbstractReflectSessionRef(&session, false)) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Beginning PulseNode test...\n"); + if (server.ServerProcessLoop() == B_NO_ERROR) LogTime(MUSCLE_LOG_INFO, "testpulsechild event loop exiting.\n"); + else LogTime(MUSCLE_LOG_CRITICALERROR, "testpulsechild event loop exiting with an error condition.\n"); + } + server.Cleanup(); + + return 0; +} diff --git a/test/testqueryfilter.cpp b/test/testqueryfilter.cpp new file mode 100644 index 00000000..1de17404 --- /dev/null +++ b/test/testqueryfilter.cpp @@ -0,0 +1,232 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "regex/QueryFilter.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Operation failed, line %i\n", __LINE__); +#define NEGATIVETEST(x) if ((x) == B_NO_ERROR) printf("Operation succeeded when it should not have, line %i\n", __LINE__); + +void printSep(const char * title); +void printSep(const char * title) +{ + printf("\n----------------- %s -------------------\n", title); +} + +#define COMMAND_HELLO 0x1234 +#define COMMAND_GOODBYE 0x4321 + +// This program exercises the Message class. +int main(void) +{ + Message m1; + m1.AddFloat("va", 1.0f); + printf("m1=" UINT32_FORMAT_SPEC"\n", m1.FlattenedSize()); + m1.AddInt32("co", 32); + printf("m2=" UINT32_FORMAT_SPEC"\n", m1.FlattenedSize()); + + printSep("Testing Replace*() with okayToAdd..."); + Message butter; + butter.ReplaceInt8(true, "int8", 8); + butter.ReplaceInt16(true, "int16", 16); + butter.ReplaceInt32(true, "int32", 32); + butter.ReplaceInt64(true, "int64", 64); + butter.ReplaceFloat(true, "float", 3.14f); + butter.ReplaceDouble(true, "double", 6.28); + butter.ReplacePoint(true, "point", Point(5,4)); + butter.ReplaceRect(true, "rect", Rect(5,6,7,8)); + butter.ReplacePointer(true, "pointer", &butter); + butter.PrintToStream(); + + butter.ReplaceInt16(true, "int16", 0, 17); + butter.ReplaceInt16(true, "int16", 1, 18); + butter.ReplaceInt8(true, "int8", 25, 25); // should work the same as AddInt8("int8", 25); + + butter.AddTag("Tag", RefCountableRef(GetMessageFromPool(6666)())); + butter.AddTag("Tag", RefCountableRef(GetMessageFromPool(7777)())); + butter.PrintToStream(); + + printf("(butter==m1) == %i\n", butter == m1); + printf("(butter==butter) == %i\n", butter == butter); + + printSep("Testing Add*()..."); + + Message msg(COMMAND_HELLO); + TEST(msg.AddString("Friesner", "Jeremy")); + TEST(msg.AddString("Friesner", "Joanna")); + TEST(msg.AddString("Friesner", "Joellen")); + TEST(msg.AddString("Chicken", "Soup")); + TEST(msg.AddString("Chicken", "Vegetable")); + TEST(msg.AddString("Chicken", "Lips")); + TEST(msg.AddString("Fred", "Flintstone")); + TEST(msg.AddString("Buddha", "Bark")); + TEST(msg.AddPoint("point12", Point(1,2))); + TEST(msg.AddPoint("point12", Point(2,1))); + TEST(msg.AddRect("rect1234", Rect(1,2,3,4))); + TEST(msg.AddRect("rect2345", Rect(2,3,4,5))); + TEST(msg.AddData("Data", B_RAW_TYPE, "ABCDEFGHIJKLMNOPQRS", 12)); + TEST(msg.AddData("Data", B_RAW_TYPE, "Mouse", 3)); + + Message subMessage(1); + TEST(subMessage.AddString("I am a", "sub message!")); + TEST(subMessage.AddInt32("My age is", 32)); + + Message subsubMessage(2); + TEST(subsubMessage.AddBool("Wow, that's deep!", true)); + TEST(subsubMessage.AddMessage("This is actually okay to do!", subsubMessage)); + TEST(subMessage.AddMessage("subsubMessage", subsubMessage)); + + TEST(msg.AddMessage("subMessage", subMessage)); + + {for (int i=0; i<10; i++) TEST(msg.AddInt8("TestInt8", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt16("TestInt16", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt32("TestInt32", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddInt64("TestInt64", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddDouble("TestDouble", i));} + {for (int i=0; i<10; i++) TEST(msg.AddFloat("TestFloat", i)); } + {for (int i=0; i<10; i++) TEST(msg.AddBool("TestBool", i)); } + + printf("Finished message:\n"); + msg.PrintToStream(); + + printSep("Testing RemoveName, RemoveData, Replace*()..."); + TEST(msg.RemoveData("TestInt8", 5)); + TEST(msg.RemoveName("Buddha")); + TEST(msg.RemoveData("Fred", 0)); + TEST(msg.RemoveData("Friesner", 1)); + NEGATIVETEST(msg.RemoveData("Glorp", 0)); + NEGATIVETEST(msg.RemoveData("Chicken", 5)); + TEST(msg.ReplaceString(false, "Friesner", 0, "Jorge")); + TEST(msg.ReplaceString(false, "Chicken", 1, "Feet")); + TEST(msg.ReplaceString(false, "Chicken", 2, "Breast")); + NEGATIVETEST(msg.ReplaceString(false, "Chicken", 3, "Soul")); + TEST(msg.ReplaceDouble(true, "TestDouble", 2, 222.222)); + TEST(msg.ReplaceFloat(true, "TestFloat", 3, 333.333)); + NEGATIVETEST(msg.ReplaceFloat(false, "RootBeerFloat", 0, 444.444f)); + TEST(msg.ReplaceBool(false, "TestBool", 5)); + TEST(msg.ReplaceRect(false, "rect2345", Rect(2,3,4,5))); + + Message eqMsg = msg; + printf("EQMSG=msg == %i\n", eqMsg==msg); + + printf("Replaced message:\n"); + msg.PrintToStream(); + + printSep("Testing the Find() commands..."); + String strResult; + TEST(msg.FindString("Friesner", strResult)); + printf("Friesner(0) = %s\n", strResult.Cstr()); + const char * res; + TEST(msg.FindString("Friesner", 1, &res)); + printf("Friesner(1) = %s\n", res); + NEGATIVETEST(msg.FindString("Friesner", 2, strResult)); + NEGATIVETEST(msg.FindString("Friesner", 3, strResult)); + + int8 int8Result; + TEST(msg.FindInt8("TestInt8", 5, int8Result)); + printf("TestInt8(5) = %i\n",int8Result); + + int16 int16Result; + TEST(msg.FindInt16("TestInt16", 4, int16Result)); + printf("TestInt16(4) = %i\n",int16Result); + + int32 int32Result; + TEST(msg.FindInt32("TestInt32", 4, int32Result)); + printf("TestInt32(4) = " INT32_FORMAT_SPEC"\n",int32Result); + + int64 int64Result; + TEST(msg.FindInt64("TestInt64", 4, int64Result)); + printf("TestInt64(4) = " INT64_FORMAT_SPEC "\n",int64Result); + + float floatResult; + TEST(msg.FindFloat("TestFloat", 4, floatResult)); + printf("TestFloat(4) = %f\n",floatResult); + + double doubleResult; + TEST(msg.FindDouble("TestDouble", 4, doubleResult)); + printf("TestDouble(4) = %f\n",doubleResult); + + Rect rectResult; + TEST(msg.FindRect("rect2345", rectResult)); + printf("TestRect: "); rectResult.PrintToStream(); + + Point pointResult; + TEST(msg.FindPoint("point12", 1, pointResult)); + printf("TestPoint: "); pointResult.PrintToStream(); + + msg.AddTag("ThisShouldn'tBeBackAfterUnflatten", RefCountableRef(NULL)); + + const void * gd; + uint32 getDataSize; + TEST(msg.FindData("Data", B_RAW_TYPE, &gd, &getDataSize)); + String dataStr((const char *) gd, getDataSize); + printf("data=[%s], size=" UINT32_FORMAT_SPEC"\n", dataStr(), getDataSize); + TEST(msg.FindData("Data", B_RAW_TYPE, 1, (const void **) &gd, &getDataSize)); + dataStr.SetCstr((const char *) gd, getDataSize); + printf("data(1)=[%s], size=" UINT32_FORMAT_SPEC"\n", dataStr(), getDataSize); + + printSep("Testing misc"); + + printf("There are " UINT32_FORMAT_SPEC" string entries\n", msg.GetNumNames(B_STRING_TYPE)); + msg.PrintToStream(); + Message tryMe = msg; + printf("Msg is " UINT32_FORMAT_SPEC" bytes.\n",msg.FlattenedSize()); + msg.AddTag("anothertag", RefCountableRef(GetMessageFromPool()())); + printf("After adding tag, msg is (hopefully still) " UINT32_FORMAT_SPEC" bytes.\n",msg.FlattenedSize()); + tryMe.PrintToStream(); + + printf("Extracting...\n"); + Message extract; + TEST(tryMe.FindMessage("subMessage", extract)); + printSep("Extracted subMessage!\n"); + extract.PrintToStream(); + + Message subExtract; + TEST(extract.FindMessage("subsubMessage", subExtract)); + printSep("Extracted subsubMessage!\n"); + subExtract.PrintToStream(); + + uint32 flatSize = msg.FlattenedSize(); + printf("FlatSize=" UINT32_FORMAT_SPEC"\n",flatSize); + uint8 * buf = new uint8[flatSize*10]; + {for (uint32 i=flatSize; i [%s] (%i)\n", it.GetFieldName()(), it.HasData()); + } + + { + printf("Testing field name iterator... B_STRING_TYPE\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_STRING_TYPE); it.HasData(); it++) printf("--> [%s] (%i)\n", it.GetFieldName()(), it.HasData()); + } + + { + printf("Testing field name iterator... B_INT8_TYPE\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_INT8_TYPE); it.HasData(); it++) printf("--> [%s] (%i)\n", it.GetFieldName()(), it.HasData()); + } + + { + printf("Testing field name iterator... B_OBJECT_TYPE (should have no results)\n"); + for (MessageFieldNameIterator it = copy.GetFieldNameIterator(B_OBJECT_TYPE); it.HasData(); it++) printf("--> [%s] (%i)\n", it.GetFieldName()(), it.HasData()); + } + return 0; +} diff --git a/test/testqueue.cpp b/test/testqueue.cpp new file mode 100644 index 00000000..28de2deb --- /dev/null +++ b/test/testqueue.cpp @@ -0,0 +1,303 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "system/SetupSystem.h" +#include "util/Queue.h" +#include "util/String.h" +#include "util/StringTokenizer.h" +#include "util/TimeUtilityFunctions.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +void PrintToStream(const Queue & q); +void PrintToStream(const Queue & q) +{ + printf("Queue state is:\n"); + for (uint32 i=0; i %i\n",i,val); +*/ + printf(UINT32_FORMAT_SPEC" -> %i\n",i,q[i]); + } +} + +// This program exercises the Queue class. +int main(void) +{ + CompleteSetupSystem css; // needed for string-count stats + +#ifdef TEST_SWAP_METHOD + while(1) + { + char qs1[512]; printf("Enter q1: "); fflush(stdout); if (fgets(qs1, sizeof(qs1), stdin) == NULL) qs1[0] = '\0'; + char qs2[512]; printf("Enter q2: "); fflush(stdout); if (fgets(qs2, sizeof(qs2), stdin) == NULL) qs2[0] = '\0'; + + Queue q1, q2; + StringTokenizer t1(qs1), t2(qs2); + const char * s; + while((s = t1()) != NULL) q1.AddTail(atoi(s)); + while((s = t2()) != NULL) q2.AddTail(atoi(s)); + printf("T1Before="); PrintToStream(q1); + printf("T2Before="); PrintToStream(q2); + q1.SwapContents(q2); + printf("T1After="); PrintToStream(q1); + printf("T2After="); PrintToStream(q2); + } +#endif + +#ifdef MUSCLE_USE_CPLUSPLUS11 + { + Queue q {1,2,3,4,5}; + if (q.GetNumItems() != 5) {printf("Oh no, initialize list constructor didn't work!\n"); exit(10);} + q = {6,7,8,9,10,11}; + if (q.GetNumItems() != 6) {printf("Oh no, initialize list assignment operator didn't work!\n"); exit(10);} + } +#endif + + // Test muscleSwap() + { + Queue q1, q2; + q1.AddTail("q1"); + q2.AddTail("q2"); + muscleSwap(q1, q2); + if ((q1.GetNumItems() != 1)||(q2.GetNumItems() != 1)||(q1[0] != "q2")||(q2[0] != "q1")) + { + printf("Oh no, muscleSwap is broken for Message objects!\n"); + exit(10); + } + printf("muscleSwap() worked!\n"); + } + + + const int testSize = 15; + Queue q; + + int vars[] = {5,6,7,8,9,10,11,12,13,14,15}; + + printf("ADDTAIL TEST\n"); + { + for (int i=0; i q; + String myStr = "Magic"; + q.AddTail(myStr); + if (myStr != "Magic") + { + printf("Error, AddTail() stole my string!\n"); + exit(10); + } + } + + printf("SORT TEST 1\n"); + { + q.Clear(); + for (int i=0; i q2; + for (int i=0; i q; + const int vars[] = {9,2,3,5,8,3,5,6,6,7,2,3,4,6,8,9,3,5,6,4,3,2,1}; + if (q.AddTailMulti(vars, ARRAYITEMS(vars)) == B_NO_ERROR) + { + q.RemoveDuplicateItems(); + for (uint32 i=0; i q; (void) q.EnsureSize(NUM_ITEMS, true); + double tally = 0.0; + for (uint32 t=0; t q; (void) q.EnsureSize(NUM_ITEMS, true); + double tally = 0.0; + for (uint32 t=0; t%i\n", j, q[j]); + } + + printf("CONCAT TEST 1\n"); + { + q.Clear(); + Queue q2; + for (int i=0; i%i\n", j, q[j]); + } + + printf("CONCAT TEST 2\n"); + { + q.Clear(); + Queue q2; + for (int i=0; i%i\n", j, q[j]); + } + { + printf("GetArrayPointer() test\n"); + uint32 len = 0; + int * a; + for (uint32 i=0; (a = q.GetArrayPointer(i, len)) != NULL; i++) + { + printf("SubArray " UINT32_FORMAT_SPEC": " UINT32_FORMAT_SPEC" items: ", i, len); + for (uint32 j=0; j q; + int counter = 0; + for (uint32 j=0; j +#include "system/SetupSystem.h" +#include "system/Thread.h" +#include "util/String.h" +#include "util/RefCount.h" +#include "util/Queue.h" +#include "util/Hashtable.h" +#include "util/MiscUtilityFunctions.h" + +using namespace muscle; + +class TestItem : public RefCountable +{ +public: + TestItem() {/* empty */} + TestItem(const String & name) {SetName(name);} + ~TestItem() {_name = "Dead";} // Just to make dead-item-usage detection a bit easier + + const String & GetName() const {return _name;} + void SetName(const String & name) {_name = name;} + +private: + String _name; +}; +DECLARE_REFTYPES(TestItem); +static TestItemRef::ItemPool _pool; + +class TestThread : public Thread +{ +public: + TestThread() {/* empty */} + + virtual void InternalThreadEntry() + { + char buf[128]; sprintf(buf, "TestThread-%p", this); + const String prefix = buf; + Queue q; + bool keepGoing = 1; + uint32 counter = 0; + while(keepGoing) + { + uint32 x = rand() % 10000; + while(q.GetNumItems() < x) + { + TestItemRef tRef(_pool.ObtainObject()); + if (tRef()) + { + char buf[128]; sprintf(buf, "-" UINT32_FORMAT_SPEC, ++counter); + tRef()->SetName(prefix+buf); + q.AddTail(tRef); + } + else WARN_OUT_OF_MEMORY; + } + while(q.GetNumItems() > x) q.RemoveTail(); + + // Check to make sure no other threads are overwriting our objects + for (uint32 i=0; iGetName().StartsWith(prefix) == false) + { + printf("ERROR, thread %p expected prefix [%s], saw [%s] at position " INT32_FORMAT_SPEC"/" UINT32_FORMAT_SPEC"\n", this, prefix(), q[i]()->GetName()(), i, q.GetNumItems()); + ExitWithoutCleanup(10); + } + } + + // Check to see if the main thread wants us to exit yet + MessageRef r; + while(WaitForNextMessageFromOwner(r, 0) >= 0) if (r() == NULL) keepGoing = false; + } + } +}; + +// This program exercises the Ref class. +int main(void) +{ + CompleteSetupSystem setupSystem; + + printf("sizeof(TestItemRef)=%i\n", (int)sizeof(TestItemRef)); + + { + printf("Checking queue...\n"); + Queue q; + printf("Adding refs...\n"); + for (int i=0; i<10; i++) + { + char temp[50]; sprintf(temp, "%i", i); + TestItemRef tr(new TestItem(temp)); + ConstTestItemRef ctr(tr); + ConstTestItemRef t2(ctr); + q.AddTail(tr); + } + printf("Removing refs...\n"); + while(q.HasItems()) q.RemoveHead(); + printf("Done with queue test!\n"); + } + + { + printf("Checking hashtable\n"); + Hashtable ht; + printf("Adding refs...\n"); + for (int i=0; i<10; i++) + { + char temp[50]; sprintf(temp, "%i", i); + ht.Put(String(temp), TestItemRef(new TestItem(temp))); + } + printf("Removing refs...\n"); + for (int i=0; i<10; i++) + { + char temp[50]; sprintf(temp, "%i", i); + ht.Remove(String(temp)); + } + printf("Done with hash table test!\n"); + } + + printf("Beginning multithreaded object usage test...\n"); + { + const uint32 NUM_THREADS = 50; + TestThread threads[NUM_THREADS]; + for (uint32 i=0; i +#include +#include +#include + +#ifdef __ATHEOS__ +# include +# define BLooper os::Looper +# define BMessage os::Message +# define BMessenger os::Messenger +#else +# include +#endif + +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" + +#ifdef __ATHEOS__ +# include "atheossupport/AThread.h" +# include "atheossupport/ConvertMessages.h" +# define BMessageTransceiverThread AMessageTransceiverThread +#elif defined(__BEOS__) || defined(__HAIKU__) +# include "besupport/BThread.h" +# include "besupport/ConvertMessages.h" +#else +# error "testreflectclient only works under BeOS, Haiku, and AtheOS. Try portablereflectclient instead! +#endif + +#include "util/NetworkUtilityFunctions.h" +#include "util/StringTokenizer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +enum { + METHOD_MANUAL = 0, + METHOD_AUTOMATIC, + METHOD_ACCEPT +}; + +// This BeOS-specific program is used to test the muscled server. It is more-or-less +// functionally equivalent to the portablereflectclient, but exercises the BeOS-specific +// functionality found in the besupport class as well. +class MyLooper : public BLooper +{ +public: + MyLooper(MessageTransceiverThread & mtt) : +#ifdef __ATHEOS__ + Looper(""), +#endif + _transThread(mtt) {} + +#ifdef __ATHEOS__ + virtual void HandleMessage(os::Message * msg) +#else + virtual void MessageReceived(BMessage * msg) +#endif + { +#ifdef __ATHEOS__ + switch(msg->GetCode()) +#else + switch(msg->what) +#endif + { + case MUSCLE_THREAD_SIGNAL: + { + uint32 code; + MessageRef ref; + String session; + uint32 factoryID; + IPAddressAndPort location; + while(_transThread.GetNextEventFromInternalThread(code, &ref, &session, &factoryID, &location) >= 0) + { + String codeStr; + switch(code) + { + case MTT_EVENT_INCOMING_MESSAGE: codeStr = "IncomingMessage"; break; + case MTT_EVENT_SESSION_ACCEPTED: codeStr = "SessionAccepted"; break; + case MTT_EVENT_SESSION_ATTACHED: codeStr = "SessionAttached"; break; + case MTT_EVENT_SESSION_CONNECTED: codeStr = "SessionConnected"; break; + case MTT_EVENT_SESSION_DISCONNECTED: codeStr = "SessionDisconnected"; break; + case MTT_EVENT_SESSION_DETACHED: codeStr = "SessionDetached"; break; + case MTT_EVENT_FACTORY_ATTACHED: codeStr = "FactoryAttached"; break; + case MTT_EVENT_FACTORY_DETACHED: codeStr = "FactoryDetached"; break; + case MTT_EVENT_OUTPUT_QUEUES_DRAINED: codeStr = "OutputQueuesDrained"; break; + case MTT_EVENT_SERVER_EXITED: codeStr = "ServerExited"; break; + default: + { + char buf[5]; MakePrettyTypeCodeString(code, buf); + codeStr = "\'"; + codeStr += buf; + codeStr += "\'"; + } + } + printf("/------------------------------------------------------------\n"); + printf("Event from MTT: type=[%s], session=[%s] factoryID=[" UINT32_FORMAT_SPEC "] location=[%s]\n", codeStr(), session(), factoryID, location.ToString()()); + if (ref()) ref()->PrintToStream(); + printf("\\------------------------------------------------------------\n"); + } + } + break; + + default: + printf("MyLooper: Received unknown BMessage:\n"); +#ifndef __ATHEOS__ + msg->PrintToStream(); +#endif + break; + } + } + + MessageTransceiverThread & _transThread; +}; + +status_t SetupTransceiverThread(MessageTransceiverThread & mtt, const char * hostName, uint16 port, int method) +{ + switch(method) + { + case METHOD_MANUAL: + if (mtt.AddNewSession(Connect(hostName, port, "trc")) != B_NO_ERROR) + { + printf("Error adding manual session!\n"); + return B_ERROR; + } + break; + + case METHOD_AUTOMATIC: + if (mtt.AddNewConnectSession(hostName, port) != B_NO_ERROR) + { + printf("Error adding connect session!\n"); + return B_ERROR; + } + else printf("Will connect asynchronously to %s\n", GetConnectString(hostName, port)()); + break; + + case METHOD_ACCEPT: + { + if (mtt.PutAcceptFactory(port) != B_NO_ERROR) + { + printf("Error adding accept factory!\n"); + return B_ERROR; + } + printf("Accepting connections\n"); + } + break; + + default: + printf("CreateTransceiverThread(): unknown method %i\n", method); + return B_ERROR; + break; + } + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + (void) mtt.SetOutgoingMessageEncoding(MUSCLE_MESSAGE_ENCODING_ZLIB_1); + + MessageRef zlibRef = GetMessageFromPool(PR_COMMAND_SETPARAMETERS); + if (zlibRef()) + { + zlibRef()->AddInt32(PR_NAME_REPLY_ENCODING, MUSCLE_MESSAGE_ENCODING_ZLIB_1); + mtt.SendMessageToSessions(zlibRef); + } +#endif + + return B_NO_ERROR; +} + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + char * hostName = "localhost"; + int port = 0; + + int method = -1; + if (argc > 1) + { + if (strcmp(argv[1], "-connect") == 0) + { + method = METHOD_AUTOMATIC; + if (argc > 2) hostName = argv[2]; + if (argc > 3) port = atoi(argv[3]); + if (port <= 0) port = 2960; + } + else if (strcmp(argv[1], "-connectsync") == 0) + { + method = METHOD_MANUAL; + if (argc > 2) hostName = argv[2]; + if (argc > 3) port = atoi(argv[3]); + if (port <= 0) port = 2960; + } + else if (strcmp(argv[1], "-accept") == 0) + { + method = METHOD_ACCEPT; + if (argc > 2) port = atoi(argv[2]); + } + } + + if (method != -1) + { + BMessageTransceiverThread mtt; + MyLooper * looper = new MyLooper(mtt); + looper->Run(); + mtt.SetTarget(looper); + if ((mtt.StartInternalThread() == B_NO_ERROR)&&(SetupTransceiverThread(mtt, hostName, (uint16)port, method) == B_NO_ERROR)) + { + char text[1000]; + bool keepGoing = true; + while(keepGoing) + { + if (fgets(test, sizeof(test), stdin) == NULL) test[0] = '\0'; + printf("You typed: [%s]\n",text); + if (*text != '\0') + { + bool send = true; + MessageRef ref = GetMessageFromPool(); + + switch(text[0]) + { + case 'r': + printf("Requesting output-queues-drained notification\n"); + mtt.RequestOutputQueuesDrainedNotification(MessageRef()); + send = false; + break; + + case 'f': + { + // Test the server's un-spoof-ability + ref()->what = 'HELO'; + ref()->AddString(PR_NAME_SESSION, "nerdboy"); // should be changed transparently by the server + } + break; + + case 'i': + { + StringTokenizer tok(&text[2]); + const char * node = tok.GetNextToken(); + const char * before = tok.GetNextToken(); + const char * value = tok.GetNextToken(); + printf("Insert [%s] before [%s] under [%s]\n", value, before, node); + ref()->what = PR_COMMAND_INSERTORDEREDDATA; + ref()->AddString(PR_NAME_KEYS, node); + Message child(atoi(value)); + child.AddString("wtf?", value); + ref()->AddMessage(before, child); + } + break; + + case 'm': + ref()->what = 'umsg'; + if (text[1]) ref()->AddString(PR_NAME_KEYS, &text[2]); + ref()->AddString("info", text[1] ? "This is a directed user message" : "This is a default-directed user message"); + break; + + case 'M': + ref()->what = PR_COMMAND_SETPARAMETERS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 's': + { + ref()->what = PR_COMMAND_SETDATA; + MessageRef dataMsg = GetMessageFromPool('HELO'); + if (dataMsg()) + { + dataMsg()->AddInt32("val", atol(&text[2])); + ref()->AddMessage(&text[2], dataMsg); + } + } + break; + + case 'g': + ref()->what = PR_COMMAND_GETDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'k': + ref()->what = PR_COMMAND_KICK; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'b': + ref()->what = PR_COMMAND_ADDBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'B': + ref()->what = PR_COMMAND_REMOVEBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'G': + ref()->what = PR_COMMAND_GETDATA; + ref()->AddString(PR_NAME_KEYS, "j*/k*"); + ref()->AddString(PR_NAME_KEYS, "k*/j*"); + break; + + case 'q': + keepGoing = send = false; + break; + + // test undefined type + case 'u': + { + const uint8 junk[] = "junkman"; + ref()->AddData("junk type", 0x1234, (const void *) junk, sizeof(junk)); + ref()->PrintToStream(); + } + break; + + case 'x': + ref()->what = PR_COMMAND_SETPARAMETERS; + (void) ref()->AddArchiveMessage(&text[2], Int32QueryFilter("val", Int32QueryFilter::OP_GREATER_THAN, 10)); + break; + + case 'p': + ref()->what = PR_COMMAND_SETPARAMETERS; + ref()->AddString(&text[2], ""); + break; + + case 'P': + ref()->what = PR_COMMAND_GETPARAMETERS; + break; + + case 'd': + ref()->what = PR_COMMAND_REMOVEDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'D': + ref()->what = PR_COMMAND_REMOVEPARAMETERS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'I': + ref()->what = 1651666225; + ref()->AddString("br_authorid", "jeremy"); + ref()->AddString("br_streamid", "smurfs"); + ref()->AddInt32("br_hostip", GetHostByName("beshare.befaqs.com", false)); + ref()->AddInt16("br_port", 2960); + break; + + case 't': + { + // test all data types + ref()->what = 1234; + ref()->AddString("String", "this is a string"); + ref()->AddInt8("Int8", 123); + ref()->AddInt8("-Int8", -123); + ref()->AddInt16("Int16", 1234); + ref()->AddInt16("-Int16", -1234); + ref()->AddInt32("Int32", 12345); + ref()->AddInt32("-Int32", -12345); + ref()->AddInt64("xInt64", -1); + ref()->AddInt64("xInt64", 1); + ref()->AddInt64("Int64", 123456789); + ref()->AddInt64("-Int64", -123456789); + ref()->AddBool("Bool", true); + ref()->AddBool("-Bool", false); + ref()->AddFloat("Float", 1234.56789f); + ref()->AddFloat("-Float", -1234.56789f); + ref()->AddDouble("Double", 1234.56789); + ref()->AddDouble("-Double", -1234.56789); + ref()->AddPointer("Pointer", ref()); + ref()->AddRect("Rect", Rect(1,2,3,4)); + ref()->AddRect("Rect", Rect(2,3,4,5)); + ref()->AddPoint("Point", Point(4,5)); + ref()->AddFlat("Flat", *ref()); + char data[] = "This is some data"; + ref()->AddData("Flat2", B_RAW_TYPE, data, sizeof(data)); + } + break; + + default: + printf("Sorry, wot?\n"); + send = false; + break; + } + + if (send) + { + printf("Sending message...\n"); +// ref()->PrintToStream(); + mtt.SendMessageToSessions(ref); + } + } + } + + printf("Shutting down MessageTransceiverThread...\n"); + mtt.ShutdownInternalThread(); + + printf("Shutting down looper...\n"); + if (looper->Lock()) looper->Quit(); + } + else printf("Could not set up session!\n"); + } + else + { + printf("Usage: testreflectclient -connect [hostname=localhost] [port=2960]\n"); + printf(" testreflectclient -connectsync [hostname=localhost] [port=2960]\n"); + printf(" testreflectclient -accept [port=0]\n"); + } + printf("Test client exiting, bye!\n"); + return 0; +} + diff --git a/test/testregex.cpp b/test/testregex.cpp new file mode 100644 index 00000000..0534e44d --- /dev/null +++ b/test/testregex.cpp @@ -0,0 +1,22 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "regex/StringMatcher.h" + +using namespace muscle; + +// Just some quick testing of the StringMatcher class... +int main(int argc, char ** argv) +{ + if (argc <= 1) + { + printf("Usage: testregex 'pattern' 'str1' 'str2' [...]\n"); + return 5; + } + + StringMatcher sm(argv[1]); + printf("Testing pattern: \"%s\"%s\n", argv[1], sm.IsPatternUnique()?" (UNIQUE)":""); + for (int i=2; i 1) + { + ConstSocketRef s = Connect(argv[1], 2960, "testresponse"); + if (s()) + { + Message pingMessage(PR_COMMAND_PING); // we'll keep on sending this and seeing how long it takes to get back + + TCPSocketDataIO sockIO(s, false); + + MessageIOGateway ioGateway; + ioGateway.SetDataIO(DataIORef(&sockIO, false)); + + QueueGatewayMessageReceiver inQueue; + uint64 lastThrowTime = 0; + bool pingSent = false; + uint64 min = (uint64)-1; + uint64 max = 0; + uint64 total = 0; + uint64 count = 0; + uint64 lastPrintTime = 0; + + SocketMultiplexer multiplexer; + while(1) + { + if ((pingSent == false)&&(ioGateway.AddOutgoingMessage(MessageRef(&pingMessage, false)) == B_NO_ERROR)) + { + pingSent = true; + lastThrowTime = GetRunTime64(); + } + + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + if (ioGateway.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + if (multiplexer.WaitForEvents() < 0) + { + LogTime(MUSCLE_LOG_ERROR, "WaitForEvents() failed, aborting!\n"); + break; + } + if (multiplexer.IsSocketReadyForRead(fd)) + { + if (ioGateway.DoInput(inQueue) < 0) + { + LogTime(MUSCLE_LOG_ERROR, "Error reading from gateway, aborting!\n"); + break; + } + + MessageRef next; + while(inQueue.RemoveHead(next) == B_NO_ERROR) + { + if ((pingSent)&&(next()->what == PR_RESULT_PONG)) + { + uint64 result = GetRunTime64()-lastThrowTime; + min = muscleMin(min, result); + max = muscleMax(max, result); + total += result; + count++; + if (OnceEvery(MICROS_PER_SECOND, lastPrintTime)) LogTime(MUSCLE_LOG_INFO, "Results: min=" UINT64_FORMAT_SPEC "us max=" UINT64_FORMAT_SPEC "us avg=" UINT64_FORMAT_SPEC "us trials=" UINT64_FORMAT_SPEC "\n", min, max, total/count, count); + + pingSent = false; // we need to send another one now + } + } + } + + if ((multiplexer.IsSocketReadyForWrite(fd))&&(ioGateway.DoOutput() < 0)) + { + LogTime(MUSCLE_LOG_ERROR, "Error writing to gateway, aborting!\n"); + break; + } + } + } + } + else LogTime(MUSCLE_LOG_ERROR, "Usage: testresponse 192.168.0.150\n"); + return 0; +} diff --git a/test/testserial.cpp b/test/testserial.cpp new file mode 100644 index 00000000..90c6823f --- /dev/null +++ b/test/testserial.cpp @@ -0,0 +1,40 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "dataio/RS232DataIO.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) +#define TESTSIZE(x) if ((x) < 0) printf("Test failed, line %i\n",__LINE__) + +int main(int /*argc*/, char ** /*argv*/) +{ + RS232DataIO io("/dev/ttyS0", 38400, true); + if (io.IsPortAvailable()) + { + char buf[1024]; + int32 ret = 1; + int c = 0; + while(ret >= 0) + { + ret = io.Read(buf, sizeof(buf)); + printf("Read " INT32_FORMAT_SPEC" bytes: [", ret); + for (int32 i=0; i + +#include "system/SharedMemory.h" +#include "util/NetworkUtilityFunctions.h" + +using namespace muscle; + +static const char * TEST_KEY = "testsharedmem_key"; +static const uint32 TEST_AREA_SIZE = 4096; + +// This program exercises the Message class. +int main(int argc, char ** argv) +{ + bool deleteArea = ((argc > 1)&&(strncmp(argv[1], "del", 3) == 0)); + + uint8 base = 0; + LogTime(MUSCLE_LOG_INFO, deleteArea ? "Deleting shared memory area!\n" : "Beginning shared memory test!\n"); + + SharedMemory m; + if (m.SetArea(TEST_KEY, TEST_AREA_SIZE, true) == B_NO_ERROR) + { + if (deleteArea) LogTime(MUSCLE_LOG_INFO, "Deletion of area %s %s\n", m.GetAreaName()(), (m.DeleteArea() == B_NO_ERROR) ? "succeeded" : "failed"); + else + { + uint8 * a = m.GetAreaPointer(); + uint32 s = m.GetAreaSize(); + + if (m.IsCreatedLocally()) + { + LogTime(MUSCLE_LOG_INFO, "Created new shared memory area %s\n", m.GetAreaName()()); + for (uint32 i=0; i 0) + { + uint8 * a = m.GetAreaPointer(); + {for (uint32 i=1; i 0) + { + const uint8 * a = m.GetAreaPointer(); + {for (uint32 i=1; i +#endif + +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +// This program tests the SocketMultiplexer class by seeing how many chained socket-pairs +// we can chain a message through sequentially +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + uint32 numPairs = 5; + if (argc > 1) numPairs = atoi(argv[1]); + + bool quiet = false; + if ((argc > 2)&&(strcmp(argv[2], "quiet") == 0)) quiet = true; + +#ifdef __APPLE__ + // Tell MacOS/X that yes, we really do want to create this many file descriptors + struct rlimit rl; + rl.rlim_cur = rl.rlim_max = (numPairs*2)+5; + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) perror("setrlimit"); +#endif + + printf("Testing %i socket-pairs chained together...\n", numPairs); + + Queue senders; (void) senders.EnsureSize(numPairs, true); + Queue receivers; (void) receivers.EnsureSize(numPairs, true); + + for (uint32 i=0; i= endTime) break; + + int ret = multiplexer.WaitForEvents(); + if (ret < 0) + { + printf("WaitForEvents errored out, aborting test!\n"); + break; + } + + uint64 elapsed = GetRunTime64()-then; + if (quiet == false) printf("WaitForEvents returned %i after " UINT64_FORMAT_SPEC" microseconds.\n", ret, elapsed); + + count++; + tally += elapsed; + minRunTime = muscleMin(minRunTime, elapsed); + maxRunTime = muscleMax(maxRunTime, elapsed); + + for (uint32 i=0; i 0) + { + uint32 nextIdx = (i+1)%numPairs; + int32 sentBytes = SendData(senders[nextIdx], buf, numBytesReceived, false); + if (quiet == false) printf("Sent " INT32_FORMAT_SPEC" bytes on sender #" UINT32_FORMAT_SPEC"\n", sentBytes, nextIdx); + } + } + } + } + printf("Test complete: WaitEvents() called " UINT64_FORMAT_SPEC" times, averageTime=" UINT64_FORMAT_SPEC"uS, minimumTime=" UINT64_FORMAT_SPEC"uS, maximumTime=" UINT64_FORMAT_SPEC"uS.\n", count, tally/(count?count:1), minRunTime, maxRunTime); + return 0; +} diff --git a/test/teststring.cpp b/test/teststring.cpp new file mode 100644 index 00000000..bf723245 --- /dev/null +++ b/test/teststring.cpp @@ -0,0 +1,112 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "message/Message.h" +#include "util/String.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "support/Tuple.h" // make sure the template operators don't mess us up + +using namespace muscle; + +#define TEST(x) if (!(x)) printf("Test failed, line %i\n",__LINE__) + +// This program exercises the String class. +int main(void) +{ +#ifdef TEST_MEMMEM + char lookIn[512]; printf("Enter LookIn string: "); fflush(stdout); if (fgets(lookIn, sizeof(lookIn), stdin) == NULL) lookIn[0] = '\0'; + lookIn[strlen(lookIn)-1] = '\0'; + while(1) + { + char lookFor[512]; printf("Enter LookFor string: "); fflush(stdout); if (fgets(lookFor, sizeof(lookFor), stdin) == NULL) lookFor[0] = '\0'; + lookFor[strlen(lookFor)-1] = '\0'; + + printf("lookIn=[%s] lookFor=[%s]\n", lookIn, lookFor); + const uint8 * r = MemMem((const uint8 *)lookIn, (uint32)strlen(lookIn), (const uint8 *)lookFor, (uint32)strlen(lookFor)); + printf("r=[%s]\n", r); + } +#endif + +#ifdef TEST_PARSE_ARGS + while(1) + { + char base[512]; printf("Enter string: "); fflush(stdout); if (fgets(base, sizeof(base), stdin) == NULL) base[0] = '\0'; + + Message args; + if (ParseArgs(base, args) == B_NO_ERROR) + { + printf("Parsed: "); args.PrintToStream(); + } + else printf("Parse failed!\n"); + } +#endif + +#ifdef TEST_REPLACE_METHOD + while(1) + { + char base[512]; printf("Enter string: "); fflush(stdout); if (fgets(base, sizeof(base), stdin) == NULL) base[0] = '\0'; + char replaceMe[512]; printf("Enter replaceMe: "); fflush(stdout); if (fgets(replaceMe, sizeof(replaceMe), stdin) == NULL) replaceMe[0] = '\0'; + char withMe[512]; printf("Enter withMe: "); fflush(stdout); if (fgets(withMe, sizeof(withMe), stdin) == NULL) withMe[0] = '\0'; + + String b(base); + int32 ret = b.Replace(replaceMe, withMe); + printf(INT32_FORMAT_SPEC": Afterwards, [%s] (" UINT32_FORMAT_SPEC")\n", ret, b(), b.Length()); + } +#endif + + int five=5, six=6; + muscleSwap(five, six); + if ((five != 6)||(six != 5)) {printf("Oh no, trivial muscleSwap() is broken! five=%i six=%i\n", five, six); exit(10);} + + String ss1 = "This is string 1", ss2 = "This is string 2"; + PrintAndClearStringCopyCounts("Before Swap"); + muscleSwap(ss1, ss2); + PrintAndClearStringCopyCounts("After Swap"); + printf("ss1=[%s] ss2=[%s]\n", ss1(), ss2()); + + Point p(1.5,2.5); + Rect r(3.5,4.5,5.5,6.5); + int16 dozen = 13; + String aString = String("%1 is a %2 %3 booltrue=%4 boolfalse=%5 point=%6 rect=%7").Arg(dozen).Arg("baker's dozen").Arg(3.14159).Arg(true).Arg(false).Arg(p).Arg(r); + printf("arg string = [%s]\n", aString()); + + String temp; + temp.SetCstr("1234567890", 3); + printf("123=[%s]\n", temp()); + temp.SetCstr("1234567890"); + printf("%s\n", temp()); + + String scale("do"); scale = scale.AppendWord("re", ", ").AppendWord("mi").AppendWord(String("fa")).AppendWord("so").AppendWord("la").AppendWord("ti").AppendWord("do"); + printf("scale = [%s]\n", scale()); + + String rem("Hello sailor"); + printf("[%s]\n", (rem+"maggot"-"sailor")()); + rem -= "llo"; + printf("[%s]\n", rem()); + rem -= "xxx"; + printf("[%s]\n", rem()); + rem -= 'H'; + printf("[%s]\n", rem()); + rem -= 'r'; + printf("[%s]\n", rem()); + rem -= rem; + printf("[%s]\n", rem()); + + String test = "hello"; + test = test + " and " + " goodbye " + '!'; + printf("test=[%s]\n", test()); + + test.Replace(test, "foo"); + printf("foo=[%s]\n", test()); + test.Replace("o", test); + printf("ffoofoo=[%s]\n", test()); + + String s1("one"); + String s2("two"); + String s3; + printf("[%s]\n", s1.AppendWord(s2, ", ").AppendWord(s3, ", ")()); + + return 0; +} diff --git a/test/testsysteminfo.cpp b/test/testsysteminfo.cpp new file mode 100644 index 00000000..a8bb6f6d --- /dev/null +++ b/test/testsysteminfo.cpp @@ -0,0 +1,32 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "system/SystemInfo.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +static String GetSystemPathAux(uint32 whichPath) +{ + String ret; + if (GetSystemPath(whichPath, ret) != B_NO_ERROR) ret = ""; + return ret; +} + +// This program prints out the results returned by the SystemInfo functions. The implementation +// of these functions is very OS-specific, so this is a helpful way to test them on each platform +// to ensure they are working properly. +int main(void) +{ + CompleteSetupSystem css; + + uint32 numCores; + if (GetNumberOfProcessors(numCores) == B_NO_ERROR) printf("TestSystemInfo: There are " UINT32_FORMAT_SPEC " CPU cores in the system.\n", numCores); + else printf("TestSystemInfo: Unable to determine the number of CPU cores in the system.\n"); + printf(" OS name = [%s]\n", GetOSName()); + printf(" file sep = [%s]\n", GetFilePathSeparator()); + printf(" cur path = [%s]\n", GetSystemPathAux(SYSTEM_PATH_CURRENT)()); + printf(" exe path = [%s]\n", GetSystemPathAux(SYSTEM_PATH_EXECUTABLE)()); + printf(" tmp path = [%s]\n", GetSystemPathAux(SYSTEM_PATH_TEMPFILES)()); + return 0; +} diff --git a/test/testthread.cpp b/test/testthread.cpp new file mode 100644 index 00000000..b7c18a1e --- /dev/null +++ b/test/testthread.cpp @@ -0,0 +1,63 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "system/Thread.h" +#include "system/ThreadLocalStorage.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +static ThreadLocalStorage _tls; // just to test the ThreadLocalStorage class while we're at it + +class TestThread : public Thread +{ +public: + TestThread() {/* empty */} + + virtual status_t MessageReceivedFromOwner(const MessageRef & msgRef, uint32) + { + // Some sanity checking + if (this != Thread::GetCurrentThread()) printf("TestThread: Error, GetCurrentThread() should return %p, actually returned %p\n", this, Thread::GetCurrentThread()); + if (IsCallerInternalThread() == false) printf("TestThread: Error, IsCallerInternalThread() returned false!\n"); + + bool hasValue = (_tls.GetThreadLocalObject() != NULL); + int * tls = _tls.GetOrCreateThreadLocalObject(); + if (tls) + { + if (hasValue == false) *tls = 12; + if (msgRef()) {printf("threadTLS=%i: Internal thread saw: ", *tls); msgRef()->PrintToStream(); return B_NO_ERROR;} + else {printf("threadTLS=%i: Internal thread exiting\n", *tls); return B_ERROR;} + } + else {WARN_OUT_OF_MEMORY; return B_ERROR;} + } +}; + +// This program exercises the Thread class. +int main(void) +{ + CompleteSetupSystem css; + + int * tls = _tls.GetOrCreateThreadLocalObject(); + if (tls) *tls = 3; + else WARN_OUT_OF_MEMORY; + + TestThread t; + printf("main thread: TestThread is %p (main thread is %p/%i)\n", &t, Thread::GetCurrentThread(), t.IsCallerInternalThread()); + if (t.StartInternalThread() == B_NO_ERROR) + { + char buf[256]; + while(fgets(buf, sizeof(buf), stdin)) + { + if (buf[0] == 'q') break; + MessageRef msg(GetMessageFromPool(1234)); + msg()->AddString("str", buf); + t.SendMessageToInternalThread(msg); + } + } + + printf("Cleaning up (mainTLS=%i)...\n", *_tls.GetThreadLocalObject()); // make sure the TLS hasn't been changed by the other thread + t.SendMessageToInternalThread(MessageRef()); // ask internal thread to shut down + t.WaitForInternalThreadToExit(); + printf("Bye!\n"); + return 0; +} diff --git a/test/testthreadpool.cpp b/test/testthreadpool.cpp new file mode 100644 index 00000000..1d461bd6 --- /dev/null +++ b/test/testthreadpool.cpp @@ -0,0 +1,51 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "system/ThreadPool.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +class TestClient : public IThreadPoolClient +{ +public: + TestClient() : IThreadPoolClient(NULL) {/* empty */} + + virtual void MessageReceivedFromThreadPool(const MessageRef & msgRef, uint32 numLeft) + { + char buf[20]; + printf("MessageFromOwner called in thread %s, msgRef=%p (what=" UINT32_FORMAT_SPEC"), numLeft=" UINT32_FORMAT_SPEC"\n", muscle_thread_id::GetCurrentThreadID().ToString(buf), msgRef(), msgRef()?msgRef()->what:666, numLeft); + Snooze64(200000); + } +}; + +// This program exercises the Thread class. +int main(void) +{ + CompleteSetupSystem css; + + printf("Creating pool...\n"); fflush(stdout); + + ThreadPool pool; + { + printf("Sending TestClient Messages to pool...\n"); fflush(stdout); + TestClient tcs[10]; + for (uint32 i=0; iAddString("hey", "dude"); + tcs[j].SendMessageToThreadPool(msg); + } + } + + printf("Waiting for Messages to complete...\n"); + for (uint32 i=0; i +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" +#include "util/StringTokenizer.h" + +using namespace muscle; + +static float GetDiffHours(uint64 t1, uint64 t2) {return (float) ((double)((int64)t1 - (int64) t2))/(60.0 * 60.0 * (double)MICROS_PER_SECOND);} + +// This program is used to test out muscle's time/date interpretation functions +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message argsMsg; + ParseArgs(argc, argv, argsMsg); + HandleStandardDaemonArgs(argsMsg); + + const uint64 epoch = 0; + printf("epoch time (UTC) = %s\n", GetHumanReadableTimeString(epoch, MUSCLE_TIMEZONE_UTC)()); + printf("epoch time (loc) = %s\n", GetHumanReadableTimeString(epoch, MUSCLE_TIMEZONE_LOCAL)()); + + const uint64 nowLocal = GetCurrentTime64(MUSCLE_TIMEZONE_LOCAL); + const String nowLocalStr = GetHumanReadableTimeString(nowLocal, MUSCLE_TIMEZONE_LOCAL); + printf("NOW (Local) = " UINT64_FORMAT_SPEC " = %s\n", nowLocal, nowLocalStr()); + const uint64 reparsedLocal = ParseHumanReadableTimeString(nowLocalStr(), MUSCLE_TIMEZONE_LOCAL); + printf(" reparsed = " UINT64_FORMAT_SPEC " (diff=%.1f hours)\n", reparsedLocal, GetDiffHours(reparsedLocal, nowLocal)); + + const uint64 nowUTC = GetCurrentTime64(MUSCLE_TIMEZONE_UTC); + const String nowUTCStr = GetHumanReadableTimeString(nowUTC, MUSCLE_TIMEZONE_LOCAL); + printf("NOW (UTC) = " UINT64_FORMAT_SPEC " = %s\n (or, in local terms, %s)\n", nowUTC, nowUTCStr(), GetHumanReadableTimeString(nowUTC, MUSCLE_TIMEZONE_UTC)()); + + const uint64 reparsedUTC = ParseHumanReadableTimeString(nowUTCStr(), MUSCLE_TIMEZONE_LOCAL); + printf(" reparsed = " UINT64_FORMAT_SPEC " (diff=%.1f hours)\n", reparsedUTC, GetDiffHours(reparsedUTC, nowUTC)); + + printf("The offset between local time and UTC is %.1f hours.\n", GetDiffHours(nowLocal, nowUTC)); + + HumanReadableTimeValues v; + if (GetHumanReadableTimeValues(nowLocal, v, MUSCLE_TIMEZONE_LOCAL) == B_NO_ERROR) printf("HRTV(local) = [%s]\n", v.ExpandTokens("%T DoW=%w (%t) (%f) (%q) (micro=%x) (rand=%r)")()); + else printf("Error getting human readable time values for local!\n"); + if (GetHumanReadableTimeValues(nowUTC, v, MUSCLE_TIMEZONE_LOCAL) == B_NO_ERROR) printf("HRTV(UTC) = [%s]\n", v.ExpandTokens("%T DoW=%w (%t) (%f) (%q) (micro=%x) (rand=%r)")()); + else printf("Error getting human readable time values for UTC!\n"); + + // Interactive time interval testing + if ((argc > 1)&&(strcmp(argv[1], "testintervals") == 0)) + { + while(1) + { + printf("Enter micros, minPrecision(micros): "); fflush(stdout); + char buf[512]; if (fgets(buf, sizeof(buf), stdin) == NULL) buf[0] = '\0'; + StringTokenizer tok(buf, ", "); + const char * m = tok(); + const char * p = tok(); + if (m) + { + uint64 micros = Atoull(m); + uint64 precision = p ? Atoull(p) : 0; + printf(" You entered " UINT64_FORMAT_SPEC" microseconds, minimum precision " UINT64_FORMAT_SPEC" microseconds.\n", micros, precision); + bool isAccurate; + String s = GetHumanReadableTimeIntervalString(micros, MUSCLE_NO_LIMIT, precision, &isAccurate); + printf("Result (%s) : %s\n", isAccurate?"Exact":"Approximate", s()); + } + } + } + + // Test intervals + printf("Testing time interval parsing and generation. This may take a little while...\n"); + const uint64 TEN_YEARS_IN_MICROSECONDS = ((uint64)315360)*NANOS_PER_SECOND; // yes, nanos per second is correct here! + uint64 delta = 1; + for (uint64 i=0; i<=TEN_YEARS_IN_MICROSECONDS; i+=delta) + { + bool isAccurate; + String s = GetHumanReadableTimeIntervalString(i, MUSCLE_NO_LIMIT, 0, &isAccurate); + if (isAccurate == false) printf("Error, string [%s] is not accurate for i=" UINT64_FORMAT_SPEC".\n", s(), i); + uint64 t = ParseHumanReadableTimeIntervalString(s); + //printf(" %llu -> %s -> %llu\n", i, s(), t); + if (t != i) printf("Error, Recovered time " UINT64_FORMAT_SPEC" does not match original time " UINT64_FORMAT_SPEC" (string=[%s])\n", t, i, s()); + delta++; + } + + return 0; +} diff --git a/test/testtuple.cpp b/test/testtuple.cpp new file mode 100644 index 00000000..4586bb64 --- /dev/null +++ b/test/testtuple.cpp @@ -0,0 +1,273 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "support/Rect.h" +#include "util/String.h" + +using namespace muscle; + +#define TEST(x) if (!(x)) printf("Test failed, line %i\n",__LINE__) + +typedef Tuple<3,int> MyTuple; + +static void PrintMyTuple(const MyTuple & a); +static void PrintMyTuple(const MyTuple & a) +{ + printf("{%i,%i,%i}", a[0], a[1], a[2]); +} + +static void PrintEquation1(const char * op, const MyTuple & a, const MyTuple & b, const MyTuple & c); +static void PrintEquation1(const char * op, const MyTuple & a, const MyTuple & b, const MyTuple & c) +{ + PrintMyTuple(a); + printf(" %s ", op); + PrintMyTuple(b); + printf(" = "); + PrintMyTuple(c); + printf("\n"); +} + +static uint32 counter = 0; + +// test subclassing +class MyTupleSubclass : public Tuple<5, float> +{ +public: + MyTupleSubclass() {/* empty */} + + MyTupleSubclass(float first) + { + for (uint32 i=0; i +{ +public: + StringTupleSubclass() {/* empty */} + + StringTupleSubclass(const String & s1, const String & s2, const String & s3) + { + (*this)[0] = s1; + (*this)[1] = s2; + (*this)[2] = s3; + } + + void PrintToStream() const + { + printf("{"); + for (uint32 i=0; i +{ +public: + FiveTuple() {/* empty */} +}; +DECLARE_ALL_TUPLE_OPERATORS(FiveTuple, int); + +static void PrintFiveTuple(const FiveTuple & ft) +{ + for (uint32 i=0; i>rightShift); + printf("\n"); + } + + printf("\nTest 1, with tuple using 3 ints\n"); + { + MyTuple a; + a[0] = 5; a[1] = 10; a[2] = 15; + MyTuple b; + b[0] = 1; b[1] = 2; b[2] = -3; + + b.Replace(-3, -4); + + printf("a="); PrintMyTuple(a); printf("\n"); + printf("a+3="); PrintMyTuple(a+3); printf("\n"); + printf("a-3="); PrintMyTuple(a-3); printf("\n"); + printf("a*3="); PrintMyTuple(a*3); printf("\n"); + printf("a/3="); PrintMyTuple(a/3); printf("\n"); + + PrintEquation1("+", a, b, a+b); + PrintEquation1("-", a, b, a-b); + PrintEquation1("*", a, b, a*b); + PrintEquation1("/", a, b, a/b); + printf("a.b=%i\n", a.DotProduct(b)); + printf("b.a=%i\n", b.DotProduct(a)); + PrintEquation1("++", a, b, a+b+b); + PrintEquation1("+-", a, b, a+b-b); + PrintEquation1("u-", a, a, -a); + printf("max value in a is %i, max in b is %i\n", a.GetMaximumValue(), b.GetMaximumValue()); + printf("min value in a is %i, min in b is %i\n", a.GetMinimumValue(), b.GetMinimumValue()); + } + printf("\n\nTest 2, with subclass using 5 floats\n"); + { + MyTupleSubclass a(5.0f); + MyTupleSubclass b(1.0f); + + printf("a="); a.PrintToStream(); printf("\n"); + printf("a+3="); (a+3).PrintToStream(); printf("\n"); + printf("a-3="); (a-3).PrintToStream(); printf("\n"); + printf("a*3="); (a*3).PrintToStream(); printf("\n"); + printf("a/3="); (a/3).PrintToStream(); printf("\n"); + + PrintEquation2("+", a, b, a+b); + PrintEquation2("-", a, b, a-b); + PrintEquation2("*", a, b, a*b); + PrintEquation2("/", a, b, a/b); + PrintEquation2("++", a, b, a+b+b); + PrintEquation2("+-", a, b, a+b-b); + PrintEquation2("u-", a, a, -a); + printf("a.b=%f\n", a.DotProduct(b)); + printf("b.a=%f\n", b.DotProduct(a)); + printf("max value in a is %f, max in b is %f\n", a.GetMaximumValue(), b.GetMaximumValue()); + printf("min value in a is %f, min in b is %f\n", a.GetMinimumValue(), b.GetMinimumValue()); + } + printf("\n\nTest 3, with tuple using 3 strings\n"); + { + StringTupleSubclass a("red", "green", "blue"); + StringTupleSubclass b("light", "grass", "rinse"); + + printf("a="); a.PrintToStream(); printf("\n"); + printf("a+'b'="); (a+"b").PrintToStream(); printf("\n"); + printf("a-'b'="); (a-"b").PrintToStream(); printf("\n"); + + PrintEquation3("+", a, b, a+b); + PrintEquation3("-", a, b, a-b); + PrintEquation3("++", a, b, a+b+b); + PrintEquation3("+-", a, b, a+b-b); + printf("max value in a is %s, max in b is %s\n", a.GetMaximumValue()(), b.GetMaximumValue()()); + printf("min value in a is %s, min in b is %s\n", a.GetMinimumValue()(), b.GetMinimumValue()()); + } + printf("\n\nTest 4, using Points\n"); + { + Point a(5.0f, 6.0f); + Point b(2.0f, 3.0f); + + printf("a="); a.PrintToStream(); printf("\n"); + printf("a+3="); (a+3.0f).PrintToStream(); printf("\n"); + printf("a-3="); (a-3.0f).PrintToStream(); printf("\n"); + printf("a*3="); (a*3.0f).PrintToStream(); printf("\n"); + printf("a/3="); (a/3.0f).PrintToStream(); printf("\n"); + + PrintEquation4("+", a, b, a+b); + PrintEquation4("-", a, b, a-b); + PrintEquation4("*", a, b, a*b); + PrintEquation4("/", a, b, a/b); + PrintEquation4("++", a, b, a+b+b); + PrintEquation4("+-", a, b, a+b-b); + PrintEquation4("u-", a, a, -a); + printf("a.b=%f\n", a.DotProduct(b)); + printf("b.a=%f\n", b.DotProduct(a)); + printf("max value in a is %f, max in b is %f\n", a.GetMaximumValue(), b.GetMaximumValue()); + printf("min value in a is %f, min in b is %f\n", a.GetMinimumValue(), b.GetMinimumValue()); + } + printf("\n\nTest 5, using Rects\n"); + { + Rect a(5,6,7,8); + Rect b(5,4,3,2); + + printf("a="); a.PrintToStream(); printf("\n"); + printf("a+3="); (a+3.0f).PrintToStream(); printf("\n"); + printf("a-3="); (a-3.0f).PrintToStream(); printf("\n"); + printf("a*3="); (a*3.0f).PrintToStream(); printf("\n"); + printf("a/3="); (a/3.0f).PrintToStream(); printf("\n"); + + PrintEquation5("+", a, b, a+b); + PrintEquation5("-", a, b, a-b); + PrintEquation5("*", a, b, a*b); + PrintEquation5("/", a, b, a/b); + PrintEquation5("++", a, b, a+b+b); + PrintEquation5("+-", a, b, a+b-b); + PrintEquation5("u-", a, a, -a); + printf("a.b=%f\n", a.DotProduct(b)); + printf("b.a=%f\n", b.DotProduct(a)); + printf("max value in a is %f, max in b is %f\n", a.GetMaximumValue(), b.GetMaximumValue()); + printf("min value in a is %f, min in b is %f\n", a.GetMinimumValue(), b.GetMinimumValue()); + } + return 0; +} diff --git a/test/testtypedefs.cpp b/test/testtypedefs.cpp new file mode 100644 index 00000000..9d192fc2 --- /dev/null +++ b/test/testtypedefs.cpp @@ -0,0 +1,54 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include "support/MuscleSupport.h" + +using namespace muscle; + +template inline void testWidth(const T & val, int expectedSize, const char * name) +{ + printf("%s: size=%i expected %i (%s)\n", name, (int) sizeof(val), expectedSize, (sizeof(val) == expectedSize) ? "pass" : "ERROR, WRONG SIZE!"); +} + +static void testStr(const char * title, const char * gen, const char * expected) +{ + if (strcmp(gen, expected) == 0) printf("%s: pass (%s)\n", title, gen); + else printf("%s: ERROR, got [%s], expected [%s]\n", title, gen, expected); +} + +// This program makes sure that the MUSCLE typedefs have the proper bit-widths. +int main(int, char **) +{ + printf("Testing MUSCLE typedefs to make sure they are defined to the correct bit-widths...\n"); + if (sizeof(void *) == sizeof(uintptr)) printf("uintptr: pass, sizeof(uintptr)=%i, sizeof(void *)=%i\n", (int)sizeof(uintptr), (int)sizeof(void *)); + else printf("uintptr: ERROR, sizeof(uintptr)=%i, sizeof(void *)=%i\n", (int)sizeof(uintptr), (int)sizeof(void *)); + + {int8 i = 0; testWidth(i, 1, " int8");} + {uint8 i = 0; testWidth(i, 1, " uint8");} + {int16 i = 0; testWidth(i, 2, " int16");} + {uint16 i = 0; testWidth(i, 2, "uint16");} + {int32 i = 0; testWidth(i, 4, " int32");} + {uint32 i = 0; testWidth(i, 4, "uint32");} + {int64 i = 0; testWidth(i, 8, " int64");} + {uint64 i = 0; testWidth(i, 8, "uint64");} + {float i = 0; testWidth(i, 4, " float");} + {double i = 0; testWidth(i, 8, "double");} + {uintptr i= 0; testWidth(i, sizeof(void *), "uintptr");} + printf("Typedef bit-width testing complete.\n"); + + printf("\nTesting MUSCLE sprintf() macros to make sure they are output the correct strings...\n"); + char buf[128]; + {sprintf(buf, "%i %i %i %i", (int8)1, (int8)2, (int8)3, (int8)4); testStr(" int8", buf, "1 2 3 4");} + {sprintf(buf, "%i %i %i %i", (uint8)1, (uint8)2, (uint8)3, (uint8)4); testStr(" uint8", buf, "1 2 3 4");} + {sprintf(buf, "%i %i %i %i", (int16)1, (int16)2, (int16)3, (int16)4); testStr(" int16", buf, "1 2 3 4");} + {sprintf(buf, "%i %i %i %i", (uint16)1, (uint16)2, (uint16)3, (uint16)4); testStr("uint16", buf, "1 2 3 4");} + {sprintf(buf, INT32_FORMAT_SPEC " " INT32_FORMAT_SPEC " " INT32_FORMAT_SPEC " " INT32_FORMAT_SPEC, (int32)1, (int32)2, (int32)3, (int32)4); testStr(" int32", buf, "1 2 3 4");} + {sprintf(buf, UINT32_FORMAT_SPEC " " UINT32_FORMAT_SPEC " " UINT32_FORMAT_SPEC " " UINT32_FORMAT_SPEC, (uint32)1, (uint32)2, (uint32)3, (uint32)4); testStr("uint32", buf, "1 2 3 4");} + {sprintf(buf, XINT32_FORMAT_SPEC " " XINT32_FORMAT_SPEC " " XINT32_FORMAT_SPEC " " XINT32_FORMAT_SPEC, (int32)26, (int32)27, (int32)28, (int32)29); testStr("xint32", buf, "1a 1b 1c 1d");} + {sprintf(buf, INT64_FORMAT_SPEC " " INT64_FORMAT_SPEC " " INT64_FORMAT_SPEC " " INT64_FORMAT_SPEC, (int64)1, (int64)2, (int64)3, (int64)4); testStr(" int64", buf, "1 2 3 4");} + {sprintf(buf, UINT64_FORMAT_SPEC " " UINT64_FORMAT_SPEC " " UINT64_FORMAT_SPEC " " UINT64_FORMAT_SPEC, (uint64)1, (uint64)2, (uint64)3, (uint64)4); testStr("uint64", buf, "1 2 3 4");} + {sprintf(buf, "%.1f %.1f %.1f %.1f", 1.5f, 2.5f, 3.5f, 4.5f); testStr(" float", buf, "1.5 2.5 3.5 4.5");} + {sprintf(buf, "%.1f %.1f %.1f %.1f", 1.5, 2.5, 3.5, 4.5); testStr("double", buf, "1.5 2.5 3.5 4.5");} + printf("String format testing complete.\n"); + return 0; +} diff --git a/test/testudp.cpp b/test/testudp.cpp new file mode 100644 index 00000000..252bfa0e --- /dev/null +++ b/test/testudp.cpp @@ -0,0 +1,230 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include + +#include "dataio/UDPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "iogateway/RawDataMessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "system/SetupSystem.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +// This is a text based UDP test client. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + printf("Note: MUSCLE_EXPECTED_MTU_SIZE_BYTES=%i\n", MUSCLE_EXPECTED_MTU_SIZE_BYTES); + printf("Note: MUSCLE_IP_HEADER_SIZE_BYTES=%i\n", MUSCLE_IP_HEADER_SIZE_BYTES); + printf("Note: MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES=%i\n", MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES); + printf("Note: MUSCLE_UDP_HEADER_SIZE_BYTES=%i\n", (int) MUSCLE_UDP_HEADER_SIZE_BYTES); + printf("Note: MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET=%i\n", (int) MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET); + + Message args; (void) ParseArgs(argc, argv, args); + const char * target = args.GetCstr("sendto", "localhost"); + const char * bindto = args.GetCstr("listen", "3960"); + bool useRawGateway = args.HasName("raw"); + if (useRawGateway) printf("Using RawDataMessageIOGateway...\n"); + + ConstSocketRef s = CreateUDPSocket(); + if (s() == NULL) + { + printf("Error creating UDP Socket!\n"); + return 10; + } + + uint16 bindPort = atoi(bindto); + uint16 actualPort; + if (BindUDPSocket(s, bindPort, &actualPort) == B_NO_ERROR) printf("Bound socket to port %u\n", actualPort); + else printf("Error, couldn't bind to port %u\n", bindPort); + + MessageIOGateway gw; + RawDataMessageIOGateway rgw; + UDPSocketDataIO * udpIO = new UDPSocketDataIO(s, false); + udpIO->SetSendDestination(IPAddressAndPort(target, 3960, true)); + printf("Set UDP send destination to [%s]\n", udpIO->GetSendDestination().ToString()()); + + // Only one of these will actually be used + gw.SetDataIO(DataIORef(udpIO)); + rgw.SetDataIO(DataIORef(udpIO)); + AbstractMessageIOGateway * agw = useRawGateway ? (AbstractMessageIOGateway *)&rgw : (AbstractMessageIOGateway *)&gw; + + char text[1000] = ""; + QueueGatewayMessageReceiver inQueue; + SocketMultiplexer multiplexer; + printf("UDP Event loop starting...\n"); + while(s()) + { + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + + if (agw->HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + multiplexer.RegisterSocketForReadReady(STDIN_FILENO); + + while(s()) + { + if (multiplexer.WaitForEvents() < 0) printf("testudp: WaitForEvents() failed!\n"); + if (multiplexer.IsSocketReadyForRead(STDIN_FILENO)) + { + if (fgets(text, sizeof(text), stdin) == NULL) text[0] = '\0'; + char * ret = strchr(text, '\n'); if (ret) *ret = '\0'; + } + + if (text[0]) + { + printf("You typed: [%s]\n",text); + bool send = true; + MessageRef ref = GetMessageFromPool(useRawGateway?PR_COMMAND_RAW_DATA:0); + + if (useRawGateway) ref()->AddFlat(PR_NAME_DATA_CHUNKS, ParseHexBytes(text)); + else + { + switch(text[0]) + { + case 'm': + ref()->what = MAKETYPE("umsg"); + ref()->AddString(PR_NAME_KEYS, &text[2]); + ref()->AddString("info", "This is a user message"); + break; + + case 's': + ref()->what = PR_COMMAND_SETDATA; + ref()->AddMessage(&text[2], Message(MAKETYPE("HELO"))); + break; + + case 'k': + ref()->what = PR_COMMAND_KICK; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'b': + ref()->what = PR_COMMAND_ADDBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'B': + ref()->what = PR_COMMAND_REMOVEBANS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'g': + ref()->what = PR_COMMAND_GETDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'G': + ref()->what = PR_COMMAND_GETDATATREES; + ref()->AddString(PR_NAME_KEYS, &text[2]); + ref()->AddString(PR_NAME_TREE_REQUEST_ID, "Tree ID!"); + break; + + case 'q': + send = false; + s.Reset(); + break; + + case 'p': + ref()->what = PR_COMMAND_SETPARAMETERS; + ref()->AddString(&text[2], ""); + break; + + case 'P': + ref()->what = PR_COMMAND_GETPARAMETERS; + break; + + case 'd': + ref()->what = PR_COMMAND_REMOVEDATA; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 'D': + ref()->what = PR_COMMAND_REMOVEPARAMETERS; + ref()->AddString(PR_NAME_KEYS, &text[2]); + break; + + case 't': + { + // test all data types + ref()->what = 1234; + ref()->AddString("String", "this is a string"); + ref()->AddInt8("Int8", 123); + ref()->AddInt8("-Int8", -123); + ref()->AddInt16("Int16", 1234); + ref()->AddInt16("-Int16", -1234); + ref()->AddInt32("Int32", 12345); + ref()->AddInt32("-Int32", -12345); + ref()->AddInt64("Int64", 123456789); + ref()->AddInt64("-Int64", -123456789); + ref()->AddBool("Bool", true); + ref()->AddBool("-Bool", false); + ref()->AddFloat("Float", 1234.56789f); + ref()->AddFloat("-Float", -1234.56789f); + ref()->AddDouble("Double", 1234.56789); + ref()->AddDouble("-Double", -1234.56789); + ref()->AddPointer("Pointer", ref()); + ref()->AddFlat("Flat", *ref()); + char data[] = "This is some data"; + ref()->AddData("Flat", B_RAW_TYPE, data, sizeof(data)); + } + break; + + default: + printf("Sorry, wot?\n"); + send = false; + break; + } + } + + if (send) + { + printf("Sending message...\n"); +// ref()->PrintToStream(); + agw->AddOutgoingMessage(ref); + } + text[0] = '\0'; + } + + bool reading = multiplexer.IsSocketReadyForRead(fd); + bool writing = multiplexer.IsSocketReadyForWrite(fd); + bool writeError = ((writing)&&(agw->DoOutput() < 0)); + bool readError = ((reading)&&(agw->DoInput(inQueue) < 0)); + if ((readError)||(writeError)) + { + printf("%s: Connection closed, exiting.\n", readError?"Read Error":"Write Error"); + s.Reset(); + } + + MessageRef incoming; + while(inQueue.RemoveHead(incoming) == B_NO_ERROR) + { + printf("Heard message from server:-----------------------------------\n"); + incoming()->PrintToStream(); + printf("-------------------------------------------------------------\n"); + } + + if ((reading == false)&&(writing == false)) break; + + multiplexer.RegisterSocketForReadReady(fd); + if (agw->HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(fd); + multiplexer.RegisterSocketForReadReady(STDIN_FILENO); + } + } + + if (agw->HasBytesToOutput()) + { + printf("Waiting for all pending messages to be sent...\n"); + while((agw->HasBytesToOutput())&&(agw->DoOutput() >= 0)) {printf ("."); fflush(stdout);} + } + printf("\n\nBye!\n"); + + return 0; +} diff --git a/test/testzip.cpp b/test/testzip.cpp new file mode 100644 index 00000000..2359ee27 --- /dev/null +++ b/test/testzip.cpp @@ -0,0 +1,53 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "zlib/ZipFileUtilityFunctions.h" + +using namespace muscle; + +#define COMMAND_HELLO 0x1234 +#define COMMAND_GOODBYE 0x4321 + +// This program tests the ZipFileUtilityFunctions functions +int main(int argc, char ** argv) +{ + if (argc <= 1) + { + printf("testzip somezipfiletoread.zip [newzipfiletowrite.zip] [namesonly]\n"); + return 0; + } + + bool loadData = true; + for (int i=1; iPrintToStream(); + + if (argc > 2) + { + if (loadData) + { + printf("\n\n... writing new .zip file [%s]\n", argv[2]); + if (WriteZipFile(argv[2], *msg()) == B_NO_ERROR) printf("Creation of [%s] succeeded!\n", argv[2]); + else printf("Creation of [%s] FAILED!\n", argv[2]); + } + else printf("There's no point in writing output file [%s], since I never loaded the .zip data anyway.\n", argv[2]); + } + } + else printf("Error reading .zip file [%s]\n", argv[1]); + + return 0; +} diff --git a/test/udpproxy.cpp b/test/udpproxy.cpp new file mode 100644 index 00000000..65833e6c --- /dev/null +++ b/test/udpproxy.cpp @@ -0,0 +1,169 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "dataio/UDPSocketDataIO.h" +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/MiscUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +static const int DEFAULT_PORT = 8000; // LX-300's default port for OSC + +static status_t ReadIncomingData(const String & desc, DataIO & readIO, const SocketMultiplexer & multiplexer, Queue & outQ) +{ + if (multiplexer.IsSocketReadyForRead(readIO.GetReadSelectSocket().GetFileDescriptor())) + { + uint8 buf[4096]; + int32 ret = readIO.Read(buf, sizeof(buf)); + if (ret > 0) + { + LogTime(MUSCLE_LOG_TRACE, "Read " INT32_FORMAT_SPEC" bytes from %s:\n", ret, desc()); + LogHexBytes(MUSCLE_LOG_TRACE, buf, ret); + + ByteBufferRef toNetworkBuf = GetByteBufferFromPool(ret, buf); + if (toNetworkBuf()) (void) outQ.AddTail(toNetworkBuf); + } + else if (ret < 0) {LogTime(MUSCLE_LOG_ERROR, "Error, readIO.Read() returned %i\n", ret); return B_ERROR;} + } + return B_NO_ERROR; +} + +static status_t WriteOutgoingData(const String & desc, DataIO & writeIO, const SocketMultiplexer & multiplexer, Queue & outQ, uint32 & writeIdx) +{ + if (multiplexer.IsSocketReadyForWrite(writeIO.GetWriteSelectSocket().GetFileDescriptor())) + { + while(outQ.HasItems()) + { + ByteBufferRef & firstBuf = outQ.Head(); + uint32 bufSize = firstBuf()->GetNumBytes(); + if (writeIdx >= bufSize) + { + outQ.RemoveHead(); + writeIdx = 0; + } + else + { + int32 ret = writeIO.Write(firstBuf()->GetBuffer()+writeIdx, firstBuf()->GetNumBytes()-writeIdx); + if (ret > 0) + { + writeIO.FlushOutput(); + LogTime(MUSCLE_LOG_TRACE, "Wrote " INT32_FORMAT_SPEC" bytes to %s:\n", ret, desc()); + LogHexBytes(MUSCLE_LOG_TRACE, firstBuf()->GetBuffer()+writeIdx, ret); + writeIdx += ret; + } + else if (ret < 0) LogTime(MUSCLE_LOG_ERROR, "Error, writeIO.Write() returned %i\n", ret); + } + } + } + return B_NO_ERROR; +} + +static status_t DoSession(const String aDesc, DataIO & aIO, const String & bDesc, DataIO & bIO) +{ + Queue outgoingBData; + Queue outgoingAData; + uint32 bIndex = 0, aIndex = 0; + SocketMultiplexer multiplexer; + + while(true) + { + int aReadFD = aIO.GetReadSelectSocket().GetFileDescriptor(); + int bReadFD = bIO.GetReadSelectSocket().GetFileDescriptor(); + int aWriteFD = aIO.GetWriteSelectSocket().GetFileDescriptor(); + int bWriteFD = bIO.GetWriteSelectSocket().GetFileDescriptor(); + + multiplexer.RegisterSocketForReadReady(aReadFD); + multiplexer.RegisterSocketForReadReady(bReadFD); + if (outgoingAData.HasItems()) multiplexer.RegisterSocketForWriteReady(aWriteFD); + if (outgoingBData.HasItems()) multiplexer.RegisterSocketForWriteReady(bWriteFD); + + if (multiplexer.WaitForEvents() >= 0) + { + if (ReadIncomingData( aDesc, aIO, multiplexer, outgoingBData) != B_NO_ERROR) return B_ERROR; + if (ReadIncomingData( bDesc, bIO, multiplexer, outgoingAData) != B_NO_ERROR) return B_ERROR; + if (WriteOutgoingData(aDesc, aIO, multiplexer, outgoingAData, aIndex) != B_NO_ERROR) return B_ERROR; + if (WriteOutgoingData(bDesc, bIO, multiplexer, outgoingBData, bIndex) != B_NO_ERROR) return B_ERROR; + } + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Error, WaitForEvents() failed!\n"); + return B_ERROR; + } + } +} + +static void LogUsage() +{ + Log(MUSCLE_LOG_INFO, "Usage: udpproxy target=192.168.1.101:8000 [listen=9000] target=192.168.1.2:8000 [listen=9001]\n"); +} + +// This program acts as a proxy to redirect UDP packets to a further source (and back) +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Message args; (void) ParseArgs(argc, argv, args); + (void) HandleStandardDaemonArgs(args); + + if (args.HasName("help")) + { + LogUsage(); + return 10; + } + + uint16 listenPorts[2] = {DEFAULT_PORT, DEFAULT_PORT+1}; + IPAddressAndPort targets[2]; + { + uint16 targetPorts[2] = {DEFAULT_PORT, DEFAULT_PORT+1}; + String hostNames[2]; + for (uint32 i=0; i<2; i++) + { + if (ParseConnectArg(args, "target", hostNames[i], targetPorts[i], false, i) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Error, couldn't parse target argument #%i\n", i+1); + LogUsage(); + return 10; + } + (void) ParsePortArg(args, "listen", listenPorts[i], i); + + targets[i] = IPAddressAndPort(GetHostByName(hostNames[i]()), targetPorts[i]); + if (IsValidAddress(targets[i].GetIPAddress())) LogTime(MUSCLE_LOG_INFO, "Sending to target %s, listening on port %u\n", targets[i].ToString()(), listenPorts[i]); + else + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't resolve hostname [%s]\n", hostNames[i]()); + return 10; + } + } + } + + DataIORef udpIOs[2]; + for (uint32 i=0; i<2; i++) + { + ConstSocketRef udpSock = CreateUDPSocket(); + if (udpSock() == NULL) + { + LogTime(MUSCLE_LOG_ERROR, "Creating UDP socket failed!\n"); + return 10; + } + if (BindUDPSocket(udpSock, listenPorts[i], &listenPorts[i]) != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_ERROR, "Failed to bind UDP socket to port %u!\n", listenPorts[i]); + return 10; + } + UDPSocketDataIO * dio = newnothrow UDPSocketDataIO(udpSock, false); + if (dio == NULL) + { + WARN_OUT_OF_MEMORY; + return 10; + } + dio->SetSendDestination(targets[i]); + udpIOs[i].SetRef(dio); + } + + status_t ret = DoSession(targets[0].ToString(), *udpIOs[0](), targets[1].ToString(), *udpIOs[1]()); + LogTime(MUSCLE_LOG_INFO, "udpproxy exiting%s!\n", (ret==B_NO_ERROR)?"":" with an error"); + return 0; +} diff --git a/test/udpproxy.vcproj b/test/udpproxy.vcproj new file mode 100644 index 00000000..d9d42a12 --- /dev/null +++ b/test/udpproxy.vcproj @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/uploadstress.cpp b/test/uploadstress.cpp new file mode 100644 index 00000000..517d925c --- /dev/null +++ b/test/uploadstress.cpp @@ -0,0 +1,84 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include +#include +#include +#include + +#include "dataio/TCPSocketDataIO.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "system/SetupSystem.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +using namespace muscle; + +#define TEST(x) if ((x) != B_NO_ERROR) printf("Test failed, line %i\n",__LINE__) + +// This client just uploads a bunch of stuff to the server, trying to batter it down. +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + const char * hostName = "localhost"; + int port = 0; + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = atoi(argv[2]); + if (port <= 0) port = 2960; + + QueueGatewayMessageReceiver inQueue; + SocketMultiplexer multiplexer; + while(true) + { + uint32 bufCount=0; + ConstSocketRef s = Connect(hostName, (uint16)port, "uploadstress"); + if (s() == NULL) return 10; + + MessageIOGateway gw; + gw.SetDataIO(DataIORef(new TCPSocketDataIO(s, false))); + while(true) + { + int fd = s.GetFileDescriptor(); + multiplexer.RegisterSocketForReadReady(fd); + multiplexer.RegisterSocketForWriteReady(fd); + + if (multiplexer.WaitForEvents() < 0) printf("uploadstress: WaitForEvents() failed!\n"); + + bool reading = multiplexer.IsSocketReadyForRead(fd); + bool writing = multiplexer.IsSocketReadyForWrite(fd); + + if (gw.HasBytesToOutput() == false) + { + char buf[128]; + sprintf(buf, UINT32_FORMAT_SPEC, bufCount++); + printf("Adding message [%s]\n", buf); + + MessageRef smsg = GetMessageFromPool(PR_COMMAND_SETDATA); + Message data(1234); + data.AddString("nerf", "boy!"); + smsg()->AddMessage(buf, data); + gw.AddOutgoingMessage(smsg); + } + + bool writeError = ((writing)&&(gw.DoOutput() < 0)); + bool readError = ((reading)&&(gw.DoInput(inQueue) < 0)); + if ((readError)||(writeError)) + { + printf("Connection closed, exiting.\n"); + break; + } + + MessageRef incoming; + while(inQueue.RemoveHead(incoming) == B_NO_ERROR) + { + printf("Heard message from server:-----------------------------------\n"); + incoming()->PrintToStream(); + printf("-------------------------------------------------------------\n"); + } + } + } + + printf("\n\nBye!\n"); + return 0; +} diff --git a/test/win32client.cpp b/test/win32client.cpp new file mode 100644 index 00000000..7d1d936e --- /dev/null +++ b/test/win32client.cpp @@ -0,0 +1,115 @@ +#include "winsupport/Win32MessageTransceiverThread.h" +#include "iogateway/MessageIOGateway.h" +#include "reflector/StorageReflectConstants.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/StringTokenizer.h" +#include "system/SetupSystem.h" + +using namespace muscle; + +int main(int argc, char ** argv) +{ + CompleteSetupSystem css; + + Win32AllocateStdioConsole(); + + char * hostName = "localhost"; + uint16 port = 0; + + if (argc > 1) hostName = argv[1]; + if (argc > 2) port = (uint16) atoi(argv[2]); + if (port <= 0) port = 2960; + + Win32MessageTransceiverThread mtt(CreateEvent(0, false, false, 0), true); + + printf("Connecting to host=[%s] port=%i\n", hostName, port); + if ((mtt.StartInternalThread() == B_NO_ERROR)&&(mtt.AddNewConnectSession(hostName, port) == B_NO_ERROR)) + { + // The only thing this example needs to wait for notification on + // is the MessageTransceiverThread's signal-handle. A real-life + // application would probably need to wait on other things too, + // in which case those handles would go into this array also. + ::HANDLE waitObjects[] = {mtt.GetSignalHandle()}; + + bool keepGoing = true; + while(keepGoing) + { + // Wait for next event or timeout + int waitResult = WaitForMultipleObjects(ARRAYITEMS(waitObjects), waitObjects, false, 1000); + if (waitResult == WAIT_TIMEOUT) + { + MessageRef msg = GetMessageFromPool(PR_COMMAND_GETPARAMETERS); + if (msg()) + { + printf("Sending PR_COMMAND_GETPARAMETERS message to server...\n"); + mtt.SendMessageToSessions(msg); + } + } + else if (waitResult == WAIT_OBJECT_0) + { + // Hey, the Win32MessageTransceiverThread says he has something for us! + uint32 eventCode; + MessageRef msg; + while(mtt.GetNextEventFromInternalThread(eventCode, &msg) >= 0) + { + switch(eventCode) + { + case MTT_EVENT_INCOMING_MESSAGE: + printf("EVENT: A new message from the remote computer is ready to process. The Message is:\n"); + if (msg()) msg()->PrintToStream(); + break; + + case MTT_EVENT_SESSION_ACCEPTED: + printf("EVENT: A new session has been created by one of our factory objects\n"); + break; + + case MTT_EVENT_SESSION_ATTACHED: + printf("EVENT: A new session has been attached to the local server\n"); + break; + + case MTT_EVENT_SESSION_CONNECTED: + printf("EVENT: A session on the local server has completed its connection to the remote one\n"); + break; + + case MTT_EVENT_SESSION_DISCONNECTED: + printf("EVENT: A session on the local server got disconnected from its remote peer\n"); + keepGoing = false; // no sense in continuing now! + break; + + case MTT_EVENT_SESSION_DETACHED: + printf("EVENT: A session on the local server has detached (and been destroyed)\n"); + break; + + case MTT_EVENT_FACTORY_ATTACHED: + printf("EVENT: A ReflectSessionFactory object has been attached to the server\n"); + break; + + case MTT_EVENT_FACTORY_DETACHED: + printf("EVENT: A ReflectSessionFactory object has been detached (and been destroyed)\n"); + break; + + case MTT_EVENT_OUTPUT_QUEUES_DRAINED: + printf("EVENT: Output queues of sessions previously specified in RequestOutputQueuesDrainedNotification() have drained\n"); + break; + + case MTT_EVENT_SERVER_EXITED: + printf("EVENT: The ReflectServer event loop has terminated\n"); + break; + + default: + printf("EVENT: Unknown event code " UINT32_FORMAT_SPEC" from Win32MessageTransceiverThread!?\n", eventCode); + break; + } + } + } + } + printf("Shutting down MessageTransceiverThread...\n"); + } + else printf("Error, could not start Win32MessageTransceiverThread!\n"); + + mtt.Reset(); // important, to avoid race conditions in the destructor!! + + return 0; +} + diff --git a/test/win32client.vcproj b/test/win32client.vcproj new file mode 100644 index 00000000..2f67281e --- /dev/null +++ b/test/win32client.vcproj @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/util/BatchOperator.h b/util/BatchOperator.h new file mode 100644 index 00000000..5779a076 --- /dev/null +++ b/util/BatchOperator.h @@ -0,0 +1,190 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleBatchOperator_h +#define MuscleBatchOperator_h + +#include "util/NestCount.h" + +namespace muscle { + +/** This macro is the best way to declare a BatchGuard object */ +#ifdef _MSC_VER +#define DECLARE_BATCHGUARD(bo, ...) const BatchOperatorBase::BatchGuard & MUSCLE_UNIQUE_NAME = (bo).GetBatchGuard(__VA_ARGS__); (void) MUSCLE_UNIQUE_NAME +#else +# define DECLARE_BATCHGUARD(bo, args...) const BatchOperatorBase::BatchGuard & MUSCLE_UNIQUE_NAME = (bo).GetBatchGuard(args); (void) MUSCLE_UNIQUE_NAME +#endif + +/** This class includes the common functionality of a BatchOperator that is the same across all template + * instantiations of the BatchOperator family. It should not be used directly by user code. + */ +class BatchOperatorBase +{ +public: + /** This class is used by the DECLARE_BATCHGUARD() macro. Do not access it directly. */ + class BatchGuard + { + public: + /** Default constructor. */ + BatchGuard() {/* empty */} + }; + + /** Read-only access to our internal NestCount object, in case you are interested in querying its state. */ + const NestCount & GetNestCount() const {return _count;} + +protected: + /** This constructor is available only to our concrete BatchOperator subclasses. */ + BatchOperatorBase() {/* empty */} + + /** This object keeps track of how many layers into the batch's call tree we currently are. */ + NestCount _count; +}; + +/** This class is meant to represent an object that can do a series of operations more + * efficiently if it knows when the series is starting and ending. To use it, you would + * subclass this class, implement its BatchBegins() and BatchEnds() methods, and then place + * BatchGuard objects on the stack in the calling code, at the top of any routine + * that may be doing one or more operations in sequence. The benefit is that your BatchBegins() + * and BatchEnd() routines would then be automatically called at the proper times, and + * nesting/recursion is guaranteed to be handled correctly. + * + * This is a slightly more complex implementation of BatchOperator, in that it allows + * you to specify a BatchArgs argument that is associated with the batch. The BatchArgs + * argument may be of any type that you care to specify, and can be used either to + * convey information about that batch (e.g. the batch's name, for undo purposes), or + * to differentiate different types of batch operation (if you want to use batching + * of different kinds within the same object), or both. + */ +template class BatchOperator : public BatchOperatorBase +{ +public: + /** Default constructor. */ + BatchOperator() {/* empty */} + + /** Destructor */ + virtual ~BatchOperator() {/* empty */} + + /** You can explicitly call this at the beginning of a batch, although it's safer to instantiate a BatchGuard object + * to call this method for you; that way its destructor will be sure to call the matching EndOperationBatch() at the proper time. + * @param args The arguments to pass to BatchBegins(), if we are the outermost level of batch nesting. + * @returns true iff BatchBegins() was called, or false if it wasn't (because we were already in a batch) + */ + inline bool BeginOperationBatch(const BatchArgs & args = BatchArgs()) {if (_count.Increment()) {BatchBegins(args); return true;} else return false;} + + /** You should explicitly call this at the end of any batch that you explicitly called BeginOperationBatch() for. + * Note that it's safer to instantiate a BatchGuard object so that its destructor will call this method for you. + * @param args The arguments to pass to BatchEnds(), if we are the outermost level of batch nesting. + * @returns true iff BatchEnds() was called, or false if it wasn't (because we were already in a batch) + */ + inline bool EndOperationBatch(const BatchArgs & args = BatchArgs()) + { + bool ret = false; + if (_count.IsOutermost()) {BatchEnds(args); ret = true;} // note that BatchEnds() is called while _count is still non-zero! + (void) _count.Decrement(); + return ret; + } + +protected: + /** Called by BeginOperationBatch(), when the outermost level of a batch begins. + * You should implement it to do any setup steps that are required at the beginning of a series of operations. + * @param args A user-defined object representing arguments for setting up the batch. + */ + virtual void BatchBegins(const BatchArgs & args) = 0; + + /** Called by EndOperationBatch(), when the outermost level of a batch ends. + * You should implement it to do any final steps that are required at the end of a series of operations. + * @param args A user-defined object representing arguments for ending the batch. This will be the same + * object that was previously passed to BatchBegins(). + */ + virtual void BatchEnds(const BatchArgs & args) = 0; + +private: + class BatchGuardImp : public BatchGuard + { + public: + BatchGuardImp(BatchOperator & bop, const BatchArgs & args) : _bop(bop), _args(args) {(void) _bop.BeginOperationBatch(_args);} + BatchGuardImp(const BatchGuardImp & rhs) : BatchGuard(), _bop(rhs._bop), _args(rhs._args) {(void) _bop.BeginOperationBatch(_args);} + ~BatchGuardImp() {(void) _bop.EndOperationBatch(_args);} + + private: + BatchOperator & _bop; + BatchArgs _args; + }; + +public: + /** Returns a BatchGuard object that will keep this BatchOperator in a batch for as long as it exits. + * This method should only be called indirectly, via the DECLARE_BATCHGUARD(ba) macro. + * @param ba The BatchArgs object to pass to BeginOperationBatch(). + */ + BatchGuardImp GetBatchGuard(const BatchArgs & ba = BatchArgs()) {return BatchGuardImp(*this, ba);} +}; + +/** This class is meant to represent an object that can do a series of operations more + * efficiently if it knows when the series is starting and ending. To use it, you would + * subclass this class, implement its BatchBegins() and BatchEnds() methods, and then place + * BatchGuard objects on the stack in the calling code, at the top of any routine + * that may be doing one or more operations in sequence. The benefit is that your BatchBegins() + * and BatchEnd() routines would then be automatically called at the proper times, and + * nesting/recursion is guaranteed to be handled correctly. + * + * This is a simple implementation of BatchOperator that doesn't use any batch arguments. + */ +template <> class BatchOperator : public BatchOperatorBase +{ +public: + /** Default constructor. */ + BatchOperator() {/* empty */} + + /** Destructor. */ + virtual ~BatchOperator() {/* empty */} + + /** You can explicitly call this at the beginning of a batch, although it's safer to instantiate a BatchGuard object + * to call this method for you; that way its destructor will be sure to call the matching EndOperationBatch() at the proper time. + * @returns true iff BatchBegins() was called, or false if it wasn't (because we were already in a batch) + */ + inline bool BeginOperationBatch() {if (_count.Increment()) {BatchBegins(); return true;} else return false;} + + /** You should explicitly call this at the end of any batch that you explicitly called BeginOperationBatch() for. + * Note that it's safer to instantiate a BatchGuard object so that its destructor will call this method for you. + * @returns true iff BatchEnds() was called, or false if it wasn't (because the batch was nested and we are therefore still in the batch) + */ + inline bool EndOperationBatch() + { + bool ret = false; + if (_count.IsOutermost()) {BatchEnds(); ret = true;} // note that BatchEnds() is called while _count is still non-zero! + (void) _count.Decrement(); + return ret; + } + +protected: + /** Called by BeginOperationBatch(), when the outermost level of a batch begins. + * You should implement it to do any setup steps that are required at the beginning of a series of operations. + */ + virtual void BatchBegins() = 0; + + /** Called by EndOperationBatch(), when the outermost level of a batch ends. + * You should implement it to do any final steps that are required at the end of a series of operations. + */ + virtual void BatchEnds() = 0; + +private: + class BatchGuardImp : public BatchGuard + { + public: + BatchGuardImp(BatchOperator & bop) : _bop(bop) {(void) _bop.BeginOperationBatch();} + BatchGuardImp(const BatchGuardImp & rhs) : BatchGuard(), _bop(rhs._bop) {(void) _bop.BeginOperationBatch();} + ~BatchGuardImp() {(void) _bop.EndOperationBatch();} + + private: + BatchOperator & _bop; + }; + +public: + /** Returns a BatchGuard object that will keep this BatchOperator in a batch for as long as it exits. + * This method should only be called indirectly, via the DECLARE_BATCHGUARD() macro. + */ + BatchGuardImp GetBatchGuard() {return BatchGuardImp(*this);} +}; + +}; // end namespace muscle + +#endif diff --git a/util/ByteBuffer.cpp b/util/ByteBuffer.cpp new file mode 100644 index 00000000..b1a0f94a --- /dev/null +++ b/util/ByteBuffer.cpp @@ -0,0 +1,599 @@ +#include "dataio/DataIO.h" +#include "util/ByteBuffer.h" +#include "util/MiscUtilityFunctions.h" +#include "system/GlobalMemoryAllocator.h" + +namespace muscle { + +void ByteBuffer :: AdoptBuffer(uint32 numBytes, uint8 * optBuffer) +{ + Clear(true); // free any previously held array + _buffer = optBuffer; + _numValidBytes = _numAllocatedBytes = numBytes; +} + +status_t ByteBuffer :: SetBuffer(uint32 numBytes, const uint8 * buffer) +{ + if (IsByteInLocalBuffer(buffer)) + { + // Special logic for handling it when the caller wants our bytes-array to become a subset of its former self. + uint32 numReadableBytes = (uint32)((_buffer+_numValidBytes)-buffer); + if (numBytes > numReadableBytes) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "ByteBuffer::SetBuffer(); Attempted to read " UINT32_FORMAT_SPEC " bytes off the end of our internal buffer!\n", numBytes-numReadableBytes); + return B_ERROR; + } + else + { + if (buffer > _buffer) memmove(_buffer, buffer, numBytes); + return SetNumBytes(numBytes, true); + } + } + else + { + Clear(numBytes<(_numAllocatedBytes/2)); // FogBugz #6933: if the new buffer takes up less than half of our current space, toss it + if (SetNumBytes(numBytes, false) != B_NO_ERROR) return B_ERROR; + if ((buffer)&&(_buffer)) memcpy(_buffer, buffer, numBytes); + return B_NO_ERROR; + } +} + +status_t ByteBuffer :: SetNumBytes(uint32 newNumBytes, bool retainData) +{ + TCHECKPOINT; + + if (newNumBytes > _numAllocatedBytes) + { + IMemoryAllocationStrategy * as = GetMemoryAllocationStrategy(); + if (retainData) + { + uint8 * newBuf = (uint8 *) (as ? as->Realloc(_buffer, newNumBytes, _numAllocatedBytes, true) : muscleRealloc(_buffer, newNumBytes)); + if (newBuf) + { + _buffer = newBuf; + _numAllocatedBytes = _numValidBytes = newNumBytes; + } + else + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } + else + { + uint8 * newBuf = NULL; + if (newNumBytes > 0) + { + newBuf = (uint8 *) (as ? as->Malloc(newNumBytes) : muscleAlloc(newNumBytes)); + if (newBuf == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } + if (as) as->Free(_buffer, _numAllocatedBytes); else muscleFree(_buffer); + _buffer = newBuf; + _numAllocatedBytes = _numValidBytes = newNumBytes; + } + } + else _numValidBytes = newNumBytes; // truncating our array is easy! + + return B_NO_ERROR; +} + +status_t ByteBuffer :: AppendBytes(const uint8 * bytes, uint32 numBytes, bool allocExtra) +{ + if (numBytes == 0) return B_NO_ERROR; + + if ((bytes)&&(IsByteInLocalBuffer(bytes))&&((_numValidBytes+numBytes)>_numAllocatedBytes)) + { + // Oh dear, caller wants us to add a copy of some of our own bytes to ourself, AND we'll need to perform a reallocation to do it! + // So to avoid freeing (bytes) before we read from them, we're going to copy them over to a temporary buffer first. + uint8 * tmpBuf = newnothrow uint8[numBytes]; + if (tmpBuf) memcpy(tmpBuf, bytes, numBytes); + else {WARN_OUT_OF_MEMORY; return B_ERROR;} + status_t ret = AppendBytes(tmpBuf, numBytes, allocExtra); + delete [] tmpBuf; + return ret; + } + + uint32 oldValidBytes = _numValidBytes; // save this value since SetNumBytes() will change it + if (SetNumBytesWithExtraSpace(_numValidBytes+numBytes, allocExtra) != B_NO_ERROR) return B_ERROR; + if (bytes != NULL) memcpy(_buffer+oldValidBytes, bytes, numBytes); + return B_NO_ERROR; +} + +status_t ByteBuffer :: SetNumBytesWithExtraSpace(uint32 newNumValidBytes, bool allocExtra) +{ + if (SetNumBytes(((allocExtra)&&(newNumValidBytes > _numAllocatedBytes)) ? muscleMax(newNumValidBytes*4, (uint32)128) : newNumValidBytes, true) == B_NO_ERROR) + { + _numValidBytes = newNumValidBytes; + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t ByteBuffer :: FreeExtraBytes() +{ + TCHECKPOINT; + + if (_numValidBytes < _numAllocatedBytes) + { + IMemoryAllocationStrategy * as = GetMemoryAllocationStrategy(); + uint8 * newBuf = (uint8 *) (as ? as->Realloc(_buffer, _numValidBytes, _numAllocatedBytes, true) : muscleRealloc(_buffer, _numValidBytes)); + if ((_numValidBytes == 0)||(newBuf)) + { + _buffer = newBuf; + _numAllocatedBytes = _numValidBytes; + } + else return B_ERROR; + } + return B_NO_ERROR; +} + +/** Overridden to set our buffer directly from (copyFrom)'s Flatten() method */ +status_t ByteBuffer :: CopyFromImplementation(const Flattenable & copyFrom) +{ + uint32 numBytes = copyFrom.FlattenedSize(); + if (SetNumBytes(numBytes, false) != B_NO_ERROR) return B_ERROR; + copyFrom.Flatten(_buffer); + return B_NO_ERROR; +} + +void ByteBuffer :: Clear(bool releaseBuffers) +{ + if (releaseBuffers) + { + IMemoryAllocationStrategy * as = GetMemoryAllocationStrategy(); + if (as) as->Free(_buffer, _numAllocatedBytes); else muscleFree(_buffer); + _buffer = NULL; + _numValidBytes = _numAllocatedBytes = 0; + } + else SetNumBytes(0, false); +} + +void ByteBuffer :: PrintToStream(uint32 maxBytesToPrint, uint32 numColumns, FILE * optFile) const +{ + PrintHexBytes(GetBuffer(), muscleMin(maxBytesToPrint, GetNumBytes()), "ByteBuffer", numColumns, optFile); +} + +ByteBuffer operator+(const ByteBuffer & lhs, const ByteBuffer & rhs) +{ + ByteBuffer ret; + if (ret.SetNumBytes(lhs.GetNumBytes()+rhs.GetNumBytes(), false) == B_NO_ERROR) + { + memcpy(ret.GetBuffer(), lhs.GetBuffer(), lhs.GetNumBytes()); + memcpy(ret.GetBuffer()+lhs.GetNumBytes(), rhs.GetBuffer(), rhs.GetNumBytes()); + } + return ret; +} + +static ByteBufferRef::ItemPool _bufferPool; +ByteBufferRef::ItemPool * GetByteBufferPool() {return &_bufferPool;} +const ByteBuffer & GetEmptyByteBuffer() {return _bufferPool.GetDefaultObject();} + +static const ConstByteBufferRef _emptyBufRef(&_bufferPool.GetDefaultObject(), false); +ConstByteBufferRef GetEmptyByteBufferRef() {return _emptyBufRef;} + +ByteBufferRef GetByteBufferFromPool(uint32 numBytes, const uint8 * optBuffer) {return GetByteBufferFromPool(_bufferPool, numBytes, optBuffer);} +ByteBufferRef GetByteBufferFromPool(ObjectPool & pool, uint32 numBytes, const uint8 * optBuffer) +{ + ByteBufferRef ref(pool.ObtainObject()); + if ((ref())&&(ref()->SetBuffer(numBytes, optBuffer) != B_NO_ERROR)) ref.Reset(); // return NULL ref on out-of-memory + return ref; +} + +ByteBufferRef GetByteBufferFromPool(DataIO & dio) {return GetByteBufferFromPool(_bufferPool, dio);} + +ByteBufferRef GetByteBufferFromPool(ObjectPool & pool, DataIO & dio) +{ + int64 dioLen = dio.GetLength(); + if (dioLen < 0) return ByteBufferRef(); // we don't support reading in unknown lengths of data (for now) + + int64 pos = dio.GetPosition(); + if (pos < 0) pos = 0; + + int64 numBytesToRead = dioLen-pos; + if (numBytesToRead < 0) return ByteBufferRef(); // wtf? + + int64 maxBBSize = (int64) ((uint32)-1); // no point trying to read more data than a ByteBuffer will support anyway + if (numBytesToRead > maxBBSize) return ByteBufferRef(); + + ByteBufferRef ret = GetByteBufferFromPool(pool, (uint32)numBytesToRead); + if (ret() == NULL) return ByteBufferRef(); + + // This will truncate the ByteBuffer if we end up reading fewer bytes than we expected to + ret()->SetNumBytes(dio.ReadFully(ret()->GetBuffer(), ret()->GetNumBytes()), true); + return ret; +} + +// These Flattenable methods are implemented here so that if you don't use them, you +// don't need to include ByteBuffer.o in your Makefile. If you do use them, then you +// needed to include ByteBuffer.o in your Makefile anyway. + +Ref Flattenable :: FlattenToByteBuffer() const +{ + ByteBufferRef bufRef = GetByteBufferFromPool(FlattenedSize()); + if (bufRef()) Flatten(bufRef()->GetBuffer()); + return bufRef; +} + +status_t Flattenable :: FlattenToByteBuffer(ByteBuffer & outBuf) const +{ + if (outBuf.SetNumBytes(FlattenedSize(), false) != B_NO_ERROR) return B_ERROR; + Flatten(outBuf.GetBuffer()); + return B_NO_ERROR; +} + +status_t Flattenable :: UnflattenFromByteBuffer(const ByteBuffer & buf) +{ + return Unflatten(buf.GetBuffer(), buf.GetNumBytes()); +} + +status_t Flattenable :: UnflattenFromByteBuffer(const ConstRef & buf) +{ + return buf() ? Unflatten(buf()->GetBuffer(), buf()->GetNumBytes()) : B_ERROR; +} + +uint32 ByteBuffer :: ReadInt8s(int8 * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, GetNumValidBytesAtOffset(readByteOffset)); + memcpy(vals, readAt, numValsToRead); + readByteOffset += numValsToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadInt16s(int16 * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/sizeof(int16))); + uint32 numBytesToRead = numValsToRead*sizeof(int16); + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i(&readAt[i*sizeof(int16)])); + } + else memcpy(vals, readAt, numBytesToRead); + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadInt32s(int32 * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/sizeof(int32))); + uint32 numBytesToRead = numValsToRead*sizeof(int32); + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i(&readAt[i*sizeof(int32)])); + } + else memcpy(vals, readAt, numBytesToRead); + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadInt64s(int64 * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/sizeof(int64))); + uint32 numBytesToRead = numValsToRead*sizeof(int64); + if (IsEndianSwapEnabled()) + { + for (uint64 i=0; i(&readAt[i*sizeof(int64)])); + } + else memcpy(vals, readAt, numBytesToRead); + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadFloats(float * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/sizeof(int32))); + uint32 numBytesToRead = numValsToRead*sizeof(int32); + if (IsEndianSwapEnabled()) + { +#if B_HOST_IS_BENDIAN + for (uint32 i=0; i(&readAt[i*sizeof(int32)])); +#else + for (uint32 i=0; i(&readAt[i*sizeof(int32)])); +#endif + } + else memcpy(vals, readAt, numBytesToRead); + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadDoubles(double * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/sizeof(int64))); + uint32 numBytesToRead = numValsToRead*sizeof(int64); + if (IsEndianSwapEnabled()) + { +#if B_HOST_IS_BENDIAN + for (uint32 i=0; i(&readAt[i*sizeof(int64)])); +#else + for (uint32 i=0; i(&readAt[i*sizeof(int64)])); +#endif + } + else memcpy(vals, readAt, numBytesToRead); + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadPoints(Point * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint32 bytesPerPoint = sizeof(int32)*2; + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/bytesPerPoint)); + uint32 numBytesToRead = numValsToRead*bytesPerPoint; + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i(&rBase[0*sizeof(int32)])), B_LENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[1*sizeof(int32)]))); +#else + vals[i].Set(B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[0*sizeof(int32)])), B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[1*sizeof(int32)]))); +#endif + } + } + else + { + for (uint32 i=0; i(&rBase[0*sizeof(int32)]), muscleCopyIn(&rBase[1*sizeof(int32)])); + } + } + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadRects(Rect * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + const uint32 bytesPerRect = sizeof(int32)*4; + const uint8 * readAt = _buffer+readByteOffset; + numValsToRead = muscleMin(numValsToRead, (uint32) (GetNumValidBytesAtOffset(readByteOffset)/bytesPerRect)); + uint32 numBytesToRead = numValsToRead*bytesPerRect; + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i(&rBase[0*sizeof(int32)])), + B_LENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[1*sizeof(int32)])), + B_LENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[2*sizeof(int32)])), + B_LENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[3*sizeof(int32)]))); +#else + vals[i].Set(B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[0*sizeof(int32)])), + B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[1*sizeof(int32)])), + B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[2*sizeof(int32)])), + B_BENDIAN_TO_HOST_IFLOAT(muscleCopyIn(&rBase[3*sizeof(int32)]))); +#endif + } + } + else + { + for (uint32 i=0; i(&rBase[0*sizeof(int32)]), + muscleCopyIn(&rBase[1*sizeof(int32)]), + muscleCopyIn(&rBase[2*sizeof(int32)]), + muscleCopyIn(&rBase[3*sizeof(int32)])); + } + } + + readByteOffset += numBytesToRead; + return numValsToRead; +} + +uint32 ByteBuffer :: ReadStrings(String * vals, uint32 numValsToRead, uint32 & readByteOffset) const +{ + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newByteSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + memcpy(writeTo, vals, numVals); + writeByteOffset += numVals; + return B_NO_ERROR; +} + +status_t ByteBuffer :: WriteInt16s(const int16 * vals, uint32 numVals, uint32 & writeByteOffset) +{ + uint32 numBytes = numVals*sizeof(int16); + uint32 newValidSize = muscleMax(_numValidBytes, writeByteOffset+numBytes); + if ((newValidSize > _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + if (IsEndianSwapEnabled()) + { +#if B_HOST_IS_BENDIAN + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + if (IsEndianSwapEnabled()) + { +#if B_HOST_IS_BENDIAN + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + uint8 * writeTo = _buffer+writeByteOffset; + + if (IsEndianSwapEnabled()) + { + for (uint32 i=0; i _numValidBytes)&&(SetNumBytesWithExtraSpace(newValidSize, true) != B_NO_ERROR)) return B_ERROR; + + for (uint32 i=0; i= _buffer)&&(byte < (_buffer+_numValidBytes)));} + + /** Convenience methods for appending one data-item to the end of this buffer. The buffer will be resized larger if necessary to hold + * the written data. Returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t AppendInt8( int8 val) {return AppendInt8s( &val, 1);} + status_t AppendInt16( int16 val) {return AppendInt16s( &val, 1);} + status_t AppendInt32( int32 val) {return AppendInt32s( &val, 1);} + status_t AppendInt64( int64 val) {return AppendInt64s( &val, 1);} + status_t AppendFloat( float val) {return AppendFloats( &val, 1);} + status_t AppendDouble(double val) {return AppendDoubles(&val, 1);} + status_t AppendPoint( const Point & val) {return AppendPoints( &val, 1);} + status_t AppendRect( const Rect & val) {return AppendRects( &val, 1);} + status_t AppendString(const String & val) {return AppendStrings(&val, 1);} + + /** Convenience methods for appending an array of data-items to the end of this buffer. The buffer will be resized larger if necessary + * to hold the written data. Returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t AppendInt8s( const int8 * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteInt8s( vals, numVals, w);} + status_t AppendInt16s( const int16 * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteInt16s( vals, numVals, w);} + status_t AppendInt32s( const int32 * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteInt32s( vals, numVals, w);} + status_t AppendInt64s( const int64 * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteInt64s( vals, numVals, w);} + status_t AppendFloats( const float * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteFloats( vals, numVals, w);} + status_t AppendDoubles(const double * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteDoubles(vals, numVals, w);} + status_t AppendPoints( const Point * vals, uint32 numVals) {uint32 w = _numValidBytes; return WritePoints( vals, numVals, w);} + status_t AppendRects( const Rect * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteRects( vals, numVals, w);} + status_t AppendStrings(const String * vals, uint32 numVals) {uint32 w = _numValidBytes; return WriteStrings(vals, numVals, w);} + + /** Convenience methods for reading one data item from this buffer. (readByteOffset) will be advanced to the byte-offset after the read item. + * If the item cannot be read (not enough bytes in the buffer), zero (or a default-constructed item) will be returned instead. + */ + int8 ReadInt8( uint32 & readByteOffset) const {int8 ret; return (ReadInt8s( &ret, 1, readByteOffset) == 1) ? ret : 0;} + int16 ReadInt16( uint32 & readByteOffset) const {int16 ret; return (ReadInt16s( &ret, 1, readByteOffset) == 1) ? ret : 0;} + int32 ReadInt32( uint32 & readByteOffset) const {int32 ret; return (ReadInt32s( &ret, 1, readByteOffset) == 1) ? ret : 0;} + int64 ReadInt64( uint32 & readByteOffset) const {int64 ret; return (ReadInt64s( &ret, 1, readByteOffset) == 1) ? ret : 0;} + float ReadFloat( uint32 & readByteOffset) const {float ret; return (ReadFloats( &ret, 1, readByteOffset) == 1) ? ret : 0.0f;} + double ReadDouble(uint32 & readByteOffset) const {double ret; return (ReadDoubles(&ret, 1, readByteOffset) == 1) ? ret : 0.0;} + Point ReadPoint( uint32 & readByteOffset) const {Point ret; return (ReadPoints( &ret, 1, readByteOffset) == 1) ? ret : Point();} + Rect ReadRect( uint32 & readByteOffset) const {Rect ret; return (ReadRects( &ret, 1, readByteOffset) == 1) ? ret : Rect();} + String ReadString(uint32 & readByteOffset) const {String ret; return (ReadStrings(&ret, 1, readByteOffset) == 1) ? ret : String();} + + /** Convenience methods for reading an array of data items from this buffer. (readByteOffset) will be advanced to the byte-offset after + * the last read item. The number of items actually read will be returned (it may be smaller than (maxValsToRead) if there weren't enough + * bytes left in the buffer to read (maxValsToRead) items) + */ + uint32 ReadInt8s( int8 * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadInt16s( int16 * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadInt32s( int32 * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadInt64s( int64 * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadFloats( float * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadDoubles(double * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadPoints( Point * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadRects( Rect * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + uint32 ReadStrings(String * vals, uint32 maxValsToRead, uint32 & readByteOffset) const; + + /** Convenience methods for writing one data-item to a specified offset in this buffer. (writeByteOffset) will be advanced to the + * byte-offset after the written item. The buffer will be resized larger if necessary to hold the written data. + */ + status_t WriteInt8( int8 val, uint32 & writeByteOffset) {return WriteInt8s( &val, 1, writeByteOffset);} + status_t WriteInt16( int16 val, uint32 & writeByteOffset) {return WriteInt16s( &val, 1, writeByteOffset);} + status_t WriteInt32( int32 val, uint32 & writeByteOffset) {return WriteInt32s( &val, 1, writeByteOffset);} + status_t WriteInt64( int64 val, uint32 & writeByteOffset) {return WriteInt64s( &val, 1, writeByteOffset);} + status_t WriteFloat( float val, uint32 & writeByteOffset) {return WriteFloats( &val, 1, writeByteOffset);} + status_t WriteDouble(double val, uint32 & writeByteOffset) {return WriteDoubles(&val, 1, writeByteOffset);} + status_t WritePoint( const Point & val, uint32 & writeByteOffset) {return WritePoints( &val, 1, writeByteOffset);} + status_t WriteRect( const Rect & val, uint32 & writeByteOffset) {return WriteRects( &val, 1, writeByteOffset);} + status_t WriteString(const String & val, uint32 & writeByteOffset) {return WriteStrings(&val, 1, writeByteOffset);} + + /** Convenience methods for writing an array data-items to a specified offset in this buffer. (writeByteOffset) will be advanced to + * the byte-offset after the last written item. The buffer will be resized larger if necessary to hold the written data. + * Returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t WriteInt8s( const int8 * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteInt16s( const int16 * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteInt32s( const int32 * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteInt64s( const int64 * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteFloats( const float * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteDoubles(const double * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WritePoints( const Point * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteRects( const Rect * vals, uint32 numVals, uint32 & writeByteOffset); + status_t WriteStrings(const String * vals, uint32 numVals, uint32 & writeByteOffset); + +protected: + /** Overridden to set our buffer directly from (copyFrom)'s Flatten() method */ + virtual status_t CopyFromImplementation(const Flattenable & copyFrom); + +private: + uint32 GetNumValidBytesAtOffset(uint32 offset) const {return (offset<_numValidBytes) ? (_numValidBytes-offset) : 0;} + status_t SetNumBytesWithExtraSpace(uint32 newNumValidBytes, bool allocExtra); + + uint8 * _buffer; // pointer to our byte array (or NULL if we haven't got one) + uint32 _numValidBytes; // number of bytes the user thinks we have + uint32 _numAllocatedBytes; // number of bytes we actually have + PointerAndBool _allocStrategy; // note that unless MUSCLE_AVOID_BITSTUFFING is defined, we are abusing the low bit here as a data-needs-swap bit +}; +DECLARE_REFTYPES(ByteBuffer); + +ByteBuffer operator+(const ByteBuffer & lhs, const ByteBuffer & rhs); + +/** This function returns a pointer to a singleton ObjectPool that can be used to minimize the number of + * ByteBuffer allocations and frees by recycling the ByteBuffer objects. + */ +ByteBufferRef::ItemPool * GetByteBufferPool(); + +/** Convenience method: Gets a ByteBuffer from the ByteBuffer pool, makes sure it holds the specified number of bytes, and returns it. + * @param numBytes Number of bytes to copy in (or just allocate, if (optBuffer) is NULL). Defaults to zero bytes (i.e. retrieve an empty buffer) + * @param optBuffer If non-NULL, points to an array of (numBytes) bytes to copy in to our internal buffer. + * If NULL, this ByteBuffer will contain (numBytes) uninitialized bytes. Defaults to NULL. + * @return Reference to a ByteBuffer object that has been initialized as specified, or a NULL ref on failure (out of memory). + */ +ByteBufferRef GetByteBufferFromPool(uint32 numBytes = 0, const uint8 * optBuffer = NULL); + +/** As above, except that the byte buffer is obtained from the specified pool instead of from the default ByteBuffer pool. + * @param pool the ObjectPool to allocate the ByteBuffer from. + * @param numBytes Number of bytes to copy in (or just allocate, if (optBuffer) is NULL). Defaults to zero bytes (i.e. retrieve an empty buffer) + * @param optBuffer If non-NULL, points to an array of (numBytes) bytes to copy in to our internal buffer. + * If NULL, this ByteBuffer will contain (numBytes) uninitialized bytes. Defaults to NULL. + * @return Reference to a ByteBuffer object that has been initialized as specified, or a NULL ref on failure (out of memory). + */ +ByteBufferRef GetByteBufferFromPool(ObjectPool & pool, uint32 numBytes = 0, const uint8 * optBuffer = NULL); + +/** Convenience method: Gets a ByteBuffer from the default ByteBuffer pool, flattens (flattenMe) into the byte buffer, and + * returns a reference to the new ByteBuffer. + * @param flattenMe A Flattenable object to flatten. + * @return Reference to a ByteBuffer object as specified, or a NULL ref on failure (out of memory). + */ +template inline ByteBufferRef GetFlattenedByteBufferFromPool(const T & flattenMe) {return GetFlattenedByteBufferFromPool(*GetByteBufferPool(), flattenMe);} + +/** Convenience method: Gets a ByteBuffer from the specified ByteBuffer pool, flattens (flattenMe) into the byte buffer, and + * returns a reference to the new ByteBuffer. + * @param pool The ObjectPool to retrieve the new ByteBuffer object from. + * @param flattenMe A Flattenable object (or at least an object with Flatten() and FlattenedSize() methods) to flatten. + * @return Reference to a ByteBuffer object as specified, or a NULL ref on failure (out of memory). + */ +template inline ByteBufferRef GetFlattenedByteBufferFromPool(ObjectPool & pool, const T & flattenMe) +{ + ByteBufferRef bufRef = GetByteBufferFromPool(pool, flattenMe.FlattenedSize()); + if (bufRef()) flattenMe.Flatten(bufRef()->GetBuffer()); + return bufRef; +} + +/** Convenience method: Returns a ByteBufferRef containing all the remaining data read in from (dio). + * @param dio The DataIO object to read the remaining data out of. Note that only DataIOs with known + * positions and lengths will work here; streaming DataIOs (like TCPSocketDataIOs) will cause a NULL + * ref to be returned (since we wouldn't know how large a buffer to allocate). + * @return Reference to a ByteBuffer object that has been initialized as specified, or a NULL ref on failure (out of memory). + */ +ByteBufferRef GetByteBufferFromPool(DataIO & dio); + +/** As above, except that the byte buffer is obtained from the specified pool instead of from the default ByteBuffer pool. + * @param pool the ObjectPool to allocate the ByteBuffer from. + * @param dio The DataIO object to read the remaining data out of. Note that only DataIOs with known + * positions and lengths will work here; streaming DataIOs (like TCPSocketDataIOs) will cause a NULL + * ref to be returned (since we wouldn't know how large a buffer to allocate). + * @return Reference to a ByteBuffer object that has been initialized as specified, or a NULL ref on failure (out of memory). + */ +ByteBufferRef GetByteBufferFromPool(ObjectPool & pool, DataIO & dio); + +/** Convenience method: returns a read-only reference to an empty ByteBuffer */ +const ByteBuffer & GetEmptyByteBuffer(); + +/** Convenience method: returns a read-only reference to a ByteBuffer that contains no data. */ +ConstByteBufferRef GetEmptyByteBufferRef(); + +/** This interface is used to represent any object that knows how to allocate, reallocate, and free memory in a special way. */ +class IMemoryAllocationStrategy +{ +public: + /** Default constructor */ + IMemoryAllocationStrategy() {/* empty */} + + /** Destructor */ + virtual ~IMemoryAllocationStrategy() {/* empty */} + + /** Called when a ByteBuffer needs to allocate a memory buffer. This method should be implemented to behave similarly to malloc(). + * @param size Number of bytes to allocate + * @returns A pointer to the allocated bytes on success, or NULL on failure. + */ + virtual void * Malloc(size_t size) = 0; + + /** Called when a ByteBuffer needs to resize a memory buffer. This method should be implemented to behave similarly to realloc(). + * @param ptr Pointer to the buffer to resize, or NULL if there is no current buffer. + * @param newSize Desired new size of the buffer + * @param oldSize Current size of the buffer + * @param retainData If false, the returned buffer need not retain the contents of the old buffer. + * @returns A pointer to the new buffer on success, or NULL on failure (or if newSize == 0) + */ + virtual void * Realloc(void * ptr, size_t newSize, size_t oldSize, bool retainData) = 0; + + /** Called when a ByteBuffer needs to free a memory buffer. This method should be implemented to behave similarly to free(). + * @param ptr Pointer to the buffer to free. + * @param size Number of byes in the buffer. + */ + virtual void Free(void * ptr, size_t size) = 0; +}; + +}; // end namespace muscle + +#endif diff --git a/util/CPULoadMeter.cpp b/util/CPULoadMeter.cpp new file mode 100644 index 00000000..63cac169 --- /dev/null +++ b/util/CPULoadMeter.cpp @@ -0,0 +1,101 @@ +#include "util/CPULoadMeter.h" +#include "util/StringTokenizer.h" + +#ifdef __APPLE__ +# include +# include +# include +# include +#endif + +namespace muscle { + +#ifdef WIN32 +# ifndef USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES +# include +# include +# endif +static uint64 FileTimeToInt64(const FILETIME & ft) {return (((uint64)(ft.dwHighDateTime))<<32)|((uint64)ft.dwLowDateTime);} +#endif + +CPULoadMeter :: CPULoadMeter() : _previousTotalTicks(0), _previousIdleTicks(0) +{ +#ifdef USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES + // Gotta dynamically load this system call, because the Borland headers doesn't know about it. :^P + _winKernelLib = LoadLibrary(TEXT("kernel32.dll")); + if (_winKernelLib) _getSystemTimesProc = (GetSystemTimesProc) GetProcAddress(_winKernelLib, "GetSystemTimes"); +#endif +} + +CPULoadMeter :: ~CPULoadMeter() +{ +#ifdef USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES + if (_winKernelLib != NULL) FreeLibrary(_winKernelLib); +#endif +} + +float CPULoadMeter :: CalculateCPULoad(uint64 idleTicks, uint64 totalTicks) +{ + uint64 totalTicksSinceLastTime = totalTicks-_previousTotalTicks; + uint64 idleTicksSinceLastTime = idleTicks-_previousIdleTicks; + float ret = 1.0f-((totalTicksSinceLastTime > 0) ? ((float)idleTicksSinceLastTime)/totalTicksSinceLastTime : 0); + _previousTotalTicks = totalTicks; + _previousIdleTicks = idleTicks; + return ret; +} + +float CPULoadMeter :: GetCPULoad() +{ + TCHECKPOINT; + + float sysLoadPercentage = -1.0f; // default (aka unset) + +#ifdef __linux__ + FILE * fpIn = fopen("/proc/stat", "r"); + if (fpIn) + { + char buf[1024]; + while(fgets(buf, sizeof(buf), fpIn)) + { + if (strncmp(buf, "cpu ", 4) == 0) + { + StringTokenizer tok(false, &buf[4]); + const char * next = tok(); + uint64 userTicks = next ? Atoull(next) : 0; next = tok(); + uint64 niceTicks = next ? Atoull(next) : 0; next = tok(); + uint64 systemTicks = next ? Atoull(next) : 0; next = tok(); + uint64 idleTicks = next ? Atoull(next) : 0; + sysLoadPercentage = CalculateCPULoad(idleTicks, userTicks+niceTicks+systemTicks+idleTicks); + break; + } + } + fclose(fpIn); + } +#elif WIN32 +# ifdef USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES + if (_getSystemTimesProc) + { + FILETIME idleTime, kernelTime, userTime; + if (_getSystemTimesProc(&idleTime, &kernelTime, &userTime)) sysLoadPercentage = CalculateCPULoad(FileTimeToInt64(idleTime), FileTimeToInt64(kernelTime)+FileTimeToInt64(userTime)); + } +# else + { // keep these variables local + FILETIME idleTime, kernelTime, userTime; + if (GetSystemTimes(&idleTime, &kernelTime, &userTime)) sysLoadPercentage = CalculateCPULoad(FileTimeToInt64(idleTime), FileTimeToInt64(kernelTime)+FileTimeToInt64(userTime)); + } +# endif +#elif __APPLE__ + host_cpu_load_info_data_t cpuinfo; + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpuinfo, &count) == KERN_SUCCESS) + { + uint64 totalTicks = 0; + for(int i=0; i +{ +public: + /** Default constructor */ + CPULoadMeter(); + + /** Destructor. */ + ~CPULoadMeter(); + + /** Returns the percentage CPU load, measured since the last time this method was called. + * @note Currently this method is implemented only for Linux, OS/X, and Windows. + * For other operating systems, this method will always return a negative value. + * @returns 0.0f if the CPU was idle, 1.0f if the CPU was fully loaded, or something + * in between. Returns a negative value if the CPU time could not be measured. + */ + float GetCPULoad(); + +private: + float CalculateCPULoad(uint64 idleTicks, uint64 totalTicks); + + uint64 _previousTotalTicks; + uint64 _previousIdleTicks; + +#ifdef WIN32 +# if defined(MUSCLE_USING_NEW_MICROSOFT_COMPILER) +// we will use the statically linked version +# else +# define USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES 1 +# endif +#endif + +#ifdef USE_KERNEL32_DLL_FOR_GETSYSTEMTIMES + typedef WINBASEAPI BOOL WINAPI (*GetSystemTimesProc) (OUT LPFILETIME t1, OUT LPFILETIME t2, OUT LPFILETIME t3); + GetSystemTimesProc _getSystemTimesProc; + HMODULE _winKernelLib; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/util/Cloneable.h b/util/Cloneable.h new file mode 100644 index 00000000..f7dc8876 --- /dev/null +++ b/util/Cloneable.h @@ -0,0 +1,43 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleCloneable_h +#define MuscleCloneable_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** An interface that can be inherited by any class that wants to provide a Clone() + * method that will return a copy of itself. + */ +class Cloneable +{ +public: + /** Default constructor. */ + Cloneable() {/* empty */} + + /** Virtual destructor, to keep C++ honest. Don't remove this unless you like crashing */ + virtual ~Cloneable() {/* empty */} + + /** Should be implemented by the inheriting concrete class to return a freshly allocated copy of itself. */ + virtual Cloneable * Clone() const = 0; +}; +#define DECLARE_STANDARD_CLONE_METHOD(class_name) virtual Cloneable * Clone() const {Cloneable * r = newnothrow class_name(*this); if (r == NULL) WARN_OUT_OF_MEMORY; return r;} + +/** A preferred version of CloneObject() that uses the argument's built-in Clone() method */ +inline Cloneable * CloneObject(const Cloneable & item) {return item.Clone();} + +#if DISABLED_FOR_NOW_SINCE_THE_COMPILER_SOMETIMES_USES_IT_WHEN_IT_SHOULDNT_JAF +/** A fallback version of CloneObject() for concrete types that don't support the Cloneable interface */ +template inline Item * CloneObject(const Item & item) +{ + Item * c = newnothrow Item(item); + if (c) *c = item; + else WARN_OUT_OF_MEMORY; + return c; +} +#endif + +}; // end namespace muscle + +#endif diff --git a/util/CountedObject.h b/util/CountedObject.h new file mode 100644 index 00000000..5bb440b5 --- /dev/null +++ b/util/CountedObject.h @@ -0,0 +1,114 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleCountedObject_h +#define MuscleCountedObject_h + +#include // So we can use typeid().name() in GetCounterTypeName() +#include "system/AtomicCounter.h" +#include "util/Hashtable.h" + +namespace muscle { + +#ifndef MUSCLE_AVOID_OBJECT_COUNTING + +/** This base class is used to construct a linked-list of ObjectCounter objects so that we can iterate over them and print them out */ +class ObjectCounterBase +{ +public: + /** To be implemented by ObjectCounter subclass to return a human-readable name indicating the type that is being counted */ + virtual const char * GetCounterTypeName() const = 0; + + /** Implemented by subclass to return the number of objects of our type that are currently allocated. */ + uint32 GetCount() const {return _counter.GetCount();} + + /** Returns the previous counter in our global list of ObjectCounters. */ + const ObjectCounterBase * GetPreviousCounter() const {return _prevCounter;} + + /** Returns the next counter in our global list of ObjectCounters. */ + const ObjectCounterBase * GetNextCounter() const {return _nextCounter;} + + /** Increments our internal count */ + void IncrementCounter() {_counter.AtomicIncrement();} + + /** Decrements our internal count. Returns true iff our internal count has reached zero. */ + bool DecrementCounter() {return _counter.AtomicDecrement();} + +protected: + /** Constructor. Only our ObjectCounter subclass is allowed to construct us! */ + ObjectCounterBase(); + + /** Destructor. */ + virtual ~ObjectCounterBase(); + +private: + ObjectCounterBase(const ObjectCounterBase &); // private and unimplemented + + ObjectCounterBase * _prevCounter; + ObjectCounterBase * _nextCounter; + AtomicCounter _counter; +}; + +/** This class is used by the CountedObject class to count objects. You shouldn't ever need to instantiate an object of this class directly. */ +template class ObjectCounter : public ObjectCounterBase +{ +public: + /** Default constructor */ + ObjectCounter() {/* empty */} + + /** Destructor */ + virtual ~ObjectCounter() {/* empty */} + + /** Implemented to return a human-readable string describing ObjectType's type */ + virtual const char * GetCounterTypeName() const {return typeid(ObjectType).name();} +}; + +#endif + +/** This class is a superclass that other classes can derive from if it is + * desired to keep track of the number of objects of the derived class + * that are currently allocated. Note that this class will compile down + * to a no-op if -DMUSCLE_AVOID_OBJECT_COUNTING is present. Otherwise, you can + * call PrintCountedObjectInfo() at any time to get a report of current object + * allocation counts by type. + */ +template class CountedObject +{ +public: + /** Default Constructor. */ + CountedObject() + { +#ifndef MUSCLE_AVOID_OBJECT_COUNTING + GetGlobalObjectForType< ObjectCounter >().IncrementCounter(); +#endif + } + + /** Copy Constructor. */ + CountedObject(const CountedObject & /*rhs*/) + { +#ifndef MUSCLE_AVOID_OBJECT_COUNTING + GetGlobalObjectForType< ObjectCounter >().IncrementCounter(); +#endif + } + + /** Destructor (deliberately not virtual, to avoid a vtable-pointer size-penalty) */ + ~CountedObject() + { +#ifndef MUSCLE_AVOID_OBJECT_COUNTING + GetGlobalObjectForType< ObjectCounter >().DecrementCounter(); +#endif + } +}; + +/** For debugging. On success, populates (results) with type names and their associated object counts, + * respectively. + * @param results a Hashtable to populate. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, or -DMUSCLE_TRACK_OBJECT_COUNTS wasn't defined) + */ +status_t GetCountedObjectInfo(Hashtable & results); + +/** Convenience function. Calls GetCountedObjectInfo() and pretty-prints the results to stdout. */ +void PrintCountedObjectInfo(); + +}; // end namespace muscle + +#endif diff --git a/util/DebugTimer.h b/util/DebugTimer.h new file mode 100644 index 00000000..99b40867 --- /dev/null +++ b/util/DebugTimer.h @@ -0,0 +1,87 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef DebugTimer_h +#define DebugTimer_h + +#include "util/Hashtable.h" +#include "util/String.h" + +#ifndef MUSCLE_DEBUG_TIMER_CLOCK +# if defined(__BEOS__) || defined(__HAIKU__) || defined(__ATHEOS__) || defined(WIN32) || (defined(MUSCLE_USE_LIBRT) && defined(_POSIX_MONOTONIC_CLOCK)) || defined(TARGET_PLATFORM_XENOMAI) +# define MUSCLE_DEBUG_TIMER_CLOCK GetRunTime64() +# else +# define MUSCLE_DEBUG_TIMER_CLOCK GetCurrentTime64() /* POSIX API's run-time clock has crappy resolution :^( */ +# endif +#endif + +namespace muscle { + +/** This is a little class that can be helpful for debugging. It will record the amount of time + * spent in various modes, and then when the DebugTimer object goes away, it will Log a message + * describing how much time was spent in each mode. + */ +class DebugTimer +{ +public: + /** Constructor + * @param title Title to display in the debug report generated by our constructor. Defaults to "timer". + * @param minLogTime Logging of any timer values less than this value (in microseconds) will be suppressed (so as not to distract you with trivia). Defaults to 1000 (a.k.a. 1 millisecond) + * @param startMode What mode the timer should begin in. Each mode has its elapsed time recorded separately. Default is mode zero. + * @param debugLevel log level to log at. Defaults to MUSCLE_LOG_INFO. If set to a negative number, we'll call printf() instead of LogTime(). + */ + DebugTimer(const String & title = "timer", uint64 minLogTime = 1000, uint32 startMode = 0, int debugLevel = MUSCLE_LOG_INFO); + + /** Destructor. Prints out a log message with the elapsed time, in milliseconds, spent in each mode. */ + ~DebugTimer(); + + /** Set the timer to record elapsed time to a different mode. */ + void SetMode(uint32 newMode); + + /** Returns the currently active mode number */ + uint32 GetMode() const {return _currentMode;} + + /** Convenience method: Equivalent to GetElapsedTime(GetMode()) */ + uint64 GetElapsedTime() const {return GetElapsedTime(GetMode());} + + /** Returns the amount of elapsed time, in microseconds, that has been spent in the given mode. + * Note that if (whichMode) is the currently active mode, the returned value will be growing from moment to moment. + */ + uint64 GetElapsedTime(uint32 whichMode) const + { + const uint64 * et = _modeToElapsedTime.Get(whichMode); + return (et ? *et : 0) + ((whichMode == _currentMode) ? (MUSCLE_DEBUG_TIMER_CLOCK-_startTime) : 0); + } + + /** Set whether or not the destructor should print results to the system log. Default is true. */ + void SetLogEnabled(bool e) {_enableLog = e;} + + /** Returns the state of the print-to-log-enabled, as set by SetLogEnabled() */ + bool IsLogEnabled() const {return _enableLog;} + + /** Set the minimum-log-time value, in microseconds. Time intervals shorter than this will not be logged. Defaults to zero. */ + void SetMinLogTime(uint64 lt) {_minLogTime = lt;} + + /** Returns the current minimum-log-time value, in microseconds. */ + uint64 GetMinLogTime() const {return _minLogTime;} + +private: + uint32 _currentMode; + uint64 _startTime; // time at which we entered the current mode + Hashtable _modeToElapsedTime; + + String _title; + uint64 _minLogTime; + int _debugLevel; + bool _enableLog; +}; + +/** A macro for quickly declaring a DebugTimer object on the stack. Usage example: DECLARE_DEBUGTIMER("hi") */ +#ifdef _MSC_VER +# define DECLARE_DEBUGTIMER(...) DECLARE_ANONYMOUS_STACK_OBJECT(DebugTimer, __VA_ARGS__) +#else +# define DECLARE_DEBUGTIMER(args...) DECLARE_ANONYMOUS_STACK_OBJECT(DebugTimer, args) +#endif + +}; // end namespace muscle + +#endif diff --git a/util/DemandConstructedObject.h b/util/DemandConstructedObject.h new file mode 100644 index 00000000..8b3c19fa --- /dev/null +++ b/util/DemandConstructedObject.h @@ -0,0 +1,117 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleDemandConstructedObject_h +#define MuscleDemandConstructedObject_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** This class wraps an object of the specified type, so that the wrapped object can be used as + * a member object but, without requiring its constructor to be called at the usual C++ construct-object time. + * + * Instead, the wrapped object's constructor gets called at a time of the caller's choosing; + * typically when the wrapped object is first accessed. This can be useful to avoid the + * overhead of constructing an object that may or may not ever be actually used for anything, + * while at the same time avoiding the overhead and uncertainty of a separate dynamic memory + * allocation for the object. + */ +template class DemandConstructedObject +{ +public: + /** Constructor. */ + DemandConstructedObject() : _objPointer(NULL) {/* empty*/} + + /** Copy constructor. */ + DemandConstructedObject(const DemandConstructedObject & rhs) : _objPointer(NULL) {if (rhs.IsObjectConstructed()) (void) EnsureObjectConstructed(rhs.GetObjectUnchecked());} + + /** Pseudo-Copy constructor. */ + DemandConstructedObject(const T & rhs) : _objPointer(NULL) {(void) EnsureObjectConstructed(rhs);} + + /** Destructor. Calls the destructor on our held object as well, if necessary. */ + ~DemandConstructedObject() {if (_objPointer) _objPointer->~T();} + + /** Assignment operator. */ + DemandConstructedObject & operator=(const DemandConstructedObject & from) + { + if (from.IsObjectConstructed()) GetObject() = from.GetObjectUnchecked(); + else (void) EnsureObjectDestructed(); + return *this; + } + + /** Assignment operator */ + DemandConstructedObject & operator=(const T & from) + { + (void) GetObject() = from; + return *this; + } + + /** Equality operator. Destructed objects are always considered equal. + * Constructed objects are never considered equal to destructed objects. + * Two constructed objects will be considered equal according to their == operator. + */ + bool operator == (const DemandConstructedObject & rhs) const + { + bool amConstructed = IsObjectConstructed(); + if (amConstructed != rhs.IsObjectConstructed()) return false; + return ((amConstructed == false)||(GetObjectUnchecked() == rhs.GetObjectUnchecked())); + } + + /** Returns the opposite of our equality operator. */ + bool operator != (const DemandConstructedObject & rhs) const {return !(*this==rhs);} + + /** Equality operator. Returns true iff our held object is constructed and equal to (rhs) */ + bool operator == (const T & rhs) const {return ((IsObjectConstructed())&&(GetObjectUnchecked() == rhs));} + + /** Equality operator. Returns true iff our held object is constructed and equal to (rhs) */ + bool operator != (const T & rhs) const {return !(*this==rhs);} + + /** Returns a valid reference to our wrapped object. EnsureObjectConstructed() will be called if necessary. */ + T & GetObject() {(void) EnsureObjectConstructed(); return *_objPointer;} + + /** Read-only version of GetObject. EnsureObjectConstructed() will be called if necessary. */ + const T & GetObject() const {(void) EnsureObjectConstructed(); return *_objPointer;} + + /** Returns a reference to our wrapped object. Note that this method DOES NOT + * call EnsureObjectConstructed(), so if you call this method you MUST be sure the object + * was already constructed, or you will get back a NULL reference. + */ + T & GetObjectUnchecked() {return *_objPointer;} + + /** Read-only version of GetObjectUnchecked(). */ + const T & GetObjectUnchecked() const {return *_objPointer;} + + /** Ensures that our held object is constructed. It's not typically necessary to call + * this method manually, because GetObject() will call it for you when necessary. + * If the object was already constructed, then this method is a no-op. + * @returns true if this call constructed the object, or false if the object was already constructed. + */ + bool EnsureObjectConstructed() const {if (_objPointer) return false; else {_objPointer = new (_objUnion._buf) T(); return true;}} + + /** Same as above, except if this call needs to construct the object, it does so using the object's + * copy-constructor, and passed in (val) as the object to copy from. + * @param val The object to copy from, if we need to construct our object. + * @returns true if this call constructed the object, or false if the object was already constructed. + */ + bool EnsureObjectConstructed(const T & val) const {if (_objPointer) return false; else {_objPointer = new (_objUnion._buf) T(val); return true;}} + + /** Calls our held object's destructor, if necessary. + * If the object isn't currently valid, then this method is a no-op. + * @returns true if this call destructed the object, or false if the object was already destructed. + */ + bool EnsureObjectDestructed() const {if (_objPointer) {_objPointer->~T(); _objPointer = NULL; return true;} else return false;} + + /** Returns true iff the held object pointer is currently constructed. */ + bool IsObjectConstructed() const {return _objPointer != NULL;} + +private: + mutable T * _objPointer; + mutable union { + T * _junk; // only here ensure object-friendly alignment + uint8 _buf[sizeof(T)]; + } _objUnion; +}; + +}; // end namespace muscle + +#endif diff --git a/util/Directory.cpp b/util/Directory.cpp new file mode 100644 index 00000000..298c6e7c --- /dev/null +++ b/util/Directory.cpp @@ -0,0 +1,265 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#if defined(_WIN32) || defined(WIN32) +# include +# include +#else +# include +# include // needed for chmod codes under MacOS/X +#endif + +#include "system/SystemInfo.h" // for GetFilePathSeparator() +#include "util/Directory.h" + +namespace muscle { + +#ifdef WIN32 + +/* Windows implementation of dirent.h Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. +*/ + +struct dirent +{ + char * d_name; +}; + +typedef struct _DIR +{ + long handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +} DIR; + +static DIR * opendir(const char *name) +{ + DIR * dir = NULL; + if((name)&&(name[0])) + { + size_t base_length = strlen(name); + const char *all = strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + if (((dir = (DIR *)malloc(sizeof *dir)) != NULL) && ((dir->name = (char *) malloc(base_length + strlen(all) + 1)) != NULL)) + { + strcat(strcpy(dir->name, name), all); + if((dir->handle = (long) _findfirst(dir->name, &dir->info)) != -1) dir->result.d_name = NULL; + else + { + free(dir->name); + free(dir); + dir = 0; + } + } + else + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else errno = EINVAL; + return dir; +} + +static int closedir(DIR *dir) +{ + int result = -1; + if (dir) + { + if (dir->handle != -1) result = _findclose(dir->handle); + free(dir->name); + free(dir); + } + if (result == -1) errno = EBADF; + return result; +} + +static struct dirent * readdir(DIR *dir) +{ + struct dirent * result = NULL; + if(dir && dir->handle != -1) + { + if((!dir->result.d_name)||(_findnext(dir->handle, &dir->info) != -1)) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else errno = EBADF; + return result; +} + +static void rewinddir(DIR *dir) +{ + if((dir)&&(dir->handle != -1)) + { + _findclose(dir->handle); + dir->handle = (long) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else errno = EBADF; +} +#endif + +void Directory :: operator++(int) +{ + DIR * dir = (DIR *) _dirPtr; + struct dirent * entry = dir ? readdir(dir) : NULL; + _currentFileName = entry ? entry->d_name : NULL; +} + +void Directory :: Rewind() +{ + if (_dirPtr) rewinddir((DIR *)_dirPtr); + (*this)++; +} + +void Directory :: Reset() +{ + if (_path) + { + delete [] _path; + _path = NULL; + } + if (_dirPtr) + { + closedir((DIR *)_dirPtr); + _dirPtr = NULL; + } +} + +bool Directory :: Exists(const char * dirPath) +{ + Directory d; + return ((dirPath)&&(d.SetDir(dirPath) == B_NO_ERROR)); +} + +status_t Directory :: SetDir(const char * dirPath) +{ + Reset(); + if (dirPath) + { + int pathLen = (int) strlen(dirPath); + const char * sep = GetFilePathSeparator(); + int sepLen = (int) strlen(sep); + int extraBytes = ((pathLen= sepLen)&&(strcmp(&dirPath[dirPathLen-sepLen], sep) == 0)) {sep = ""; sepLen=0;} + + for (const char * fn; (fn = d.GetCurrentFileName()) != NULL; d++) + { + if ((strcmp(fn, ".") != 0)&&(strcmp(fn, "..") != 0)) + { + int fnLen = (int) strlen(fn); + char * catStr = newnothrow_array(char, dirPathLen+sepLen+fnLen+1); + if (catStr == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + + // Compose the sub-item's full path + strcpy(catStr, dirPath); + strcpy(catStr+dirPathLen, sep); + strcpy(catStr+dirPathLen+sepLen, fn); + + // First, try to delete the sub-item as a file; if not, as a directory +#ifdef _MSC_VER + int unlinkRet = _unlink(catStr); // stupid MSVC! +#else + int unlinkRet = unlink(catStr); +#endif + status_t ret = (unlinkRet == 0) ? B_NO_ERROR : Directory::DeleteDirectory(catStr, true); + delete [] catStr; + if (ret != B_NO_ERROR) return ret; + } + } + } +#ifdef WIN32 + return RemoveDirectoryA(dirPath) ? B_NO_ERROR : B_ERROR; +#else + return (rmdir(dirPath) == 0) ? B_NO_ERROR : B_ERROR; +#endif +} + +}; // end namespace muscle diff --git a/util/Directory.h b/util/Directory.h new file mode 100644 index 00000000..b3c364ed --- /dev/null +++ b/util/Directory.h @@ -0,0 +1,102 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleDirectory_h +#define MuscleDirectory_h + +#include "support/MuscleSupport.h" +#include "util/CountedObject.h" +#include "util/RefCount.h" + +namespace muscle { + +/** A cross-platform API for iterating over the contents of a specified filesystem directory. */ +class Directory : public RefCountable, private CountedObject +{ +public: + /** Default constructor: creates an invalid Directory object. */ + Directory() : _path(NULL), _dirPtr(NULL), _currentFileName(NULL) {/* empty */} + + /** Constructor + * @param dirPath The path to the directory to open. This is the same as calling SetDir(dirPath). + */ + Directory(const char * dirPath) : _path(NULL), _dirPtr(NULL), _currentFileName(NULL) {(void) SetDir(dirPath);} + + /** Destructor. Closes our held directory descriptor, if we have one. */ + ~Directory() {Reset();} + + /** Returns true iff we were able to open the specified directory. */ + bool IsValid() const {return (_dirPtr != NULL);} + + /** Returns a pointer to the current file name in the directory iteration, or NULL if there is no current file name. + * Note that the returned string will not remain valid if this Directory object is changed. + */ + const char * GetCurrentFileName() const {return _currentFileName;} + + /** Iterates to the next file name in the directory. */ + void operator++(int); + + /** Rewinds the iteration back to the top of the directory. */ + void Rewind(); + + /** Closes this Directory object's directory and resets it to the invalid state. */ + void Reset(); + + /** Closes any existing held directory and replaces it with the one indicated by (dirPath). + * @param dirPath Path to the new directory to open. SetDir(NULL) is the same as calling Reset(). + * @returns B_NO_ERROR if the new directory was successfully opened, B_ERROR if it was not (directory not found?). + */ + status_t SetDir(const char * dirPath); + + /** This static method will create a directory with the specified path. + * @param dirPath the directory's name (include path if desired) to create. + * @param forceCreateParentDirsIfNecessary If true, we'll create directories above the new directory also if necessary. + * Otherwise we'll fail if the new directory's parent director doesn't exist. + * @param errorIfAlreadyExists If true, and the requested folder already exists, then this method will return B_ERROR. + * If false (the default), then this method will return B_NO_ERROR if the directory already exists. + * @note This method was originally called CreateDirectory() but that was causing namespace collisions with + * some defines in the Microsoft Windows system headers, so I've renamed it to MakeDirectory() to avoid that problem. + * @return B_NO_ERROR on success, or B_ERROR on failure (directory already exists, or permission denied). + */ + static status_t MakeDirectory(const char * dirPath, bool forceCreateParentDirsIfNecessary, bool errorIfAlreadyExists=false); + + /** This static method will delete a directory with the specified path. + * @param dirPath the directory's name (include path if desired) to delete. + * @param forceDeleteSubItemsIfNecessary If true, we'll recursively delete all the items in the directory as well. + * Otherwise we'll fail if the directory to be deleted isn't empty. + * @return B_NO_ERROR on success, or B_ERROR on failure (directory wasn't empty, or permission denied). + */ + static status_t DeleteDirectory(const char * dirPath, bool forceDeleteSubItemsIfNecessary); + + /** Convenience method. Given a path to a file, this method will create any missing directories + * along that path, so that the file can be created. + * @param filePath a path to a file, including the filename itself (the filename part will be ignored) + * @returns B_NO_ERROR on success (i.e. directory was created, or already exists), or B_ERROR on failure (out of memory or permission denied?) + */ + static status_t MakeDirectoryForFile(const char * filePath); + + /** Returns true iff the specified directory exists. + * @param dirPath Path to check to see if it refers to an existing directory. + * @returns true iff (dirPath) does in fact refer to an existing directory. + */ + static bool Exists(const char * dirPath); + + /** Returns the path string that was passed in to this Directory object, or NULL if + * there is no current directory active. Note that the Directory object makes an + * internal copy of the passed in string, so this pointer will be valid even if + * the string passed in to the constructor (or SetDir()) isn't anymore. + * @note this string, if non-NULL, will always have a file-path-separator character at the end. + */ + const char * GetPath() const {return _path;} + +private: + Directory(const Directory & rhs); // deliberately private and unimplemented + + char * _path; + void * _dirPtr; + const char * _currentFileName; +}; +DECLARE_REFTYPES(Directory); + +}; // end namespace muscle + +#endif diff --git a/util/FilePathInfo.cpp b/util/FilePathInfo.cpp new file mode 100644 index 00000000..ce520d1b --- /dev/null +++ b/util/FilePathInfo.cpp @@ -0,0 +1,116 @@ +#include "util/FilePathInfo.h" +#include "system/SystemInfo.h" // for GetFilePathSeparator() + +#ifndef WIN32 +# if defined(__APPLE__) && defined(MUSCLE_64_BIT_PLATFORM) && !defined(_DARWIN_FEATURE_64_BIT_INODE) +# define _DARWIN_FEATURE_64_BIT_INODE // enable 64-bit stat(), if it isn't already enabled +# endif +# include +#endif + +namespace muscle { + +FilePathInfo :: FilePathInfo(bool exists, bool isRegularFile, bool isDir, bool isSymlink, uint64 fileSizeBytes, uint64 aTime, uint64 cTime, uint64 mTime) + : _flags((exists?(1< 0)&&(optFilePath[sLen-1] == *GetFilePathSeparator())) + { + // Special case for paths that end in a slash: we want to ignore the slash (otherwise we can't see files at this location, only folders) + char * temp = newnothrow_array(char, sLen); // not plus 1, because we're going to put the NUL where the trailing slash was + if (temp) + { + memcpy(temp, optFilePath, sLen-1); + temp[sLen-1] = '\0'; + SetFilePath(temp); + delete [] temp; + return; + } + else WARN_OUT_OF_MEMORY; + } + + Reset(); + + if (optFilePath) + { +#ifdef WIN32 + // FILE_FLAG_BACKUP_SEMANTICS is necessary or CreateFile() will + // fail when trying to open a directory. + HANDLE hFile = CreateFileA(optFilePath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + _flags |= (1L< +# define HT_UniversalSinkValueRef template +# define HT_UniversalSinkKeyValueRef template +# define HT_SinkKeyParam HT_KeyParam && +# define HT_SinkValueParam HT_ValueParam && +# define HT_PlunderKey(key) std::move(key) +# define HT_ForwardKey(key) std::forward(key) +# define HT_PlunderValue(val) std::move(val) +# define HT_ForwardValue(val) std::forward(val) +#else +// For earlier versions of C++, use the traditional copy/ref semantics +# define HT_UniversalSinkKeyRef +# define HT_UniversalSinkValueRef +# define HT_UniversalSinkKeyValueRef +# define HT_SinkKeyParam const KeyType & +# define HT_SinkValueParam const ValueType & +# define HT_KeyParam const KeyType & +# define HT_ValueParam const ValueType & +# define HT_PlunderKey(key) (key) +# define HT_ForwardKey(key) (key) +# define HT_PlunderValue(val) (val) +# define HT_ForwardValue(val) (val) +#endif + +namespace muscle { + +// implementation detail; please ignore this! +static const uint32 MUSCLE_HASHTABLE_INVALID_HASH_CODE = (uint32)-1; +static const uint32 MUSCLE_HASHTABLE_INVALID_SLOT_INDEX = (uint32)-1; + +// Forward declarations +template class HashtableIterator; +template class HashtableBase; +template class HashtableMid; + +/** This is a class that literally represents no data. It is meant to be used as + * a placeholder in Hashtables that need to contain only keys and no values. + */ +class Void +{ +public: + /** Default ctor */ + Void() {/* empty */} + + /** Always returns false -- all Voids are created equal and therefore one can't be less than another. */ + bool operator <(const Void &) const {return false;} + + /** Always returns true -- all Voids are created equal */ + bool operator ==(const Void &) const {return true;} + + /** Always returns false -- all Voids are created equal */ + bool operator !=(const Void &) const {return false;} +}; + +/** These flags can be passed to the HashtableIterator constructor (or to the GetIterator()/GetIteratorAt() + * functions in the Hashtable class) to modify the iterator's behaviour. + */ +enum { + HTIT_FLAG_BACKWARDS = (1<<0), // iterate backwards. Conveniently equal to ((bool)true), for backwards compatibility with old code + HTIT_FLAG_NOREGISTER = (1<<1), // don't register with Hashtable object + NUM_HTIT_FLAGS = 2 // number of HTIT_FLAG_* constants that have been defined +}; + +/** + * This class is an iterator object, used for iterating over the set + * of keys or values in a Hashtable. Note that the Hashtable class + * maintains the ordering of its keys and values, unlike many hash table + * implementations. + * + * Given a Hashtable object, you can obtain one or more of these + * iterator objects by calling the Hashtable's GetIterator() method. + * + * This iterator actually contains separate state for two iterations: + * one for iterating over the values in the Hashtable, and one for + * iterating over the keys. These two iterations can be done independently + * of each other. + * + * The most common form for an iteration is this: + * + * for (HashtableIterator iter(table); iter.HasData(); iter++) + * { + * const String & nextKey = iter.GetKey(); + * int nextValue = iter.GetValue(); + * [...] + * } + * + * It is safe to modify or delete a Hashtable during a traversal; the active iterators + * will be automatically notified so that they do the right thing and won't continue to + * reference at any now-invalid items. + */ +template ::Type > class HashtableIterator +{ +public: + /** + * Default constructor. It's here only so that you can include HashtableIterators + * as member variables, in arrays, etc. HashtableIterators created with this + * constructor are "empty", so they won't be useful until you set them equal to a + * HashtableIterator that was returned by Hashtable::GetIterator(). + */ + HashtableIterator(); + + /** Copy Constructor. */ + HashtableIterator(const HashtableIterator & rhs); + + /** Convenience Constructor -- makes an iterator equivalent to the value returned by table.GetIterator(). + * @param table the Hashtable to iterate over. + * @param flags A bit-chord of HTIT_FLAG_* constants (see above). Defaults to zero for default behaviour. + */ + HashtableIterator(const HashtableBase & table, uint32 flags = 0); + + /** Convenience Constructor -- makes an iterator equivalent to the value returned by table.GetIteratorAt(). + * @param table the Hashtable to iterate over. + * @param startAt the first key that should be returned by the iteration. If (startAt) is not in the table, + * the iterator will not return any results. + * @param flags A bit-chord of HTIT_FLAG_* constants (see above). Set to zero to get the default behaviour. + */ + HT_UniversalSinkKeyRef HashtableIterator(const HashtableBase & table, HT_SinkKeyParam startAt, uint32 flags); + + /** Destructor */ + ~HashtableIterator(); + + /** Assignment operator. */ + HashtableIterator & operator=(const HashtableIterator & rhs); + + /** Advances this iterator by one entry in the table. */ + void operator++(int) + { + if (_scratchKeyAndValue.EnsureObjectDestructed() == false) _iterCookie = _owner ? _owner->GetSubsequentEntry(_iterCookie, _flags) : NULL; + UpdateKeyAndValuePointers(); + } + + /** Retracts this iterator by one entry in the table. The opposite of the ++ operator. */ + void operator--(int) {bool b = IsBackwards(); SetBackwards(!b); (*this)++; SetBackwards(b);} + + /** Returns true iff this iterator is pointing to valid key/value data. Do not call GetKey() or GetValue() + * unless this method returns true! Note that the value returned by this method can change if the + * Hashtable is modified. + */ + bool HasData() const {return (_currentKey != NULL);} + + /** + * Returns a reference to the key this iterator is currently pointing at. This method does not change the state of the iterator. + * @note Be careful with this method, if this iterator isn't currently pointing at any key, + * it will return a NULL reference and your program will crash when you try to use it. + * Typically you should only call this function after checking to see that HasData() returns true. + * @note The returned reference is only guaranteed to remain valid for as long as the Hashtable remains unchanged. + */ + const KeyType & GetKey() const + { + assert(_currentKey != NULL); // this makes clang++SA happy + return *_currentKey; + } + + /** + * Returns a reference to the value this iterator is currently pointing at. + * @note Be careful with this method, if this iterator isn't currently pointing at any value, + * it will return a NULL reference and your program will crash when you try to use it. + * Typically you should only call this function after checking to see that HasData() returns true. + * @note The returned reference is only guaranteed to remain valid for as long as the Hashtable remains unchanged. + */ + ValueType & GetValue() const + { + assert(_currentVal != NULL); // this makes clang++SA happy + return *_currentVal; + } + + /** Returns this iterator's HTIT_FLAG_* bit-chord value. */ + uint32 GetFlags() const {return _flags;} + + /** Sets or unsets the HTIT_FLAG_BACWARDS flag on this iterator. + * @param backwards If true, this iterator will be set to iterate backwards from wherever it is currently; + * if false, this iterator will be set to iterate forwards from wherever it is currently. + */ + void SetBackwards(bool backwards) {if (backwards) _flags |= HTIT_FLAG_BACKWARDS; else _flags &= ~HTIT_FLAG_BACKWARDS;} + + /** Returns true iff this iterator is set to iterate in reverse order -- i.e. if HTIT_FLAG_BACKWARDS + * was passed in to the constructor, or if SetBackwards(true) was called. + */ + bool IsBackwards() const {return ((_flags & HTIT_FLAG_BACKWARDS) != 0);} + +private: + friend class HashtableBase; + + HT_UniversalSinkKeyValueRef void SetScratchValues(HT_SinkKeyParam key, HT_SinkValueParam val) + { + KeyAndValue & kav = _scratchKeyAndValue.GetObject(); + kav._key = HT_ForwardKey(key); + kav._value = HT_ForwardValue(val); + } + + void UpdateKeyAndValuePointers() + { + if (_scratchKeyAndValue.IsObjectConstructed()) + { + _currentKey = &_scratchKeyAndValue.GetObjectUnchecked()._key; + _currentVal = &_scratchKeyAndValue.GetObjectUnchecked()._value; + } + else if ((_iterCookie)&&(_owner)) // (_owner) test isn't strictly necessary, but it keeps clang++ happy + { + _currentKey = &_owner->GetKeyFromCookie(_iterCookie); + _currentVal = &_owner->GetValueFromCookie(_iterCookie); + } + else + { + _currentKey = NULL; + _currentVal = NULL; + } + } + + void * _scratchSpace[2]; // ignore this; it's temp scratch space used by EnsureSize(). + + void * _iterCookie; + const KeyType * _currentKey; // cached result, so that GetKey() can be a branch-free inline method + ValueType * _currentVal; // cached result, so that GetValue() can be a branch-free inline method + + uint32 _flags; + HashtableIterator * _prevIter; // for the doubly linked list so that the table can notify us if it is modified + HashtableIterator * _nextIter; // for the doubly linked list so that the table can notify us if it is modified + const HashtableBase * _owner; // table that we are associated with + + // Used for emergency storage of scratch values + class KeyAndValue + { + public: + KeyAndValue() {/* empty */} + HT_UniversalSinkKeyValueRef KeyAndValue(HT_SinkKeyParam key, HT_SinkValueParam value) : _key(HT_ForwardKey(key)), _value(HT_ForwardValue(value)) {/* empty */} + + KeyType _key; + ValueType _value; + }; + DemandConstructedObject _scratchKeyAndValue; + bool _okayToUnsetThreadID; +}; + +/** This internal superclass is an implementation detail and should not be instantiated directly. Instantiate a Hashtable, OrderedKeysHashtable, or OrderedValuesHashtable instead. */ +template class HashtableBase +{ +public: + /** Returns the number of items stored in the table. */ + uint32 GetNumItems() const {return _numItems;} + + /** Convenience method; Returns true iff the table is empty (i.e. if GetNumItems() is zero). */ + bool IsEmpty() const {return (_numItems == 0);} + + /** Convenience method; Returns true iff the table is non-empty (i.e. if GetNumItems() is non-zero). */ + bool HasItems() const {return (_numItems > 0);} + + /** Returns true iff the table contains a mapping with the given key. (O(1) search time) */ + bool ContainsKey(const KeyType & key) const {return (this->GetEntry(this->ComputeHash(key), key) != NULL);} + + /** Returns true iff the table contains a mapping with the given value. (O(n) search time) */ + bool ContainsValue(const ValueType & value) const; + + /** Returns true iff this Hashtable has the same contents as (rhs). + * @param rhs The table to compare this table against + * @param considerOrdering Whether the order of the key/value pairs within the tables should be considered as part of "equality". Defaults to false. + * @returns true iff (rhs) is equal to to (this), false if they differ. + */ + bool IsEqualTo(const HashtableBase & rhs, bool considerOrdering = false) const; + + /** Returns true iff the keys in this Hashtable are the same as the keys in (rhs). + * Note that the ordering of the keys is not considered, only their values. Values in either table are not considered, either. + * @param rhs The Hashtable whose keys we should compare against our keys. + * @returns true iff both Hashtables have the same set of Key objects. + */ + template bool AreKeySetsEqual(const HashtableBase & rhs) const; + + /** Returns the given key's position in the hashtable's linked list, or -1 if the key wasn't found. O(n) count time (if the key exists, O(1) if it doesn't) */ + int32 IndexOfKey(const KeyType & key) const; + + /** Returns the index of the first (or last) occurrance of (value), or -1 if (value) does + * not exist in this Hashtable. Note that the search is O(N). + * @param value The value to search for. + * @param searchBackwards If set to true, the table will be searched in reverse order. + * and the index returned (if valid) will be the index of the + * last instance of (value) in the table, rather than the first. + * @returns The index into the table, or -1 if (value) doesn't exist in the table's set of values. + */ + int32 IndexOfValue(const ValueType & value, bool searchBackwards = false) const; + + /** Attempts to retrieve the associated value from the table for a given key. (O(1) lookup time) + * @param key The key to use to look up a value. + * @param setValue On success, the associated value is copied into this object. + * @return B_NO_ERROR on success, B_ERROR if their was no value found for the given key. + */ + status_t GetValue(const KeyType & key, ValueType & setValue) const; + + /** Retrieve a pointer to the associated value object for the given key. (O(1) lookup time) + * @param key The key to use to look up a value. + * @return A pointer to the internally held value object for the given key, + * or NULL of no object was found. Note that this object is only + * guaranteed to remain valid as long as the Hashtable remains unchanged. + */ + ValueType * GetValue(const KeyType & key); + + /** As above, but read-only. + * @param key The key to use to look up a value. + * @return A pointer to the internally held value object for the given key, + * or NULL of no object was found. Note that this object is only + * guaranteed to remain valid as long as the Hashtable remains unchanged. + */ + const ValueType * GetValue(const KeyType & key) const; + + /** Given a lookup key, returns the a copy of the actual key as held by the table. + * This method is only useful in rare situations where the hashing or comparison + * functions are such that lookupKeys and held keys are not guaranteed equivalent. + * @param lookupKey The key used to look up the held key object. + * @param setKey On success, the actual key held in the table is placed here. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t GetKey(const KeyType & lookupKey, KeyType & setKey) const; + + /** Given a key, returns a pointer to the actual key object in this table that matches + * that key, or NULL if there is no matching key. This method is only useful in rare + * situations where the hashing or comparison functions are such that lookup keys and + * held keys are not guaranteed equivalent. + * @param lookupKey The key used to look up the key object + * @return A pointer to an internally held key object on success, or NULL on failure. + */ + const KeyType * GetKey(const KeyType & lookupKey) const; + + /** The iterator type that goes with this HashtableBase type */ + typedef HashtableIterator IteratorType; + + /** Get an iterator for use with this table. + * @param flags A bit-chord of HTIT_FLAG_* constants (see above). Defaults to zero, for default behaviour. + * @return an iterator object that can be used to examine the items in the hash table, starting at + * the specified key. If the specified key is not in this table, an empty iterator will be returned. + */ + IteratorType GetIterator(uint32 flags = 0) const {return IteratorType(*this, flags);} + + /** Get an iterator for use with this table, starting at the given entry. + * @param startAt The key in this table to start the iteration at. + * @param flags A bit-chord of HTIT_FLAG_* constants (see above). Defaults to zero, for default behaviour. + * @return an iterator object that can be used to examine the items in the hash table, starting at + * the specified key. If the specified key is not in this table, an empty iterator will be returned. + */ + HT_UniversalSinkKeyRef IteratorType GetIteratorAt(HT_KeyParam startAt, uint32 flags = 0) const + { + return HashtableIterator(*this, HT_ForwardKey(startAt), flags); + } + + /** Returns a pointer to the (index)'th key in this Hashtable. + * (This Hashtable class keeps its entries in a well-defined order) + * Note that this method is an O(N) operation, so for iteration, always use GetIterator() instead. + * @param index Index of the key to return a pointer to. Should be in the range [0, GetNumItems()-1]. + * @returns Pointer to the key at position (index) on success, or NULL on failure (bad index?) + */ + const KeyType * GetKeyAt(uint32 index) const; + + /** Returns the (index)'th key in this Hashtable. + * (This Hashtable class keeps its entries in a well-defined order) + * Note that this method is an O(N) operation, so for iteration, always use GetIterator() instead. + * @param index Index of the key to return a pointer to. Should be in the range [0, GetNumItems()-1]. + * @param retKey On success, the contents of the (index)'th key will be written into this object. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t GetKeyAt(uint32 index, KeyType & retKey) const; + + /** Removes a mapping from the table. (O(1) removal time) + * @param key The key of the key-value mapping to remove. + * @return B_NO_ERROR if a key was found and the mapping removed, or B_ERROR if the key wasn't found. + */ + status_t Remove(const KeyType & key) {return RemoveAux(key, NULL);} + + /** Removes the mapping with the given (key) and places its value into (setRemovedValue). (O(1) removal time) + * @param key The key of the key-value mapping to remove. + * @param setRemovedValue On success, the removed value is copied into this object. + * @return B_NO_ERROR if a key was found and the mapping removed, or B_ERROR if the key wasn't found. + */ + status_t Remove(const KeyType & key, ValueType & setRemovedValue) {return RemoveAux(key, &setRemovedValue);} + + /** Convenience method: For each key in the passed-in-table, removes that key (and its associated value) from this table. + * @param pairs A table containing keys that should be removed from this table. + * @returns the number of items actually removed from this table. + */ + uint32 Remove(const HashtableBase & pairs); + + /** Removes a mapping from the table and returns the removed value. + * If the mapping did not exist in the table, a default value is returned instead. + * @param key The key of the key-value mapping to remove. + * @return The value of the removed key-value mapping, or a default-constructed value if the key wasn't found. + */ + ValueType RemoveWithDefault(const KeyType & key) {ValueType ret; return (RemoveAux(key, &ret) == B_NO_ERROR) ? ret : GetDefaultValue();} + + /** Removes a mapping from the table and returns the removed value. + * If the mapping did not exist in the table, the specified default value is returned instead. + * @param key The key of the key-value mapping to remove. + * @param defaultValue The default value to return if (key) wasn't found. + * @return The value of the removed key-value mapping, or the specified default value if the key wasn't found. + */ + HT_UniversalSinkValueRef ValueType RemoveWithDefault(const KeyType & key, HT_SinkValueParam defaultValue) {ValueType ret; return (RemoveAux(key, &ret) == B_NO_ERROR) ? ret : HT_ForwardValue(defaultValue);} + + /** Convenience method: Removes from this Hashtable all key/value pairs for which the same key is not present in (pairs) + * @returns the number of items actually removed from this table. + */ + uint32 Intersect(const HashtableBase & pairs); + + /** Convenience method: Removes the first key/value mapping in the table. (O(1) removal time) + * @return B_NO_ERROR if the first mapping was removed, or B_ERROR if this table was empty. + */ + status_t RemoveFirst() {return RemoveEntryByIndex(_iterHeadIdx, NULL);} + + /** Convenience method: Removes the first key/value mapping in the table and places the removed key + * into (setRemovedKey). (O(1) removal time) + * @param setRemovedKey On success, the removed key is copied into this object. + * @return B_NO_ERROR if the first mapping was removed and the key placed into (setRemovedKey), or B_ERROR if the table was empty. + */ + status_t RemoveFirst(KeyType & setRemovedKey) + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + if (e == NULL) return B_ERROR; + setRemovedKey = e->_key; + return RemoveEntry(e, NULL); + } + + /** Convenience method: Removes the first key/value mapping in the table and places its + * key into (setRemovedKey) and its value into (setRemovedValue). (O(1) removal time) + * @param setRemovedKey On success, the removed key is copied into this object. + * @param setRemovedValue On success, the removed value is copied into this object. + * @return B_NO_ERROR if the first mapping was removed and the key and value placed into the arguments, or B_ERROR if the table was empty. + */ + status_t RemoveFirst(KeyType & setRemovedKey, ValueType & setRemovedValue) + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + if (e == NULL) return B_ERROR; + setRemovedKey = e->_key; + return RemoveEntry(e, &setRemovedValue); + } + + /** Convenience method: Removes the last key/value mapping in the table. (O(1) removal time) + * @return B_NO_ERROR if the last mapping was removed, or B_ERROR if the table was empty. + */ + status_t RemoveLast() + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? RemoveEntry(e, NULL) : B_ERROR; + } + + /** Convenience method: Removes the last key/value mapping in the table and places the removed key + * into (setRemovedKey). (O(1) removal time) + * @param setRemovedKey On success, the removed key is copied into this object. + * @return B_NO_ERROR if the last mapping was removed and the key placed into (setRemovedKey), or B_ERROR if the table was empty. + */ + status_t RemoveLast(KeyType & setRemovedKey) + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + if (e == NULL) return B_ERROR; + setRemovedKey = e->_key; + return RemoveEntry(e, NULL); + } + + /** Convenience method: Removes the last key/value mapping in the table and places its + * key into (setRemovedKey) and its value into (setRemovedValue). (O(1) removal time) + * @param setRemovedKey On success, the removed key is copied into this object. + * @param setRemovedValue On success, the removed value is copied into this object. + * @return B_NO_ERROR if the last mapping was removed and the key and value placed into the arguments, or B_ERROR if the table was empty. + */ + status_t RemoveLast(KeyType & setRemovedKey, ValueType & setRemovedValue) + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + setRemovedKey = e->_key; + return RemoveEntry(e, &setRemovedValue); + } + + /** Removes all mappings from the hash table. (O(N) clear time) + * @param releaseCachedData If set true, we will immediately free any buffers we may contain. + * Otherwise, we'll keep them around in case they can be re-used later on. + */ + void Clear(bool releaseCachedData = false); + + /** Computes the average number of key-comparisons that will be required for + * looking up the current contents of this table. + * Note that this method iterates over the entire table, so it should only + * be called when performance is not important (e.g. when debugging hash functions) + * @param printStatistics If true, text describing the table's layout will be printed to stdout also. + * @returns The average number of key-comparisons needed to find an item in this table, given its current contents. + */ + float CountAverageLookupComparisons(bool printStatistics = false) const; + + /** Sorts the iteration-traversal list of this table by key, using the given key-comparison functor object. + * This method uses a very efficient O(log(N)) MergeSort algorithm. + * @param func The key-comparison functor to use. + * @param optCompareCookie Optional cookie value to pass to func.Compare(). Its meaning is specific to the functor type. + */ + template void SortByKey(const KeyCompareFunctorType & func, void * optCompareCookie = NULL); + + /** As above, except that the comparison functor is not specified. The default comparison functor for our key type will be used. + * @param optCompareCookie Optional cookie value to pass to func.Compare(). Its meaning is specific to the functor type. + */ + void SortByKey(void * optCompareCookie = NULL) {SortByKey(CompareFunctor(), optCompareCookie);} + + /** Sorts the iteration-traversal list of this table by value, using the given value-comparison functor object. + * This method uses a very efficient O(log(N)) MergeSort algorithm. + * @param func The value-comparison functor to use. + * @param optCompareCookie Optional cookie value to pass to func.Compare(). Its meaning is specific to the functor type. + */ + template void SortByValue(const ValueCompareFunctorType & func, void * optCompareCookie = NULL); + + /** As above, except that the comparison functor is not specified. The default comparison functor for our value type will be used. + * @param optCompareCookie Optional cookie value to pass to func.Compare(). Its meaning is specific to the functor type. + */ + void SortByValue(void * optCompareCookie = NULL) {SortByValue(CompareFunctor(), optCompareCookie);} + + /** Returns true iff auto-sort is currently enabled on this Hashtable. Note that auto-sort only has an effect on + * OrderedKeysHashtable and OrderedValuesHashtable objects; for plain Hashtable objects it has no effect. + */ + bool GetAutoSortEnabled() const {return _autoSortEnabled;} + + /** This method can be used to set the "cookie value" that will be passed in to the comparison functor + * objects that this class is templated on. The functor objects can then use this value to access + * any context information that might be necessary to do their comparison. + * This value will be passed along during whenever a user-defined compare functor is called implicitly. + */ + void SetCompareCookie(void * cookie) {_compareCookie = cookie;} + + /** Returns the current comparison cookie, as previously set by SetCompareCookie(). Default value is NULL. */ + void * GetCompareCookie() const {return _compareCookie;} + + /** This method does an efficient zero-copy swap of this hash table's contents with those of (swapMe). + * That is to say, when this method returns, (swapMe) will be identical to the old state of this + * Hashtable, and this Hashtable will be identical to the old state of (swapMe). + * Any active iterators present for either table will swap owners also, becoming associated with the other table. + * @param swapMe The table whose contents and iterators are to be swapped with this table's. + */ + void SwapContents(HashtableBase & swapMe) {SwapContentsAux(swapMe, true);} + + /** Moves the given entry to the head of the HashtableIterator traversal sequence. + * Note that calling this method is generally a bad idea of the table is in auto-sort mode, + * as it is likely to unsort the traversal ordering and thus break auto-sorting. However, + * calling Sort() will restore the sort-order and make auto-sorting work again) + * @param moveMe Key of the item to be moved to the front of the sequence. + * @return B_NO_ERROR on success, or B_ERROR if (moveMe) was not found in the table. + */ + status_t MoveToFront(const KeyType & moveMe); + + /** Moves the given entry to the tail of the HashtableIterator traversal sequence. + * Note that calling this method is generally a bad idea of the table is in auto-sort mode, + * as it is likely to unsort the traversal ordering and thus break auto-sorting. However, + * calling Sort() will restore the sort-order and make auto-sorting work again) + * @param moveMe Key of the item to be moved to the end of the sequence. + * @return B_NO_ERROR on success, or B_ERROR if (moveMe) was not found in the table. + */ + status_t MoveToBack(const KeyType & moveMe); + + /** Moves the given entry to the spot just in front of the other specified entry in the + * HashtableIterator traversal sequence. + * Note that calling this method is generally a bad idea of the table is in auto-sort mode, + * as it is likely to unsort the traversal ordering and thus break auto-sorting. However, + * calling Sort() will restore the sort-order and make auto-sorting work again) + * @param moveMe Key of the item to be moved. + * @param toBeforeMe Key of the item that (moveMe) should be placed in front of. + * @return B_NO_ERROR on success, or B_ERROR if (moveMe) was not found in the table, + * or was the same item as (toBeforeMe). + */ + status_t MoveToBefore(const KeyType & moveMe, const KeyType & toBeforeMe); + + /** Moves the given entry to the spot just behind the other specified entry in the + * HashtableIterator traversal sequence. + * Note that calling this method is generally a bad idea of the table is in auto-sort mode, + * as it is likely to unsort the traversal ordering and thus break auto-sorting. However, + * calling Sort() will restore the sort-order and make auto-sorting work again) + * @param moveMe Key of the item to be moved. + * @param toBehindMe Key of the item that (moveMe) should be placed behind. + * @return B_NO_ERROR on success, or B_ERROR if (moveMe) was not found in the table, + * or was the same item as (toBehindMe). + */ + status_t MoveToBehind(const KeyType & moveMe, const KeyType & toBehindMe); + + /** Moves the given entry to the (nth) position in the HashtableIterator traversal sequence. + * Note that this is an O(N) operation. + * Note that calling this method is generally a bad idea of the table is in auto-sort mode, + * as it is likely to unsort the traversal ordering and thus break auto-sorting. However, + * calling Sort() will restore the sort-order and make auto-sorting work again) + * @param moveMe Key of the item to be moved. + * @param toPosition The position that this item should be moved to. Zero would move + * the item to the head of the traversal sequence, one to the second + * position, and so on. Values greater than or equal to the number + * of items in the Hashtable will move the item to the last position. + * @return B_NO_ERROR on success, or B_ERROR if (moveMe) was not found in the table. + */ + status_t MoveToPosition(const KeyType & moveMe, uint32 toPosition); + + /** Convenience synonym for GetValue() */ + status_t Get(const KeyType & key, ValueType & setValue) const {return GetValue(key, setValue);} + + /** Convenience synonym for GetValue() */ + ValueType * Get(const KeyType & key) {return GetValue(key);} + + /** As above, but read-only. */ + const ValueType * Get(const KeyType & key) const {return GetValue(key);} + + /** Similar to Get(), except that if the specified key is not found, + * the ValueType's default value is returned. + * @param key The key whose value should be returned. + * @returns (key)'s associated value, or the default ValueType value. + */ + const ValueType & GetWithDefault(const KeyType & key) const + { + const ValueType * v = Get(key); + return v ? *v : GetDefaultValue(); + } + + /** The const [] operator returns the Value associated with the specified key, or a + * default-constructed Value object if the specified key is not present in this Hashtable. + * That is to say, calling myTable[5] is equivalent to calling myTable.GetWithDefault(5). + * @param key A key whose associated value we wish to look up. + * @returns the associated value, or a default value if (key) wasn't present in this Hashtable. + */ + const ValueType & operator[](const KeyType & key) const {return GetWithDefault(key);} + + /** Similar to Get(), except that if the specified key is not found, + * the specified default value is returned. Note that this method + * returns its value by value, not by reference, to avoid any + * dangling-reference issues that might occur if (defaultValue) + * was a temporary. + * @param key The key whose value should be returned. + * @param defaultValue The value to return is (key) is not in the table. + * Defaults to a default-constructed item of the value type. + * @returns (key)'s associated value, or (defaultValue). + */ + const ValueType & GetWithDefault(const KeyType & key, const ValueType & defaultValue) const + { + const ValueType * v = Get(key); + return v ? *v : defaultValue; + } + + /** Convenience method. Returns a pointer to the first key in our iteration list, or NULL if the table is empty. */ + const KeyType * GetFirstKey() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? &e->_key : NULL; + } + + /** Convenience method. Returns a reference to the first key in our iteration list, or a default key if the table is empty. */ + const KeyType & GetFirstKeyWithDefault() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? e->_key : GetDefaultKey(); + } + + /** Convenience method. Returns a reference to the first key in our iteration list, or the specified default key object if the table is empty. + * @param defaultKey The key value to return if the table is empty. + */ + const KeyType & GetFirstKeyWithDefault(const KeyType & defaultKey) const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? e->_key : defaultKey; + } + + /** As above, but read-ony. */ + const KeyType * GetLastKey() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? & e->_key : NULL; + } + + /** Convenience method. Returns a reference to the last key in our iteration list, or a default key object if the table is empty. */ + const KeyType & GetLastKeyWithDefault() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? e->_key : GetDefaultKey(); + } + + /** Convenience method. Returns a reference to the last key in our iteration list, or the specified default key object if the table is empty. + * @param defaultKey The key value to return if the table is empty. + */ + const KeyType & GetLastKeyWithDefault(const KeyType & defaultKey) const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? e->_key : defaultKey; + } + + /** Convenience method. Returns a pointer to the last value in our iteration list, or NULL if the table is empty. */ + ValueType * GetFirstValue() + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? &e->_value : NULL; + } + + /** As above, but read-only. */ + const ValueType * GetFirstValue() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? &e->_value : NULL; + } + + /** Convenience method. Returns a reference to the first value in our iteration list, or a default value object if the table is empty. */ + const ValueType & GetFirstValueWithDefault() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? e->_value : GetDefaultValue(); + } + + /** Convenience method. Returns a reference to the first value in our iteration list, or the specified default value object if the table is empty. + * @param defaultValue The value to return if the table is empty. + */ + const ValueType & GetFirstValueWithDefault(const ValueType & defaultValue) const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + return e ? e->_value : defaultValue; + } + + /** Convenience method. Returns a pointer to the last value in our iteration list, or NULL if the table is empty. */ + ValueType * GetLastValue() + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? &e->_value : NULL; + } + + /** As above, but read-only. */ + const ValueType * GetLastValue() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? &e->_value : NULL; + } + + /** Convenience method. Returns a reference to the last value in our iteration list, or a default value object if the table is empty. */ + const ValueType & GetLastValueWithDefault() const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? e->_value : GetDefaultValue(); + } + + /** Convenience method. Returns a reference to the last value in our iteration list, or the specified default value object if the table is empty. + * @param defaultValue The value to return if the table is empty. + */ + const ValueType & GetLastValueWithDefault(const ValueType & defaultValue) const + { + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterTailIdx); + return e ? e->_value : defaultValue; + } + + /** Returns the number of table-slots that we currently have allocated. Since we often + * pre-allocate slots to avoid unnecessary reallocations, this number will usually be + * greater than the value returned by GetNumItems(). It will never be less than that value. + */ + uint32 GetNumAllocatedItemSlots() const {return _tableSize;} + + /** Returns a reference to a default-constructed Key item. The reference will remain valid for as long as this Hashtable is valid. */ + const KeyType & GetDefaultKey() const {return GetDefaultObjectForType();} + + /** Returns a reference to a default-constructed Value item. The reference will remain valid for as long as this Hashtable is valid. */ + const ValueType & GetDefaultValue() const {return GetDefaultObjectForType();} + + /** Returns the number of bytes of memory taken up by this Hashtable's data */ + uint32 GetTotalDataSize() const + { + uint32 sizePerItem = 0; + switch(this->_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + sizePerItem = sizeof(HashtableEntry); + break; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + sizePerItem = sizeof(HashtableEntry); + break; +#endif + + default: + sizePerItem = sizeof(HashtableEntry); + break; + } + return sizeof(*this)+(this->GetNumAllocatedItemSlots()*sizePerItem); + } + +protected: + status_t EnsureTableAllocated(); + +private: + enum { + HTE_INDEX_BUCKET_PREV = 0, // slot index: for making linked lists in our bucket + HTE_INDEX_BUCKET_NEXT, // slot index: for making linked lists in our bucket + HTE_INDEX_ITER_PREV, // slot index: for user's table iteration + HTE_INDEX_ITER_NEXT, // slot index: for user's table iteration + HTE_INDEX_MAP_TO, // slot index + HTE_INDEX_MAPPED_FROM, // slot index + NUM_HTE_INDICES + }; + + enum { + TABLE_INDEX_TYPE_UINT8 = 0, + TABLE_INDEX_TYPE_UINT16, + TABLE_INDEX_TYPE_UINT32, + NUM_TABLE_INDEX_TYPES + }; + + void SwapContentsAux(HashtableBase & swapMe, bool swapIterators); + + /// @cond HIDDEN_SYMBOLS + HashtableBase(uint32 tableSize, bool autoSortEnabled, void * cookie) : _numItems(0), _tableSize(tableSize), _tableIndexType(ComputeTableIndexTypeForTableSize(tableSize)), _table(NULL), _iterHeadIdx(MUSCLE_HASHTABLE_INVALID_SLOT_INDEX), _iterTailIdx(MUSCLE_HASHTABLE_INVALID_SLOT_INDEX), _freeHeadIdx(MUSCLE_HASHTABLE_INVALID_SLOT_INDEX), _autoSortEnabled(autoSortEnabled), _compareCookie(cookie), _iterList(NULL) {/* empty */} + ~HashtableBase() {Clear(true);} + + void CopyFromAux(const HashtableBase & rhs) + { + bool wasEmpty = IsEmpty(); + const HashtableEntryBase * e = rhs.IndexToEntryChecked(rhs._iterHeadIdx); // start of linked list to iterate through + while(e) + { + HashtableEntryBase * myEntry = wasEmpty ? NULL : this->GetEntry(e->_hash, e->_key); + if (myEntry) myEntry->_value = e->_value; + else + { + this->InsertIterationEntry(PutAuxAux(e->_hash, e->_key, e->_value), this->IndexToEntryChecked(_iterTailIdx)); + this->_numItems++; + } + e = rhs.GetEntryIterNextChecked(e); + } + } + + uint32 ComputeTableIndexTypeForTableSize(uint32 tableSize) {return (tableSize < 255) ? TABLE_INDEX_TYPE_UINT8 : ((tableSize < 65535) ? TABLE_INDEX_TYPE_UINT16 : TABLE_INDEX_TYPE_UINT32);} + + /** This class is an implementation detail, please ignore it. Do not access it directly. */ + class HashtableEntryBase + { + protected: + // Note: All member variables are initialized by CreateEntriesArray(), not by the ctor! + HashtableEntryBase() {/* empty */} + ~HashtableEntryBase() {/* empty */} + + public: + uint32 _hash; // precalculated for efficiency + KeyType _key; // used for '==' checking + ValueType _value; // payload + }; + uint32 PopFromFreeList(HashtableEntryBase * e, uint32 freeHeadIdx); + + /** This class is an implementation detail, please ignore it. Do not access it directly. */ + template class HashtableEntry : public HashtableEntryBase + { + public: + // Note: All member variables are initialized by CreateEntriesArray(), not by the ctor! + HashtableEntry() {/* empty */} + ~HashtableEntry() {/* empty */} + + /** Returns this entry to the free-list, and resets its key and value to their default values. */ + void PushToFreeList(const KeyType & defaultKey, const ValueType & defaultValue, uint32 & getRetFreeHeadIdx, HashtableBase * table) + { + this->_indices[HTE_INDEX_ITER_PREV] = this->_indices[HTE_INDEX_ITER_NEXT] = this->_indices[HTE_INDEX_BUCKET_PREV] = (IndexType)-1; + this->_indices[HTE_INDEX_BUCKET_NEXT] = (IndexType) getRetFreeHeadIdx; + + uint32 thisIdx = table->EntryToIndexUnchecked(this); + if (getRetFreeHeadIdx != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) static_cast(table->IndexToEntryUnchecked(getRetFreeHeadIdx))->_indices[HTE_INDEX_BUCKET_PREV] = (IndexType) thisIdx; + getRetFreeHeadIdx = thisIdx; + + this->_hash = MUSCLE_HASHTABLE_INVALID_HASH_CODE; + this->_key = defaultKey; // NOTE: These lines could have side-effects due to code in the templatized + this->_value = defaultValue; // classes! So it's important that the Hashtable be in a consistent state here + } + + /** Removes this entry from the free list, so that we are ready for use. + * @param freeHead Index of the current head of the free list + * @param table Pointer to the first entry in the HashtableEntry array + * @returns the index of the new head of the free list + */ + uint32 PopFromFreeList(uint32 freeHeadIdx, HashtableBase * table) + { + HashtableEntry * h = static_cast(table->GetEntriesArrayPointer()); + IndexType & myBucketNext = this->_indices[HTE_INDEX_BUCKET_NEXT]; + IndexType & myBucketPrev = this->_indices[HTE_INDEX_BUCKET_PREV]; + if (myBucketNext != (IndexType)-1) h[myBucketNext]._indices[HTE_INDEX_BUCKET_PREV] = myBucketPrev; + if (myBucketPrev != (IndexType)-1) h[myBucketPrev]._indices[HTE_INDEX_BUCKET_NEXT] = myBucketNext; + uint32 ret = (freeHeadIdx == table->EntryToIndexUnchecked(this)) ? ((myBucketNext == (IndexType)-1) ? MUSCLE_HASHTABLE_INVALID_SLOT_INDEX : myBucketNext) : freeHeadIdx; + myBucketPrev = myBucketNext = (IndexType)-1; + return ret; + } + + /** Allocates and returns an array if (size) HashtableEnty objects. */ + static HashtableEntryBase * CreateEntriesArray(uint32 size) + { + HashtableEntry * ret = newnothrow_array(HashtableEntry,size); + if (ret) + { + for (uint32 i=0; i_hash = MUSCLE_HASHTABLE_INVALID_HASH_CODE; + e->_indices[HTE_INDEX_BUCKET_PREV] = (IndexType)(i-1); // yes, _bucketPrev will be set to (IndexType)-1 when (i==0) + e->_indices[HTE_INDEX_BUCKET_NEXT] = (IndexType)(i+1); + e->_indices[HTE_INDEX_ITER_PREV] = e->_indices[HTE_INDEX_ITER_NEXT] = (IndexType)-1; + e->_indices[HTE_INDEX_MAP_TO] = e->_indices[HTE_INDEX_MAPPED_FROM] = (IndexType)i; + } + ret[size-1]._indices[HTE_INDEX_BUCKET_NEXT] = (IndexType)-1; + } + else WARN_OUT_OF_MEMORY; + return ret; + } + + IndexType _indices[NUM_HTE_INDICES]; + }; + + uint32 GetEntryIndexValue(const HashtableEntryBase * entry, uint32 whichIndex) const + { + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + {uint8 r = (static_cast *>(entry))->_indices[whichIndex]; return (r==(uint8)-1)?MUSCLE_HASHTABLE_INVALID_SLOT_INDEX:((uint32)r);} +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + {uint16 r = (static_cast *>(entry))->_indices[whichIndex]; return (r==(uint16)-1)?MUSCLE_HASHTABLE_INVALID_SLOT_INDEX:((uint32)r);} +#endif + + default: + {uint32 r = (static_cast *>(entry))->_indices[whichIndex]; return r;} + } + } + + void SetEntryIndexValue(HashtableEntryBase * entry, uint32 whichIndex, uint32 value) const + { + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + static_cast *>(entry)->_indices[whichIndex] = (value == MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) ? 255 : ((uint8) value); + break; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + static_cast *>(entry)->_indices[whichIndex] = (value == MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) ? 65535 : ((uint16) value); + break; +#endif + + default: + static_cast *>(entry)->_indices[whichIndex] = (uint32) value; + break; + } + } + + // Convenience methods (these check the requested value, and return NULL if it's not valid) + HashtableEntryBase * GetEntryBucketNextChecked(const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT));} + HashtableEntryBase * GetEntryBucketPrevChecked(const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV));} + HashtableEntryBase * GetEntryIterNextChecked( const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_ITER_NEXT));} + HashtableEntryBase * GetEntryIterPrevChecked( const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_ITER_PREV));} + HashtableEntryBase * GetEntryMapToChecked( const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_MAP_TO));} + HashtableEntryBase * GetEntryMappedFromChecked(const HashtableEntryBase * e) const {return this->IndexToEntryChecked(this->GetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM));} + + // Convenience methods (these assume the requested value will be valid -- be careful, as calling them when it isn't valid will mess things up badly!) + HashtableEntryBase * GetEntryBucketNextUnchecked(const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT));} + HashtableEntryBase * GetEntryBucketPrevUnchecked(const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV));} + HashtableEntryBase * GetEntryIterNextUnchecked( const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_ITER_NEXT));} + HashtableEntryBase * GetEntryIterPrevUnchecked( const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_ITER_PREV));} + HashtableEntryBase * GetEntryMapToUnchecked( const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_MAP_TO));} + HashtableEntryBase * GetEntryMappedFromUnchecked(const HashtableEntryBase * e) const {return this->IndexToEntryUnchecked(this->GetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM));} + + // Convenience methods (these methods return the index value as a uint32) + uint32 GetEntryBucketNext(const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT);} + uint32 GetEntryBucketPrev(const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV);} + uint32 GetEntryIterNext( const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_ITER_NEXT);} + uint32 GetEntryIterPrev( const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_ITER_PREV);} + uint32 GetEntryMapTo( const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_MAP_TO);} + uint32 GetEntryMappedFrom(const HashtableEntryBase * e) const {return this->GetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM);} + + // Convenience methods (these check the requested value, and set the field to MUSCLE_HASHTABLE_INVALID_SLOT_INDEX if it isn't valid) + void SetEntryBucketNextChecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT, this->EntryToIndexChecked(v));} + void SetEntryBucketPrevChecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV, this->EntryToIndexChecked(v));} + void SetEntryIterNextChecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_NEXT, this->EntryToIndexChecked(v));} + void SetEntryIterPrevChecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_PREV, this->EntryToIndexChecked(v));} + void SetEntryMapToChecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_MAP_TO, this->EntryToIndexChecked(v));} + void SetEntryMappedFromChecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->GetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM, this->EntryToIndexChecked(v));} + + // Convenience methods (these assume the requested value will be valid -- be careful, as calling them when it isn't valid will mess things up badly!) + void SetEntryBucketNextUnchecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT, this->EntryToIndexUnchecked(v));} + void SetEntryBucketPrevUnchecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV, this->EntryToIndexUnchecked(v));} + void SetEntryIterNextUnchecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_NEXT, this->EntryToIndexUnchecked(v));} + void SetEntryIterPrevUnchecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_PREV, this->EntryToIndexUnchecked(v));} + void SetEntryMapToUnchecked( HashtableEntryBase * e, const HashtableEntryBase * v) {this->SetEntryIndexValue(e, HTE_INDEX_MAP_TO, this->EntryToIndexUnchecked(v));} + void SetEntryMappedFromUnchecked(HashtableEntryBase * e, const HashtableEntryBase * v) {this->GetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM, this->EntryToIndexUnchecked(v));} + + // Convenience methods + void SetEntryBucketNext(HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_NEXT, idx);} + void SetEntryBucketPrev(HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_BUCKET_PREV, idx);} + void SetEntryIterNext( HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_NEXT, idx);} + void SetEntryIterPrev( HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_ITER_PREV, idx);} + void SetEntryMapTo( HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_MAP_TO, idx);} + void SetEntryMappedFrom(HashtableEntryBase * e, uint32 idx) {this->SetEntryIndexValue(e, HTE_INDEX_MAPPED_FROM, idx);} + + uint32 EntryToIndexUnchecked(const HashtableEntryBase * entry) const + { + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return ((uint32)(static_cast *>(entry)-static_cast *>(_table))); +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return ((uint32)(static_cast *>(entry)-static_cast *>(_table))); +#endif + + default: + return ((uint32)(static_cast *>(entry)-static_cast *>(_table))); + } + } + uint32 EntryToIndexChecked(const HashtableEntryBase * optEntry) const {return optEntry ? EntryToIndexUnchecked(optEntry) : MUSCLE_HASHTABLE_INVALID_SLOT_INDEX;} + + HashtableEntryBase * IndexToEntryUnchecked(uint32 idx) const + { + HashtableEntryBase * t = const_cast(_table); + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return &(static_cast *>(t))[idx]; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return &(static_cast *>(t))[idx]; +#endif + + default: + return &(static_cast *>(t))[idx]; + } + } + HashtableEntryBase * IndexToEntryChecked(uint32 idx) const {return (idx == MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) ? NULL : IndexToEntryUnchecked(idx);} + + friend class HashtableIterator; + + void InitializeIterator(IteratorType & iter) const + { + RegisterIterator(&iter); + iter._iterCookie = this->IndexToEntryChecked((iter._flags & HTIT_FLAG_BACKWARDS) ? _iterTailIdx : _iterHeadIdx); + iter.UpdateKeyAndValuePointers(); + } + + void InitializeIteratorAt(IteratorType & iter, const KeyType & startAt) const + { + RegisterIterator(&iter); + iter._iterCookie = this->GetEntry(this->ComputeHash(startAt), startAt); + iter.UpdateKeyAndValuePointers(); + } + + // These ugly methods are here to expose the HashtableIterator's privates to the Hashtable class without exposing them to the rest of the world + void * GetIteratorScratchSpace(const IteratorType & iter, int i) const {return iter._scratchSpace[i];} + void SetIteratorScratchSpace(IteratorType & iter, int i, void * val) const {iter._scratchSpace[i] = val;} + void * GetIteratorNextCookie(const IteratorType & iter) const {return iter._iterCookie;} + void SetIteratorNextCookie(IteratorType & iter, void * val) const {iter._iterCookie = val;} + IteratorType * GetIteratorNextIterator(const IteratorType & iter) const {return iter._nextIter;} + + // Give these classes (and only these classes!) access to the HashtableEntryBase inner class + template friend class HashtableMid; + template friend class Hashtable; + template friend class OrderedKeysHashtable; + template friend class OrderedValuesHashtable; + + HashtableEntryBase * GetEntriesArrayPointer() const {return _table;} + HT_UniversalSinkKeyValueRef HashtableEntryBase * PutAuxAux(uint32 hash, HT_SinkKeyParam key, HT_SinkValueParam value); + status_t RemoveAux(uint32 hash, const KeyType & key, ValueType * optSetValue) + { + HashtableEntryBase * e = this->GetEntry(hash, key); + return e ? RemoveEntry(e, optSetValue) : B_ERROR; + } + status_t RemoveAux(const KeyType & key, ValueType * optSetValue) + { + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(key), key); + return e ? RemoveEntry(e, optSetValue) : B_ERROR; + } + status_t RemoveEntry(HashtableEntryBase * e, ValueType * optSetValue); + status_t RemoveEntryByIndex(uint32 idx, ValueType * optSetValue); + + void SwapEntryMaps(uint32 idx1, int32 idx2); + + inline uint32 ComputeHash(const KeyType & key) const + { + uint32 ret = _hashFunctor(key); + if (ret == MUSCLE_HASHTABLE_INVALID_HASH_CODE) ret++; // avoid using the guard value as a hash code (unlikely but possible) + return ret; + } + + inline bool AreKeysEqual(const KeyType & k1, const KeyType & k2) const + { + return _hashFunctor.AreKeysEqual(k1, k2); + } + + void InsertIterationEntry(HashtableEntryBase * e, HashtableEntryBase * optBehindThisOne); + void RemoveIterationEntry(HashtableEntryBase * e); + HashtableEntryBase * GetEntry(uint32 hash, const KeyType & key) const; + HashtableEntryBase * GetEntryAt(uint32 idx) const; + bool IsBucketHead(const HashtableEntryBase * e) const + { + if (e->_hash == MUSCLE_HASHTABLE_INVALID_HASH_CODE) return false; + return (GetEntryMapToUnchecked(IndexToEntryUnchecked(e->_hash%_tableSize)) == e); + } + + // HashtableIterator's private API + void RegisterIterator(IteratorType * iter) const + { + if (iter->_flags & HTIT_FLAG_NOREGISTER) iter->_prevIter = iter->_nextIter = NULL; + else + { +#ifndef MUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + // This logic keeps iterator-registration from causing race conditions when multiple + // threads are iterating over the same Hashtable at once. Only the first thread is + // allowed to register; subsequent threads' iterators are forced into non-registering mode. + // Of course that means they won't handle modifications to the Hashtable properly, but + // then again if modifications are being made during multi-thread read-access periods, + // you're pretty much screwed anyway. This is just so that iteration (which is nominally + // a read-only operation) can be thread-safe even if the user didn't explicitly + // specify HTIT_FLAG_NOREGISTER. + if (_iteratorCount.AtomicIncrement()) + { + _iteratorThreadID = muscle_thread_id::GetCurrentThreadID(); // we're the first iterator on this Hashtable! + iter->_okayToUnsetThreadID = true; // remember that (iter) is the iterator who has to unset _iteratorThreadID + } + else if (_iteratorThreadID != muscle_thread_id::GetCurrentThreadID()) // there's a race condition here but it's harmless + { + // If we got here, then we're in a different thread from the one that has permission + // to register iterators. So for thread-safety, we're going to refrain from registering (iter). + iter->_flags |= HTIT_FLAG_NOREGISTER; // so that UnregisterIterator() will also be a no-op for (iter) + iter->_prevIter = iter->_nextIter = NULL; + (void) _iteratorCount.AtomicDecrement(); // roll back our AtomicIncrement(), since we aren't registered + return; + } +#endif + + // add (iter) to the head of our linked list of iterators + iter->_prevIter = NULL; + iter->_nextIter = _iterList; + if (_iterList) _iterList->_prevIter = iter; + _iterList = iter; + } + } + + void UnregisterIterator(IteratorType * iter) const + { + if (iter->_flags & HTIT_FLAG_NOREGISTER) iter->_prevIter = iter->_nextIter = NULL; + else + { + if (iter->_prevIter) iter->_prevIter->_nextIter = iter->_nextIter; + if (iter->_nextIter) iter->_nextIter->_prevIter = iter->_prevIter; + if (iter == _iterList) _iterList = iter->_nextIter; + iter->_prevIter = iter->_nextIter = NULL; + +#ifndef MUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + if (iter->_okayToUnsetThreadID) {iter->_okayToUnsetThreadID = false; _iteratorThreadID = muscle_thread_id();} + (void) _iteratorCount.AtomicDecrement(); +#endif + } + } + + const KeyType & GetKeyFromCookie(void * c) const {return (((HashtableEntryBase *)c)->_key);} + ValueType & GetValueFromCookie(void * c) const {return (((HashtableEntryBase *)c)->_value);} + + HashtableEntryBase * GetSubsequentEntry(void * entryPtr, uint32 flags) const + { + if (entryPtr == NULL) return NULL; + HashtableEntryBase * b = static_cast(entryPtr); + return (flags & HTIT_FLAG_BACKWARDS) ? this->GetEntryIterPrevChecked(b) : this->GetEntryIterNextChecked(b); + } + + void MoveToBackAux(HashtableEntryBase * moveMe) + { + if (this->GetEntryIterNext(moveMe) != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) + { + RemoveIterationEntry(moveMe); + InsertIterationEntry(moveMe, this->IndexToEntryChecked(_iterTailIdx)); + } + } + + void MoveToFrontAux(HashtableEntryBase * moveMe) + { + if (this->GetEntryIterPrev(moveMe) != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) + { + RemoveIterationEntry(moveMe); + InsertIterationEntry(moveMe, NULL); + } + } + + void MoveToBeforeAux(HashtableEntryBase * moveMe, HashtableEntryBase * toBeforeMe) + { + if (this->GetEntryIterNextChecked(moveMe) != toBeforeMe) + { + RemoveIterationEntry(moveMe); + InsertIterationEntry(moveMe, GetEntryIterPrevChecked(toBeforeMe)); + } + } + + void MoveToBehindAux(HashtableEntryBase * moveMe, HashtableEntryBase * toBehindMe) + { + if (GetEntryIterPrevChecked(moveMe) != toBehindMe) + { + RemoveIterationEntry(moveMe); + InsertIterationEntry(moveMe, toBehindMe); + } + } + + HashFunctorType _hashFunctor; // used to compute hash codes for key objects + + uint32 _numItems; // the number of valid elements in the hashtable + uint32 _tableSize; // the number of entries in _table (or the number to allocate if _table is NULL) + uint32 _tableIndexType; // will be a TABLE_INDEX_TYPE_* value + + HashtableEntryBase * _table; // our array of table entries (actually an array of HashtableEntry objects of some flavor: NOT an array of HashtableEntryBase objects! Be careful!) + uint32 _iterHeadIdx; // index of the start of linked list to iterate through + uint32 _iterTailIdx; // index of the end of linked list to iterate through + uint32 _freeHeadIdx; // index of the head of the list of unused HashtableEntries in our _table array + + bool _autoSortEnabled; // only used in the ordered Hashtable subclasses, but kept here so that its state can be swapped by SwapContents(), etc. + void * _compareCookie; + + mutable IteratorType * _iterList; // list of existing iterators for this table +#ifndef MUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + mutable AtomicCounter _iteratorCount; // this represents the number of HashtableIterators currently registered with this Hashtable + mutable muscle_thread_id _iteratorThreadID; // this is the ID of the thread that is allowed to register iterators (or 0 if none are registered) +#endif + + /// @endcond +}; + +/** This internal superclass is an implementation detail and should not be instantiated directly. Instantiate a Hashtable, OrderedKeysHashtable, or OrderedValuesHashtable instead. */ +template class HashtableMid : public HashtableBase +{ +public: + /** The iterator type that goes with this HashtableMid type */ + typedef HashtableIterator IteratorType; + + /** Equality operator. Returns true iff both hash tables contains the same set of keys and values. + * Note that the ordering of the keys is NOT taken into account! + */ + bool operator== (const HashtableMid & rhs) const {return this->IsEqualTo(rhs, false);} + + /** Templated psuedo-equality operator. Returns true iff both hash tables contains the same set of keys and values. + * Note that the ordering of the keys is NOT taken into account! + */ + template bool operator== (const HashtableMid & rhs) const {return this->IsEqualTo(rhs, false);} + + /** Returns true iff the contents of this table differ from the contents of (rhs). + * Note that the ordering of the keys is NOT taken into account! + */ + bool operator!= (const HashtableMid & rhs) const {return !this->IsEqualTo(rhs, false);} + + /** Templated psuedo-inequality operator. Returns false iff both hash tables contains the same set of keys and values. + * Note that the ordering of the keys is NOT taken into account! + */ + template bool operator!= (const HashtableMid & rhs) const {return !this->IsEqualTo(rhs, false);} + + /** Makes this table into a copy of a table passed in as an argument. + * @param rhs The HashtableMid to make this HashtableMid a copy of. Note that only (rhs)'s items are + * copied in; other settings such as sort mode and key/value cookies are not copied in. + * @param clearFirst If true, this HashtableMid will be cleared before the contents of (rhs) are copied + * into it. Otherwise, the contents of (rhs) will be copied in on top of the current contents. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + template status_t CopyFrom(const HashtableBase & rhs, bool clearFirst = true); + + /** Places the given (key, value) mapping into the table. Any previous entry with a key of (key) will be replaced. + * (average O(1) insertion time, unless auto-sorting is enabled, in which case it becomes O(N) insertion time for + * keys that are not already in the table) + * @param key The key that the new value is to be associated with. + * @param value The value to associate with the new key. + * @param setPreviousValue If there was a previously existing value associated with (key), it will be copied into this object. + * @param optSetReplaced If set non-NULL, this boolean will be set to true if (setPreviousValue) was written into, false otherwise. + * @return B_NO_ERROR If the operation succeeded, B_ERROR if it failed (out of memory?) + */ + HT_UniversalSinkKeyValueRef status_t Put(HT_SinkKeyParam key, HT_SinkValueParam value, ValueType & setPreviousValue, bool * optSetReplaced = NULL) {return (PutAux(this->ComputeHash(key), HT_ForwardKey(key), HT_ForwardValue(value), &setPreviousValue, optSetReplaced) != NULL) ? B_NO_ERROR : B_ERROR;} + + /** Places the given (key, value) mapping into the table. Any previous entry with a key of (key) will be replaced. + * (average O(1) insertion time, unless auto-sorting is enabled, in which case it becomes O(N) insertion time for + * keys that are not already in the table) + * @param key The key that the new value is to be associated with. + * @param value The value to associate with the new key. + * @return B_NO_ERROR If the operation succeeded, B_ERROR if it failed (out of memory?) + */ + HT_UniversalSinkKeyValueRef status_t Put(HT_SinkKeyParam key, HT_SinkValueParam value) {return (PutAux(this->ComputeHash(key), HT_ForwardKey(key), HT_ForwardValue(value), NULL, NULL) != NULL) ? B_NO_ERROR : B_ERROR;} + + /** Places a (key, value) mapping into the table. The value will be a default-constructed item of the value type. + * This call is equivalent to calling Put(key, ValueType()), but slightly more efficient. + * @param key The key to be used in the placed mapping. + * @return B_NO_ERROR If the operation succeeded, B_ERROR if it failed (out of memory?) + */ + HT_UniversalSinkKeyRef status_t PutWithDefault(HT_SinkKeyParam key) {return Put(HT_ForwardKey(key), this->GetDefaultValue());} + + /** Convenience method: For each key/value pair in the passed-in-table, Put()'s that + * key/value pair into this table. Any existing items in this table with the same + * key as any in (pairs) will be overwritten. + * @param pairs A table full of items to Put() into this table. + * @returns B_NO_ERROR on success, or B_ERROR on failue (out of memory?) + */ + template status_t Put(const HashtableMid & pairs) {return this->CopyFrom(pairs, false);} + + /** Convenience method -- returns a pointer to the value specified by (key), + * or if no such value exists, it will Put() a (key,value) pair in the HashtableMid, + * and then return a pointer to the newly-placed value. Returns NULL on + * error only (out of memory?) + * @param key The key to look for a value with + * @param defaultValue The value to auto-place in the HashtableMid if (key) isn't found. + * @returns a Pointer to the retrieved or placed value. + */ + HT_UniversalSinkKeyValueRef ValueType * GetOrPut(HT_SinkKeyParam key, HT_SinkValueParam defaultValue) + { + uint32 hash = this->ComputeHash(key); + typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, key); + if (e == NULL) e = PutAux(hash, HT_ForwardKey(key), HT_ForwardValue(defaultValue), NULL, NULL); + return e ? &e->_value : NULL; + } + + /** Convenience method -- returns a pointer to the value specified by (key), + * or if no such value exists, it will Put() a (key,value) pair in the HashtableMid, + * using a default value (as specified by ValueType's default constructor) + * and then return a pointer to the newly-placed value. Returns NULL on + * error only (out of memory?) + * @param key The key to look for a value with + * @returns a Pointer to the retrieved or placed value. + */ + HT_UniversalSinkKeyRef ValueType * GetOrPut(HT_KeyParam key) + { + uint32 hash = this->ComputeHash(key); + typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, key); + if (e == NULL) e = PutAux(hash, HT_ForwardKey(key), this->GetDefaultValue(), NULL, NULL); + return e ? &e->_value : NULL; + } + + /** Places the given (key, value) mapping into the table. Any previous entry with a key of (key) will be replaced. + * (average O(1) insertion time, unless auto-sorting is enabled, in which case it becomes O(N) insertion time for + * keys that are not already in the table) + * @param key The key that the new value is to be associated with. + * @param value The value to associate with the new key. If not specified, a value object created using + * the default constructor will be placed by default. + * @return A pointer to the value object in the table on success, or NULL on failure (out of memory?) + */ + HT_UniversalSinkKeyValueRef ValueType * PutAndGet(HT_SinkKeyParam key, HT_SinkValueParam value) + { + typename HashtableBase::HashtableEntryBase * e = PutAux(this->ComputeHash(key), HT_ForwardKey(key), HT_ForwardValue(value), NULL, NULL); + return e ? &e->_value : NULL; + } + + /** As above, except that a default value is placed into the table and returned. + * @param key The key that the new value is to be associated with. + * @return A pointer to the value object in the table on success, or NULL on failure (out of memory?) + */ + HT_UniversalSinkKeyRef ValueType * PutAndGet(HT_SinkKeyParam key) {return PutAndGet(HT_ForwardKey(key), this->GetDefaultValue());} + + /** Convenience method: If (value) is the different from (defaultValue), then (key/value) is placed into the table and a pointer + * to the placed value object is returned. + * If (value) is equal to (defaultValue), on the other hand, (key) will be removed from the table, and NULL will be returned. + * @param key The key value to affect. + * @param value The value to possibly place into the table. + * @param defaultValue The value to compare (value) with to decide whether to Put() or Remove() the key. + * @returns A pointer to the placed value if a value was placed, or a NULL pointer if the value was removed (or on error). + */ + HT_UniversalSinkKeyValueRef ValueType * PutOrRemove(HT_SinkKeyParam key, HT_SinkValueParam value, const ValueType & defaultValue) // yes, defaultValue is not declared as HT_SinkValueParam! + { + if (value == defaultValue) + { + (void) this->Remove(key); + return NULL; + } + else return PutAndGet(HT_ForwardKey(key), HT_ForwardValue(value)); + } + + /** As above, except no (defaultValue) is specified. The default-constructed ValueType is assumed. + * @param key The key value to affect. + * @param value The value to possibly place into the table. + * @returns A pointer to the placed value if a value was placed, or a NULL pointer if the value was removed (or on out of memory) + */ + HT_UniversalSinkKeyValueRef ValueType * PutOrRemove(HT_SinkKeyParam key, HT_SinkValueParam value) + { + if (value == this->GetDefaultValue()) + { + (void) this->Remove(key); + return NULL; + } + else return PutAndGet(HT_ForwardKey(key), HT_ForwardValue(value)); + } + + /** If (optValue) is non-NULL, this call places the specified key/value pair into the table. + * If (optValue) is NULL, this call removes the key/value pair from the table. + * @param key The key value to affect. + * @param optValue A pointer to the value to place into the table, or a NULL pointer to remove the key/value pair of the specified key. + * @returns A pointer to the placed value if a value was placed, or a NULL pointer if the value was removed (or on out of memory) + */ + HT_UniversalSinkKeyRef ValueType * PutOrRemove(HT_SinkKeyParam key, const ValueType * optValue) + { + if (optValue) return PutAndGet(HT_ForwardKey(key), *optValue); + else + { + (void) this->Remove(key); + return NULL; + } + } + + /** Convenience method. If the given key already exists in the Hashtable, this method returns NULL. + * Otherwise, this method puts a copy of specified value into the table and returns a pointer to the + * just-placed value. + * (average O(1) insertion time, unless auto-sorting is enabled, in which case it becomes O(N) insertion time) + * @param key The key that the new value is to be associated with. + * @param value The value to associate with the new key. + * @return A pointer to the value object in the table on success, or NULL on failure (key already exists, out of memory) + */ + HT_UniversalSinkKeyValueRef ValueType * PutIfNotAlreadyPresent(HT_SinkKeyParam key, HT_SinkValueParam value) + { + uint32 hash = this->ComputeHash(key); + typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, key); + if (e) return NULL; + else + { + e = PutAux(hash, HT_ForwardKey(key), HT_ForwardValue(value), NULL, NULL); + return e ? &e->_value : NULL; + } + } + + /** Convenience method. If the given key already exists in the Hashtable, this method returns NULL. + * Otherwise, this method puts a default value into the table and returns a pointer to the just-placed value. + * (average O(1) insertion time, unless auto-sorting is enabled, in which case it becomes O(N) insertion time) + * @param key The key that the new value is to be associated with. + * @return A pointer to the value object in the table on success, or NULL on failure (key already exists, out of memory) + */ + HT_UniversalSinkKeyRef ValueType * PutIfNotAlreadyPresent(HT_SinkKeyParam key) + { + uint32 hash = this->ComputeHash(key); + typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, key); + if (e) return NULL; + else + { + e = PutAux(hash, HT_ForwardKey(key), this->GetDefaultValue(), NULL, NULL); + return e ? &e->_value : NULL; + } + } + + /** Convenience method: Moves an item from this table to another table. + * @param moveMe The key in this table of the item that should be moved. + * @param toTable The table that the item should be in when this operation completes. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, or (moveMe) + * was not found in this table. Note that trying to move an item into its + * own table will simply return B_NO_ERROR with no side effects. + */ + template status_t MoveToTable(const KeyType & moveMe, HashtableMid & toTable); + + /** Convenience method: Copies an item from this table to another table. + * @param copyMe The key in this table of the item that should be copied. + * @param toTable The table that the item should be in when this operation completes. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, or (copyMe) + * was not found in this table. Note that trying to copy an item into its + * own table will simply return B_NO_ERROR with no side effects. + */ + template status_t CopyToTable(const KeyType & copyMe, HashtableMid & toTable) const; + + /** This method resizes the Hashtable larger if necessary, so that it has at least (newTableSize) + * entries in it. It is not necessary to call this method, but if you know in advance how many + * items you will be adding to the table, you can make the population of the table more efficient + * by calling this method before adding the items. This method will only grow the table, it will + * never shrink it. + * @param newTableSize Number of slots that you wish to have in the table (note that this is different + * from the number of actual items in the table) + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t EnsureSize(uint32 newTableSize); + + /** Convenience wrapper around EnsureSize(): This method ensures that this Hashtable has enough + * extra space allocated to fit another (numExtras) items without having to do a reallocation. + * If it doesn't, it will do a reallocation so that it does have at least that much extra space. + * @param numExtras How many extra items we want to ensure room for. Defaults to 1. + * @returns B_NO_ERROR if the extra space now exists, or B_ERROR on failure (out of memory?) + */ + status_t EnsureCanPut(uint32 numExtras = 1) {return EnsureSize(this->GetNumItems()+numExtras);} + + /** Sorts this Hashtable's contents according to its built-in sort ordering. + * Specifically, if this object is an OrderedKeysHashtable, the contents will be sorted by key; + * if this object is an OrderedValuesHashtable, the contents will be sorted by value; and if this + * class is a plain Hashtable, this method will be a no-op (since a plain Hashtable doesn't have + * a built-in sort ordering). If you want to sort a plain Hashtable, have a look at the SortByKey() + * and SortByValue() methods instead; those allow you to provide a sort ordering on the fly. + */ + void Sort() {static_cast(this)->SortAux();} + + /** This method can be used to activate or deactivate auto-sorting on this Hashtable. + * + * If active, auto-sorting ensures that whenever Put() is called, the new/updated item is + * automatically moved to the correct place in the iterator traversal list. Note that when + * auto-sort is enabled, Put() becomes an O(N) operation instead of O(1). + * + * Note that auto-sorting only has an effect on OrderedKeysHashtables and OrderedValuesHashtables, + * in which it is enabled by default. For plain Hashtables, auto-sort mode has no effect and is + * disabled by default. + * + * @param enabled True if autosort should be enabled; false if it shouldn't be. + * @param sortNow If true, Sort() will be called when entering a new auto-sort mode, to ensure that + * the table is in a sorted state. You can avoid an immediate sort by specifying this + * parameter as false, but be aware that when auto-sorting is enabled, Put() expects + * the table's contents to already be sorted according to the current auto-sort state, + * and if they aren't, it won't insert its new item at the correct location. Defaults to true. + */ + void SetAutoSortEnabled(bool enabled, bool sortNow = true) + { + if (enabled != this->_autoSortEnabled) + { + this->_autoSortEnabled = enabled; + if ((sortNow)&&(enabled)) static_cast(this)->Sort(); + } + } + +private: + HashtableMid(uint32 tableSize, bool autoSortEnabled, void * cookie) : HashtableBase(tableSize, autoSortEnabled, cookie) {/* empty */} + + // Only these classes are allowed to construct a HashtableMid object + template friend class Hashtable; + template friend class OrderedKeysHashtable; + template friend class OrderedValuesHashtable; + + HT_UniversalSinkKeyValueRef typename HashtableBase::HashtableEntryBase * PutAux(uint32 hash, HT_SinkKeyParam key, HT_SinkValueParam value, ValueType * optSetPreviousValue, bool * optReplacedFlag); +}; + +/** + * This is a handy templated Hashtable class, rather similar to Java's java.util.Hashtable, + * or the STL's hash_map<>, but with some added features not typically found in hash table + * implementations. These extra features include: + * - Any POD type or user type with a default constructor may be used as a Hashtable Key type. + * By default, MUSCLE's CalculateHashCode() function (which is a wrapper around the MurmurHash2 + * algorithm) will be used to scan the bytes of the Key object, in order to calculate a + * hash code for a given Key. However, if the Key class has a method with the signature + * "uint32 HashCode() const", then the HashCode() method will be automatically called instead. + * - Pointers can be used as Key types if desired; if the pointers point to a class with + * a "uint32 HashCode() const" method; that method will be called to generate the entry's + * hash code. Note that it is up to the calling code to ensure the pointers point to valid + * objects (and that the pointed-to objects remain valid for as long as they are pointed + * to by the keys in this Hashtable) + * - When iterating over a Hashtable, the iteration will traverse the Key/Value pairs in the + * same order that they were added to the Hashtable. Also, the ordering of Key/Value pairs + * will be preserved as additional entries are added to (or removed from) the Hashtable. + * - Adding or removing items from a Hashtable will not break HashtableIterator iterations + * that are in progress on that same Hashtable. This makes it possible, for example, to + * iterate over a Hashtable, removing undesired items as you go. (Note that the concurrent + * iterations must be within the same thread, however -- the Hashtable class is NOT thread-safe, + * unless you use Mutexes to explicitly serialize access to it.) + * - It is possible to iterate backwards over the contents of a Hashtable, or iterate starting + * at a specified entry. + * - It is possible for a HashtableIterator iteration to skip backwards as well as forwards + * in its iteration sequence, by calling iter-- instead of iter++. + * - In-place modification of Value objects contained in the Hashtable is allowed. + * - It is possible to manually modify the iteration-traversal ordering of a table via the + * MoveToFront(), MoveToBack(), MoveToBefore(), MoveToBehind(), or MoveToPosition() methods. + * - Keys and Values in the Hashtable are guaranteed never to change their locations in memory, + * except when the Hashtable has to resize its internal array to fit more entries. That means + * that if you know in advance the maximum number of items the Hashtable will ever need to + * contain, you can call myHashTable.EnsureSize(maxNumItems) in advance, and then safely retain + * pointers to the Key or Value items in the Hashtable without worrying about them becoming + * invalidated due to implicit data movement. + * - The Hashtable class never does any dynamic memory allocations, except for when it resizes + * its internal array larger. That means that if you know in advance the maximum number of items + * a Hashtable will ever need to contain, you can call myHashtable.EnsureSize(maxNumItems) at + * program start, and be guaranteed thereafter that your Hashtable will never suffer from an + * out-of-memory error. + * - The Hashtable's contents may be sorted by Key or by Value at any time, by calling SortByKey() + * or SortByValue(). + * - OrderedKeysHashtable and OrderedValuesHashtable subclasses are available; these work similarly + * to the regular Hashtable class, except that they automatically keep the table's contents sorted + * by Key (or by Value) at all times. Note that these classes are necessarily less efficient than + * a regular Hashtable, however; in particular they cause Put() to become a O(N) operation instead + * of O(1). + * - The SwapContents() method will swap the contents of two Hashtables as an O(1) operation -- + * that is, swapping the contents of two million-entry Hashtables is just as fast as swapping the + * contents of two one-entry Hashtables. + * - Various convenience methods such as PutAndGet(), GetOrPut(), PutOrRemove(), and PutIfNotAlreadyPresent() + * allow common usage patterns to be reduced to a single method call, making their intent explicit + * and their implementation uniform. + * - Methods like GetFirstKey(), GetFirstValue(), RemoveFirst() and RemoveLast() allow the + * Hashtable to be used as an efficient, keyed double-ended FIFO queue, if desired. This + * (along with MoveToFront()) makes it very easy to implement an LRU cache using a Hashtable. + * - The CountAverageLookupComparisons() method can be called during development to easily + * quantify the performance of the hash functions being used. + * - Memory overhead is 6 bytes per key-value entry if the table's capacity is less than 256; + * 12 bytes per key-value entry if the table's capacity is less than 65535, and 24 bytes per + * key-value entry for tables larger than that. + */ +template class Hashtable : public HashtableMid > +{ +public: + /** The iterator type that goes with this HashtableMid type */ + typedef HashtableIterator IteratorType; + + /** Default constructor. */ + Hashtable() : HashtableMid >(MUSCLE_HASHTABLE_DEFAULT_CAPACITY, false, NULL) {/* empty */} + + /** Copy Constructor. */ + Hashtable(const Hashtable & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), false, NULL) {(void) this->CopyFrom(rhs);} + + /** Templated pseudo-copy-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template Hashtable(const HashtableMid & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), false, NULL) {(void) this->CopyFrom(rhs);} + + /** Assignment operator */ + Hashtable & operator=(const Hashtable & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** Templated pseudo-assignment-operator: Allows us to be set from similar table with different functor types. */ + template Hashtable & operator=(const HashtableMid & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** This method does an efficient zero-copy swap of this hash table's contents with those of (swapMe). + * That is to say, when this method returns, (swapMe) will be identical to the old state of this + * Hashtable, and this Hashtable will be identical to the old state of (swapMe). + * Any active iterators present for either table will swap owners also, becoming associated with the other table. + * @param swapMe The table whose contents and iterators are to be swapped with this table's. + * @note This method is redeclared here solely to make sure that the muscleSwap() SFINAE magic sees it. + */ + void SwapContents(Hashtable & swapMe) {HashtableMid >::SwapContents(swapMe);} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** Move Constructor. */ + Hashtable(Hashtable && rhs) : HashtableMid >(0, false, NULL) {this->SwapContents(rhs);} + + /** Templated pseudo-move-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template Hashtable(HashtableMid && rhs) : HashtableMid >(0, false, NULL) {this->SwapContents(rhs);} + + /** Assignment-move operator */ + Hashtable & operator=(Hashtable && rhs) {this->SwapContents(rhs); return *this;} + + /** Templated pseudo-assignment-move-operator: Allows us to be set from similar table with different functor types. */ + template Hashtable & operator=(HashtableMid && rhs) {this->SwapContents(rhs); return *this;} +#endif + +private: + // These are the "fake virtual functions" that can be called by HashtableMid as part of the + // Curiously Recurring Template Pattern that Hashtable uses to implement polymorphic behavior at compile time. + friend class HashtableMid >; + void InsertIterationEntryAux(typename HashtableBase::HashtableEntryBase * e) {this->InsertIterationEntry(e, this->IndexToEntryChecked(this->_iterTailIdx));} + void RepositionAux(typename HashtableBase::HashtableEntryBase *) {/* empty -- reposition is a no-op for this class */} + void SortAux() {/* empty -- this class doesn't have a well-defined sort ordering */} +}; + +/** This is a Hashtable that keeps its iteration entries sorted by key at all times (unless you specifically call SetAutoSortEnabled(false)) */ +template , class HashFunctorType=typename DEFAULT_HASH_FUNCTOR(KeyType)> class OrderedKeysHashtable : public HashtableMid > +{ +public: + /** The iterator type that goes with this HashtableMid type */ + typedef HashtableIterator IteratorType; + + /** Default constructor. + * @param optCompareCookie the value that will be passed to our compare functor. Defaults to NULL. + */ + OrderedKeysHashtable(void * optCompareCookie = NULL) : HashtableMid >(MUSCLE_HASHTABLE_DEFAULT_CAPACITY, true, optCompareCookie) {/* empty */} + + /** Copy Constructor. */ + OrderedKeysHashtable(const OrderedKeysHashtable & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), true, NULL) {(void) this->CopyFrom(rhs);} + + /** Templated pseudo-copy-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template OrderedKeysHashtable(const HashtableMid & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), NULL) {(void) this->CopyFrom(rhs);} + + /** Assignment operator */ + OrderedKeysHashtable & operator=(const OrderedKeysHashtable & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** Templated pseudo-assignment-operator: Allows us to be set from similar table with different functor types. */ + template OrderedKeysHashtable & operator=(const HashtableMid & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** This method does an efficient zero-copy swap of this hash table's contents with those of (swapMe). + * That is to say, when this method returns, (swapMe) will be identical to the old state of this + * Hashtable, and this Hashtable will be identical to the old state of (swapMe). + * Any active iterators present for either table will swap owners also, becoming associated with the other table. + * @param swapMe The table whose contents and iterators are to be swapped with this table's. + * @note This method is redeclared here solely to make sure that the muscleSwap() SFINAE magic sees it. + */ + void SwapContents(OrderedKeysHashtable & swapMe) {HashtableMid >::SwapContents(swapMe);} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** Move Constructor. */ + OrderedKeysHashtable(OrderedKeysHashtable && rhs) : HashtableMid >(0, true, NULL) {this->SwapContents(rhs);} + + /** Templated pseudo-move-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template OrderedKeysHashtable(HashtableMid && rhs) : HashtableMid >(0, NULL) {this->SwapContents(rhs);} + + /** Move-Assignment operator */ + OrderedKeysHashtable & operator=(OrderedKeysHashtable && rhs) {this->SwapContents(rhs); return *this;} + + /** Templated pseudo-move-assignment-operator: Allows us to be set from similar table with different functor types. */ + template OrderedKeysHashtable & operator=(HashtableMid && rhs) {(void) this->SwapContents(rhs); return *this;} +#endif + +private: + // These are the "fake virtual functions" that can be called by HashtableMid as part of the + // Curiously Recurring Template Pattern that Hashtable uses to implement polymorphic behavior at compile time. + friend class HashtableMid >; + void InsertIterationEntryAux(typename HashtableBase::HashtableEntryBase * e); + void RepositionAux(typename HashtableBase::HashtableEntryBase *) {/* empty -- reposition is a no-op for this class */} + void SortAux() {this->SortByKey(_compareFunctor, this->GetCompareCookie());} + KeyCompareFunctorType _compareFunctor; +}; + + +/** This is a Hashtable that keeps its iteration entries sorted by value at all times (unless you specifically call SetAutoSortEnabled(false)) */ +template , class HashFunctorType=typename DEFAULT_HASH_FUNCTOR(KeyType)> class OrderedValuesHashtable : public HashtableMid > +{ +public: + /** The iterator type that goes with this HashtableMid type */ + typedef HashtableIterator IteratorType; + + /** Default constructor. + * @param optCompareCookie the value that will be passed to our compare functor. Defaults to NULL. + */ + OrderedValuesHashtable(void * optCompareCookie = NULL) : HashtableMid >(MUSCLE_HASHTABLE_DEFAULT_CAPACITY, true, optCompareCookie) {/* empty */} + + /** Copy Constructor. */ + OrderedValuesHashtable(const OrderedValuesHashtable & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), true, NULL) {(void) this->CopyFrom(rhs);} + + /** Templated pseudo-copy-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template OrderedValuesHashtable(const HashtableMid & rhs) : HashtableMid >(rhs.GetNumAllocatedItemSlots(), true, NULL) {(void) this->CopyFrom(rhs);} + + /** Assignment operator */ + OrderedValuesHashtable & operator=(const OrderedValuesHashtable & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** Templated pseudo-assignment-operator: Allows us to be set from similar table with different functor types. */ + template OrderedValuesHashtable & operator=(const HashtableMid & rhs) {(void) this->CopyFrom(rhs); return *this;} + + /** Moves the specified key/value pair so that it is in the correct position based on the + * current sort-by-value ordering. The only time you would need to call this is if the + * Hashtable is in automatic-sort-by-value mode (see SetAutoSortEnabled()) and you + * have done an in-place modification of this key's value that might have affected the key's + * correct position in the sort ordering. If you are not using sort-by-value mode, + * or if you are only doing Put()'s and Get()'s, and never modifying ValueType objects + * in-place within the table, then calling this method is not necessary and will have no effect. + * @param key The key object of the key/value pair that may need to be repositioned. + * @returns B_NO_ERROR on success, or B_ERROR if (key) was not found in the table. + */ + status_t Reposition(const KeyType & key); + + /** This method does an efficient zero-copy swap of this hash table's contents with those of (swapMe). + * That is to say, when this method returns, (swapMe) will be identical to the old state of this + * Hashtable, and this Hashtable will be identical to the old state of (swapMe). + * Any active iterators present for either table will swap owners also, becoming associated with the other table. + * @param swapMe The table whose contents and iterators are to be swapped with this table's. + * @note This method is redeclared here solely to make sure that the muscleSwap() SFINAE magic sees it. + */ + void SwapContents(OrderedValuesHashtable & swapMe) {HashtableMid >::SwapContents(swapMe);} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** Move Constructor. */ + OrderedValuesHashtable(OrderedValuesHashtable && rhs) : HashtableMid >(0, true, NULL) {this->SwapContents(rhs);} + + /** Templated pseudo-move-constructor: Allows us to be instantiated as a copy of a similar table with different functor types. */ + template OrderedValuesHashtable(HashtableMid && rhs) : HashtableMid >(0, true, NULL) {this->SwapContents(rhs);} + + /** Move Assignment operator */ + OrderedValuesHashtable & operator=(OrderedValuesHashtable && rhs) {this->SwapContents(rhs); return *this;} + + /** Templated pseudo-move-assignment-operator: Allows us to be set from similar table with different functor types. */ + template OrderedValuesHashtable & operator=(HashtableMid && rhs) {this->SwapContents(rhs); return *this;} +#endif + +private: + // These are the "fake virtual functions" that can be called by HashtableMid as part of the + // Curiously Recurring Template Pattern that Hashtable uses to implement polymorphic behavior at compile time. + friend class HashtableMid >; + void InsertIterationEntryAux(typename HashtableBase::HashtableEntryBase * e); + void RepositionAux(typename HashtableBase::HashtableEntryBase * e); + void SortAux() {this->SortByValue(_compareFunctor, this->GetCompareCookie());} + + ValueCompareFunctorType _compareFunctor; +}; + +//=============================================================== +// Implementation of HashtableBase +//=============================================================== + +// Similar to the equality operator +template +bool +HashtableBase :: +IsEqualTo(const HashtableBase & rhs, bool considerOrdering) const +{ + if (this == &rhs) return true; + if (GetNumItems() != rhs.GetNumItems()) return false; + + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + if (considerOrdering) + { + const HashtableEntryBase * hisE = rhs.IndexToEntryChecked(rhs._iterHeadIdx); + while(e) + { + if (!(hisE->_value == e->_value)) return false; + e = this->GetEntryIterNextChecked(e); + hisE = rhs.GetEntryIterNextChecked(hisE); + } + } + else + { + while(e) + { + const HashtableEntryBase * hisE = rhs.GetEntry(e->_hash, e->_key); + if ((hisE == NULL)||(!(hisE->_value == e->_value))) return false; + e = this->GetEntryIterNextChecked(e); + } + } + return true; +} + +template +bool +HashtableBase::ContainsValue(const ValueType & value) const +{ + const HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + while(e) + { + if (e->_value == value) return true; + e = GetEntryIterNextChecked(e); + } + return false; +} + +template +int32 +HashtableBase::IndexOfKey(const KeyType & key) const +{ + const HashtableEntryBase * entry = this->GetEntry(this->ComputeHash(key), key); + int32 count = -1; + if (entry) + { + if (entry == this->IndexToEntryChecked(_iterTailIdx)) count = GetNumItems()-1; + else + { + while(entry) + { + entry = this->GetEntryIterPrevChecked(entry); + count++; + } + } + } + return count; +} + +template +int32 +HashtableBase::IndexOfValue(const ValueType & value, bool searchBackwards) const +{ + if (searchBackwards) + { + int32 idx = GetNumItems(); + const HashtableEntryBase * entry = this->IndexToEntryChecked(_iterTailIdx); + while(entry) + { + --idx; + if (entry->_value == value) return idx; + entry = this->GetEntryIterPrevChecked(entry); + } + } + else + { + int32 idx = 0; + const HashtableEntryBase * entry = this->IndexToEntryChecked(_iterHeadIdx); + while(entry) + { + if (entry->_value == value) return idx; + entry = this->GetEntryIterNextChecked(entry); + idx++; + } + } + return -1; +} + +template +const KeyType * +HashtableBase::GetKeyAt(uint32 index) const +{ + HashtableEntryBase * e = GetEntryAt(index); + return e ? &e->_key : NULL; +} + +template +status_t +HashtableBase::GetKeyAt(uint32 index, KeyType & retKey) const +{ + HashtableEntryBase * e = GetEntryAt(index); + if (e) + { + retKey = e->_key; + return B_NO_ERROR; + } + return B_ERROR; +} + +template +status_t +HashtableBase::GetValue(const KeyType & key, ValueType & setValue) const +{ + const ValueType * ptr = GetValue(key); + if (ptr) + { + setValue = *ptr; + return B_NO_ERROR; + } + else return B_ERROR; +} + +template +const ValueType * +HashtableBase::GetValue(const KeyType & key) const +{ + const HashtableEntryBase * e = this->GetEntry(this->ComputeHash(key), key); + return e ? &e->_value : NULL; +} + +template +ValueType * +HashtableBase::GetValue(const KeyType & key) +{ + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(key), key); + return e ? &e->_value : NULL; +} + +template +const KeyType * +HashtableBase::GetKey(const KeyType & lookupKey) const +{ + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(lookupKey), lookupKey); + return e ? &e->_key : NULL; +} + +template +status_t +HashtableBase::GetKey(const KeyType & lookupKey, KeyType & setKey) const +{ + const KeyType * ptr = GetKey(lookupKey); + if (ptr) + { + setKey = *ptr; + return B_NO_ERROR; + } + else return B_ERROR; +} + +/// @cond HIDDEN_SYMBOLS + +template +typename HashtableBase::HashtableEntryBase * +HashtableBase::GetEntry(uint32 hash, const KeyType & key) const +{ + if (HasItems()) + { + HashtableEntryBase * e = this->GetEntryMapToUnchecked(this->IndexToEntryUnchecked(hash%_tableSize)); + if (IsBucketHead(e)) // if the e isn't the start of a bucket, then we know our entry doesn't exist + { + while(e) + { + if ((e->_hash == hash)&&(AreKeysEqual(e->_key, key))) return e; + e = this->GetEntryBucketNextChecked(e); + } + } + } + return NULL; +} + +template +typename HashtableBase::HashtableEntryBase * +HashtableBase::GetEntryAt(uint32 idx) const +{ + HashtableEntryBase * e = NULL; + if (idx < _numItems) + { + if (idx < _numItems/2) + { + e = this->IndexToEntryChecked(_iterHeadIdx); + while((e)&&(idx--)) e = this->GetEntryIterNextChecked(e); + } + else + { + idx = _numItems-(idx+1); + e = this->IndexToEntryChecked(_iterTailIdx); + while((e)&&(idx--)) e = this->GetEntryIterPrevChecked(e); + } + } + return e; +} + +template +template +bool +HashtableBase::AreKeySetsEqual(const HashtableBase & rhs) const +{ + if (GetNumItems() != rhs.GetNumItems()) return false; + for (HashtableIterator iter(*this); iter.HasData(); iter++) if (rhs.ContainsKey(iter.GetKey()) == false) return false; + return true; +} + +// Linked-list MergeSort adapted from Simon Tatham's C code at http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c +template +template +void +HashtableBase::SortByKey(const KeyCompareFunctorType & keyFunctor, void * cookie) +{ + if (this->_iterHeadIdx == MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) return; + + for (uint32 mergeSize = 1; /* empty */; mergeSize *= 2) + { + typename HashtableBase::HashtableEntryBase * p = this->IndexToEntryChecked(_iterHeadIdx); + this->_iterHeadIdx = this->_iterTailIdx = MUSCLE_HASHTABLE_INVALID_SLOT_INDEX; + + uint32 numMerges = 0; /* count number of merges we do in this pass */ + while(p) + { + numMerges++; /* there exists a merge to be done */ + + /* step `mergeSize' places along from p */ + typename HashtableBase::HashtableEntryBase * q = p; + uint32 psize = 0; + for (uint32 i=0; iGetEntryIterNextChecked(q); + if (!q) break; + } + + /* now we have two lists; merge them */ + for (uint32 qsize=mergeSize; ((psize > 0)||((qsize > 0)&&(q))); /* empty */) + { + typename HashtableBase::HashtableEntryBase * e; + + /* decide whether next element of the merge comes from p or q */ + if (psize == 0) {e = q; q = this->GetEntryIterNextChecked(q); qsize--;} + else if ((qsize == 0)||(q == NULL)) {e = p; p = this->GetEntryIterNextChecked(p); psize--;} + else if (keyFunctor.Compare(p->_key,q->_key,cookie) <= 0) {e = p; p = this->GetEntryIterNextChecked(p); psize--;} + else {e = q; q = this->GetEntryIterNextChecked(q); qsize--;} + + /* append to our new more-sorted list */ + typename HashtableBase::HashtableEntryBase * tail = this->IndexToEntryChecked(_iterTailIdx); + if (tail) this->SetEntryIterNextChecked(tail, e); + else this->_iterHeadIdx = this->EntryToIndexChecked(e); + this->SetEntryIterPrevChecked(e, tail); + this->_iterTailIdx = this->EntryToIndexChecked(e); + } + + p = q; /* now p has stepped `mergeSize' places along, and q has too */ + } + this->SetEntryIterNext(this->IndexToEntryChecked(_iterTailIdx), MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + if (numMerges <= 1) return; + } +} + +// Linked-list MergeSort adapted from Simon Tatham's C code at http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c +template +template +void +HashtableBase::SortByValue(const ValueCompareFunctorType & valFunctor, void * cookie) +{ + if (this->_iterHeadIdx == MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) return; + + for (uint32 mergeSize = 1; /* empty */; mergeSize *= 2) + { + typename HashtableBase::HashtableEntryBase * p = this->IndexToEntryChecked(this->_iterHeadIdx); + this->_iterHeadIdx = this->_iterTailIdx = MUSCLE_HASHTABLE_INVALID_SLOT_INDEX; + + uint32 numMerges = 0; /* count number of merges we do in this pass */ + while(p) + { + numMerges++; /* there exists a merge to be done */ + + /* step `mergeSize' places along from p */ + typename HashtableBase::HashtableEntryBase * q = p; + uint32 psize = 0; + for (uint32 i=0; iGetEntryIterNextChecked(q); + if (!q) break; + } + + /* now we have two lists; merge them */ + for (uint32 qsize=mergeSize; ((psize > 0)||((qsize > 0)&&(q))); /* empty */) + { + typename HashtableBase::HashtableEntryBase * e; + + /* decide whether next element of the merge comes from p or q */ + if (psize == 0) {e = q; q = this->GetEntryIterNextChecked(q); qsize--;} + else if ((qsize == 0)||(q == NULL)) {e = p; p = this->GetEntryIterNextChecked(p); psize--;} + else if (valFunctor.Compare(p->_value,q->_value,cookie) <= 0) {e = p; p = this->GetEntryIterNextChecked(p); psize--;} + else {e = q; q = this->GetEntryIterNextChecked(q); qsize--;} + + /* append to our new more-sorted list */ + typename HashtableBase::HashtableEntryBase * tail = this->IndexToEntryChecked(_iterTailIdx); + if (tail) this->SetEntryIterNextChecked(tail, e); + else this->_iterHeadIdx = this->EntryToIndexChecked(e); + this->SetEntryIterPrevChecked(e, tail); + this->_iterTailIdx = this->EntryToIndexChecked(e); + } + + p = q; /* now p has stepped `mergeSize' places along, and q has too */ + } + this->SetEntryIterNext(this->IndexToEntryChecked(_iterTailIdx), MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + if (numMerges <= 1) return; + } +} + +template +void +HashtableBase::SwapContentsAux(HashtableBase & swapMe, bool swapIterators) +{ + muscleSwap(_numItems, swapMe._numItems); + muscleSwap(_tableSize, swapMe._tableSize); + muscleSwap(_tableIndexType, swapMe._tableIndexType); + muscleSwap(_table, swapMe._table); + muscleSwap(_iterHeadIdx, swapMe._iterHeadIdx); + muscleSwap(_iterTailIdx, swapMe._iterTailIdx); + muscleSwap(_freeHeadIdx, swapMe._freeHeadIdx); + muscleSwap(_autoSortEnabled, swapMe._autoSortEnabled); + muscleSwap(_compareCookie, swapMe._compareCookie); + if (swapIterators) + { + muscleSwap(_iterList, swapMe._iterList); +#ifndef MUSCLE_AVOID_THREAD_SAFE_HASHTABLE_ITERATORS + muscleSwap(_iteratorCount, swapMe._iteratorCount); + muscleSwap(_iteratorThreadID, swapMe._iteratorThreadID); +#endif + + // Lastly, swap the owners of all iterators, so that they will unregister from the correct table when they die + { + IteratorType * next = _iterList; + while(next) + { + next->_owner = &swapMe; + next = next->_nextIter; + } + } + { + IteratorType * next = swapMe._iterList; + while(next) + { + next->_owner = this; + next = next->_nextIter; + } + } + } +} + +template +status_t +HashtableBase::EnsureTableAllocated() +{ + if (this->_table == NULL) + { + switch(this->_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + this->_table = HashtableEntry ::CreateEntriesArray(this->_tableSize); + break; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + this->_table = HashtableEntry::CreateEntriesArray(this->_tableSize); + break; +#endif + + default: + this->_table = HashtableEntry::CreateEntriesArray(this->_tableSize); + break; + } + this->_freeHeadIdx = 0; + } + return this->_table ? B_NO_ERROR : B_ERROR; +} + +// This is the part of the insertion that is CompareFunctor-neutral. +template +HT_UniversalSinkKeyValueRef +typename HashtableBase::HashtableEntryBase * +HashtableBase::PutAuxAux(uint32 hash, HT_SinkKeyParam key, HT_SinkValueParam value) +{ + HashtableEntryBase * tableSlot = this->GetEntryMapToUnchecked(this->IndexToEntryUnchecked(hash%_tableSize)); + if (IsBucketHead(tableSlot)) + { + // This slot's chain is already present -- so just create a new entry in the chain's linked list to hold our item + HashtableEntryBase * e = this->IndexToEntryUnchecked(_freeHeadIdx); + _freeHeadIdx = this->PopFromFreeList(e, _freeHeadIdx); + e->_hash = hash; + e->_key = HT_ForwardKey(key); + e->_value = HT_ForwardValue(value); + + // insert e into the list immediately after (tableSlot) + this->SetEntryBucketPrevUnchecked(e, tableSlot); + uint32 eBucketNext = this->GetEntryBucketNext(tableSlot); + this->SetEntryBucketNext(e, eBucketNext); + + uint32 eIdx = this->EntryToIndexUnchecked(e); + if (eBucketNext != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) this->SetEntryBucketPrev(this->IndexToEntryUnchecked(eBucketNext), eIdx); + this->SetEntryBucketNext(tableSlot, eIdx); + return e; + } + else + { + if (tableSlot->_hash != MUSCLE_HASHTABLE_INVALID_HASH_CODE) + { + // Hey, some other bucket is using my starter-slot! + // To get around this, we'll swap my starter-slot for an empty one and use that instead. + SwapEntryMaps(this->GetEntryMappedFrom(tableSlot), this->GetEntryMappedFrom(this->IndexToEntryChecked(_freeHeadIdx))); + tableSlot = this->IndexToEntryChecked(_freeHeadIdx); + } + _freeHeadIdx = this->PopFromFreeList(tableSlot, _freeHeadIdx); + + // First entry in tableSlot; just copy data over + tableSlot->_hash = hash; + tableSlot->_key = HT_ForwardKey(key); + tableSlot->_value = HT_ForwardValue(value); + + this->SetEntryBucketPrev(tableSlot, MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + this->SetEntryBucketNext(tableSlot, MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + return tableSlot; + } +} + +template +void +HashtableBase::SwapEntryMaps(uint32 idx1, int32 idx2) +{ + HashtableEntryBase * e1 = this->IndexToEntryUnchecked(idx1); + HashtableEntryBase * e2 = this->IndexToEntryUnchecked(idx2); + + // was: muscleSwap(e1->_mapTo, e2->_mapTo); + { + uint32 e1MapTo = this->GetEntryMapTo(e1); + uint32 e2MapTo = this->GetEntryMapTo(e2); + this->SetEntryMapTo(e1, e2MapTo); + this->SetEntryMapTo(e2, e1MapTo); + } + + this->SetEntryMappedFrom(this->GetEntryMapToUnchecked(e1), idx1); // was: _table[e1->_mapTo]._mappedFrom = idx1; + this->SetEntryMappedFrom(this->GetEntryMapToUnchecked(e2), idx2); // was: _table[e2->_mapTo]._mappedFrom = idx2; +} + +template +status_t +HashtableBase::RemoveEntryByIndex(uint32 idx, ValueType * optSetValue) +{ + HashtableEntryBase * entry = this->IndexToEntryChecked(idx); + return entry ? RemoveEntry(entry, optSetValue) : B_ERROR; +} + +template +status_t +HashtableBase::RemoveEntry(HashtableEntryBase * e, ValueType * optSetValue) +{ + RemoveIterationEntry(e); + if (optSetValue) *optSetValue = e->_value; + + HashtableEntryBase * prev = this->GetEntryBucketPrevChecked(e); + HashtableEntryBase * next = this->GetEntryBucketNextChecked(e); + if (prev) + { + this->SetEntryBucketNextChecked(prev, next); + if (next) this->SetEntryBucketPrevUnchecked(next, prev); + } + else if (next) + { + this->SetEntryBucketPrev(next, MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + SwapEntryMaps(this->GetEntryMappedFrom(e), this->GetEntryMappedFrom(next)); + } + + _numItems--; + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + static_cast *>(e)->PushToFreeList(GetDefaultKey(), GetDefaultValue(), _freeHeadIdx, this); + break; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + static_cast*>(e)->PushToFreeList(GetDefaultKey(), GetDefaultValue(), _freeHeadIdx, this); + break; +#endif + + default: + static_cast*>(e)->PushToFreeList(GetDefaultKey(), GetDefaultValue(), _freeHeadIdx, this); + break; + } + return B_NO_ERROR; +} + +template +uint32 +HashtableBase::PopFromFreeList(HashtableEntryBase * e, uint32 freeHeadIdx) +{ + switch(_tableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return static_cast *>(e)->PopFromFreeList(freeHeadIdx, this); +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + return static_cast*>(e)->PopFromFreeList(freeHeadIdx, this); +#endif + + default: + return static_cast*>(e)->PopFromFreeList(freeHeadIdx, this); + } +} + +template +void +HashtableBase::Clear(bool releaseCachedBuffers) +{ + // First go through our list of active iterators, and let them all know they are now invalid + while(_iterList) + { + IteratorType * next = _iterList->_nextIter; + _iterList->_owner = NULL; + _iterList->_iterCookie = _iterList->_prevIter = _iterList->_nextIter = NULL; + _iterList = next; + } + + // It's important to set each in-use HashtableEntryBase to its default state so + // that any held memory (e.g. RefCountables) will be freed, etc. + // Calling RemoveEntry() on each item is necessary to ensure correct behavior + // even when the templatized classes' assignment operations cause re-entrancies, etc. + while(_iterHeadIdx != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX) (void) RemoveEntryByIndex(_iterHeadIdx, NULL); + + if (releaseCachedBuffers) + { + HashtableEntryBase * oldTable = _table; + uint32 oldTableIndexType = _tableIndexType; + + _table = NULL; + _freeHeadIdx = MUSCLE_HASHTABLE_INVALID_SLOT_INDEX; + _tableSize = MUSCLE_HASHTABLE_DEFAULT_CAPACITY; + _tableIndexType = this->ComputeTableIndexTypeForTableSize(_tableSize); + + // done after state is updated, in case of re-entrancies in the dtors + switch(oldTableIndexType) + { + case TABLE_INDEX_TYPE_UINT8: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + delete [] (static_cast *>(oldTable)); + break; +#endif + + case TABLE_INDEX_TYPE_UINT16: +#ifndef MUSCLE_AVOID_MINIMIZED_HASHTABLES + delete [] (static_cast*>(oldTable)); + break; +#endif + + default: + delete [] (static_cast*>(oldTable)); + break; + } + } +} + +template +float +HashtableBase::CountAverageLookupComparisons(bool printStatistics) const +{ + Hashtable histogram; + uint32 chainCount = 0; + if (_table) + { + for (uint32 i=0; i<_tableSize; i++) + { + const HashtableEntryBase * e = this->IndexToEntryUnchecked(i); + if (IsBucketHead(e)) + { + chainCount++; + + uint32 chainSize = 0; + while(e) + { + chainSize++; + e = this->GetEntryBucketNextChecked(e); + } + uint32 * count = histogram.GetOrPut(chainSize); + if (count) (*count)++; + } + } + } + histogram.SortByKey(); + + uint32 totalNumItems = GetNumItems(); + if (printStatistics) printf("Hashtable statistics: " UINT32_FORMAT_SPEC " items in table, " UINT32_FORMAT_SPEC " slots allocated, " UINT32_FORMAT_SPEC " chains.\n", totalNumItems, _tableSize, chainCount); + if (totalNumItems > 0) + { + uint64 totalCounts = 0, totalExtras = 0; + for (HashtableIterator iter(histogram); iter.HasData(); iter++) + { + uint32 curChainSize = iter.GetKey(); + uint32 numChainsOfThisSize = iter.GetValue(); + uint32 numItemsInCurChainSize = numChainsOfThisSize*curChainSize; + if (printStatistics) printf(" " UINT32_FORMAT_SPEC " chains of size " UINT32_FORMAT_SPEC " (aka %.3f%% of items)\n", numChainsOfThisSize, curChainSize, (100.0f*numItemsInCurChainSize)/totalNumItems); + totalCounts += ((uint64)numItemsInCurChainSize)*curChainSize; + totalExtras += ((uint64)numItemsInCurChainSize)*(curChainSize-1); + } + float ret = (((float)totalExtras)/(2.0f*totalNumItems))+1.0f; + if (printStatistics) printf("Average chain length is %.3f. Average lookup requires %.3f key-comparisons.\n", ((float)totalCounts)/totalNumItems, ret); + return ret; + } + else return 0.0f; +} + +template +uint32 +HashtableBase::Remove(const HashtableBase & pairs) +{ + uint32 removeCount = 0; + if (&pairs == this) + { + removeCount = GetNumItems(); + Clear(); + } + else + { + HashtableEntryBase * e = pairs.IndexToEntryChecked(pairs._iterHeadIdx); + while(e) + { + if (RemoveAux(e->_hash, e->_key, NULL) == B_NO_ERROR) removeCount++; + e = pairs.GetEntryIterNextChecked(e); + } + } + return removeCount; +} + +template +uint32 +HashtableBase::Intersect(const HashtableBase & pairs) +{ + uint32 removeCount = 0; + if (&pairs != this) + { + HashtableEntryBase * e = this->IndexToEntryChecked(_iterHeadIdx); + while(e) + { + HashtableEntryBase * next = this->GetEntryIterNextChecked(e); // save this first, since we might be erasing (e) + if ((pairs.GetEntry(e->_hash, e->_key) == NULL)&&(this->RemoveAux(e->_hash, e->_key, NULL) == B_NO_ERROR)) removeCount++; + e = next; + } + } + return removeCount; +} + +template +status_t +HashtableBase::MoveToFront(const KeyType & moveMe) +{ + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(moveMe), moveMe); + if (e == NULL) return B_ERROR; + this->MoveToFrontAux(e); + return B_NO_ERROR; +} + +template +status_t +HashtableBase::MoveToBack(const KeyType & moveMe) +{ + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(moveMe), moveMe); + if (e == NULL) return B_ERROR; + this->MoveToBackAux(e); + return B_NO_ERROR; +} + +template +status_t +HashtableBase::MoveToBefore(const KeyType & moveMe, const KeyType & toBeforeMe) +{ + if (HasItems()) + { + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(moveMe), moveMe); + HashtableEntryBase * f = this->GetEntry(this->ComputeHash(toBeforeMe), toBeforeMe); + if ((e == NULL)||(f == NULL)||(e == f)) return B_ERROR; + this->MoveToBeforeAux(e, f); + return B_NO_ERROR; + } + else return B_ERROR; +} + +template +status_t +HashtableBase::MoveToBehind(const KeyType & moveMe, const KeyType & toBehindMe) +{ + if (HasItems()) + { + HashtableEntryBase * d = this->GetEntry(this->ComputeHash(toBehindMe), toBehindMe); + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(moveMe), moveMe); + if ((d == NULL)||(e == NULL)||(d == e)) return B_ERROR; + this->MoveToBehindAux(e, d); + return B_NO_ERROR; + } + else return B_ERROR; +} + +template +status_t +HashtableBase::MoveToPosition(const KeyType & moveMe, uint32 idx) +{ + HashtableEntryBase * e = this->GetEntry(this->ComputeHash(moveMe), moveMe); + if (e) + { + if (idx == 0) this->MoveToFrontAux(e); + else if (idx >= GetNumItems()) this->MoveToBackAux(e); + else + { + RemoveIterationEntry(e); + + HashtableEntryBase * insertAfter; + if (idx < GetNumItems()/2) + { + insertAfter = this->IndexToEntryChecked(_iterHeadIdx); + while(--idx > 0) insertAfter = this->GetEntryIterNextUnchecked(insertAfter); + } + else + { + insertAfter = this->IndexToEntryChecked(_iterTailIdx); + while(++idx < GetNumItems()) this->GetEntryIterPrevUnchecked(insertAfter); + } + InsertIterationEntry(e, insertAfter); + } + return B_NO_ERROR; + } + return B_ERROR; +} + +// Adds (e) to the our iteration linked list, behind (optBehindThis), or at the head if (optBehindThis) is NULL. +template +void +HashtableBase::InsertIterationEntry(HashtableEntryBase * e, HashtableEntryBase * optBehindThis) +{ + this->SetEntryIterPrevChecked(e, optBehindThis); + this->SetEntryIterNext(e, optBehindThis ? this->GetEntryIterNext(optBehindThis) : this->_iterHeadIdx); + + HashtableEntryBase * prev = this->GetEntryIterPrevChecked(e); + if (prev) this->SetEntryIterNextUnchecked(prev, e); + else this->_iterHeadIdx = this->EntryToIndexUnchecked(e); + + HashtableEntryBase * next = this->GetEntryIterNextChecked(e); + if (next) this->SetEntryIterPrevUnchecked(next, e); + else this->_iterTailIdx = this->EntryToIndexUnchecked(e); +} + +// Remove (e) from our iteration linked list +template +void +HashtableBase::RemoveIterationEntry(HashtableEntryBase * e) +{ + // Update any iterators that were pointing at (e), so that they now point to the entry after e. + // That way, on the next (iter++) they will move to the entry after the now-removed e, as God intended. + IteratorType * next = this->_iterList; + while(next) + { + if (next->_iterCookie == e) + { + if (next->_scratchKeyAndValue.IsObjectConstructed() == false) next->SetScratchValues(e->_key, e->_value); + next->_iterCookie = GetSubsequentEntry(next->_iterCookie, next->_flags); + next->UpdateKeyAndValuePointers(); + } + next = next->_nextIter; + } + + HashtableEntryBase * prevNode = this->GetEntryIterPrevChecked(e); + HashtableEntryBase * nextNode = this->GetEntryIterNextChecked(e); + if (this->IndexToEntryChecked(_iterHeadIdx) == e) this->_iterHeadIdx = this->EntryToIndexChecked(nextNode); + if (this->IndexToEntryChecked(_iterTailIdx) == e) this->_iterTailIdx = this->EntryToIndexChecked(prevNode); + if (prevNode) this->SetEntryIterNextChecked(prevNode, nextNode); + if (nextNode) this->SetEntryIterPrevChecked(nextNode, prevNode); + this->SetEntryIterPrev(e, MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); + this->SetEntryIterNext(e, MUSCLE_HASHTABLE_INVALID_SLOT_INDEX); +} +/// @endcond + +//=============================================================== +// Implementation of HashtableMid +//=============================================================== + +// CopyFrom() method is similar to the assignment operator, but gives a return value. +template +template +status_t +HashtableMid :: +CopyFrom(const HashtableBase & rhs, bool clearFirst) +{ + if (((const void *)(this)) == ((const void *)(&rhs))) return B_NO_ERROR; + + if (clearFirst) this->Clear(); + if (rhs.HasItems()) + { + if ((EnsureSize(this->GetNumItems()+rhs.GetNumItems()) != B_NO_ERROR)||(this->EnsureTableAllocated() != B_NO_ERROR)) return B_ERROR; + this->CopyFromAux(rhs); + static_cast(this)->SortAux(); // We do the sort (if any) at the end, since that is more efficient than traversing the list after every insert + } + return B_NO_ERROR; +} + +template +status_t +HashtableMid::EnsureSize(uint32 requestedSize) +{ + if (requestedSize <= this->_tableSize) return B_NO_ERROR; // no need to do anything if we're already big enough! + + // 1. Initialize the scratch space for our active iterators. + { + IteratorType * nextIter = this->_iterList; + while(nextIter) + { + this->SetIteratorScratchSpace(*nextIter, 0, NULL); + this->SetIteratorScratchSpace(*nextIter, 1, NULL); // these will hold our switch-to-on-success values + nextIter = this->GetIteratorNextIterator(*nextIter); + } + } + + // 2. Create a new, bigger table, to hold a copy of our data. + SubclassType biggerTable; + biggerTable._tableSize = muscleMax(this->_numItems, requestedSize); + biggerTable._tableIndexType = this->ComputeTableIndexTypeForTableSize(biggerTable._tableSize); + biggerTable.SetAutoSortEnabled(false); // make sure he doesn't do any sorting during the initial population phase + + // 3. Place all of our data into (biggerTable) + { + typename HashtableBase::HashtableEntryBase * next = this->IndexToEntryChecked(this->_iterHeadIdx); + while(next) + { + typename HashtableBase::HashtableEntryBase * hisClone = biggerTable.PutAux(next->_hash, HT_PlunderKey(next->_key), HT_PlunderValue(next->_value), NULL, NULL); + if (hisClone) + { + // Mark any iterators that will need to be redirected to point to the new nodes. + IteratorType * nextIter = this->_iterList; + while(nextIter) + { + if (this->GetIteratorNextCookie(*nextIter) == next) this->SetIteratorScratchSpace(*nextIter, 0, hisClone); + nextIter = this->GetIteratorNextIterator(*nextIter); + } + } + else return B_ERROR; // oops, out of mem, too bad. + + next = this->GetEntryIterNextChecked(next); + } + } + + // 4. Only now do we set biggerTable's auto-sort params; that way he isn't trying to sort the data we gave him in step (3) + // (which is unecessary since if auto-sort is activated, we already have the data sorted in the correct order) + biggerTable.SetCompareCookie(this->_compareCookie); + biggerTable.SetAutoSortEnabled(this->_autoSortEnabled, false); // no need to sort now, he is already sorted + + // 5. Swap contents with the bigger table, but don't swap iterator lists (we want to keep ours!) + this->SwapContentsAux(biggerTable, false); + + // 6. Lastly, fix up our iterators to point to their new entries. + { + IteratorType * nextIter = this->_iterList; + while(nextIter) + { + this->SetIteratorNextCookie(*nextIter, this->GetIteratorScratchSpace(*nextIter, 0)); + nextIter = this->GetIteratorNextIterator(*nextIter); + } + } + +#ifdef MUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS + if (this->GetNumItems() > 16) + { + float av = this->CountAverageLookupComparisons(); +# if MUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS >= 100 + if (av >= ((MUSCLE_WARN_ABOUT_LOUSY_HASH_FUNCTIONS)/100.0f)) +# else + if (av >= 2.0f) +# endif + { + LogTime(MUSCLE_LOG_WARNING, "Hashtable had average lookup comparison count of %f. Printing statistics and stack trace to stdout.\n", av); + (void) this->CountAverageLookupComparisons(true); + PrintStackTrace(); + } + } +#endif + return B_NO_ERROR; +} + +template +template +status_t +HashtableMid::CopyToTable(const KeyType & copyMe, HashtableMid & toTable) const +{ + uint32 hash = this->ComputeHash(copyMe); + const typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, copyMe); + if (e) + { + if (this == &toTable) return B_NO_ERROR; // it's already here! + if (toTable.PutAux(hash, copyMe, e->_value, NULL, NULL) != NULL) return B_NO_ERROR; + } + return B_ERROR; +} + +template +template +status_t +HashtableMid::MoveToTable(const KeyType & moveMe, HashtableMid & toTable) +{ + uint32 hash = this->ComputeHash(moveMe); + const typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, moveMe); + if (e) + { + if (this == &toTable) return B_NO_ERROR; // it's already here! + if (toTable.PutAux(hash, moveMe, HT_PlunderValue(e->_value), NULL, NULL) != NULL) return this->RemoveAux(e->_hash, moveMe, NULL); + } + return B_ERROR; +} + +template +HT_UniversalSinkKeyValueRef +typename HashtableBase::HashtableEntryBase * +HashtableMid::PutAux(uint32 hash, HT_SinkKeyParam key, HT_SinkValueParam value, ValueType * optSetPreviousValue, bool * optReplacedFlag) +{ + if (optReplacedFlag) *optReplacedFlag = false; + if (this->EnsureTableAllocated() != B_NO_ERROR) return NULL; + + // If we already have an entry for this key in the table, we can just replace its contents + typename HashtableBase::HashtableEntryBase * e = this->GetEntry(hash, key); + if (e) + { + if (optSetPreviousValue) *optSetPreviousValue = e->_value; + if (optReplacedFlag) *optReplacedFlag = true; + e->_value = HT_ForwardValue(value); + static_cast(this)->RepositionAux(e); + return e; + } + + // Rehash the table if the threshold is exceeded + if (this->_numItems == this->_tableSize) return (EnsureSize(this->_tableSize*2) == B_NO_ERROR) ? PutAux(hash, HT_ForwardKey(key), HT_ForwardValue(value), optSetPreviousValue, optReplacedFlag) : NULL; + + e = this->PutAuxAux(hash, HT_ForwardKey(key), HT_ForwardValue(value)); + static_cast(this)->InsertIterationEntryAux(e); + + this->_numItems++; + return e; +} + +//=============================================================== +// Implementation of OrderedKeysHashtable +//=============================================================== + +template +void +OrderedKeysHashtable::InsertIterationEntryAux(typename HashtableBase::HashtableEntryBase * e) +{ + typename HashtableBase::HashtableEntryBase * insertAfter = this->IndexToEntryChecked(this->_iterTailIdx); // default to appending to the end of the list + if ((this->GetAutoSortEnabled())&&(this->_iterHeadIdx != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX)) + { + // We're in sorted mode, so we'll try to place this guy in the correct position. + if (_compareFunctor.Compare(e->_key, this->IndexToEntryUnchecked(this->_iterHeadIdx)->_key, this->_compareCookie) < 0) insertAfter = NULL; // easy; append to the head of the list + else if (_compareFunctor.Compare(e->_key, this->IndexToEntryUnchecked(this->_iterTailIdx)->_key, this->_compareCookie) < 0) // only iterate through if we're before the tail, otherwise the tail is fine + { + typename HashtableBase::HashtableEntryBase * prev = this->IndexToEntryUnchecked(this->_iterHeadIdx); + typename HashtableBase::HashtableEntryBase * next = this->GetEntryIterNextChecked(prev); // more difficult; find where to insert into the middle + while(next) + { + if (_compareFunctor.Compare(e->_key, next->_key, this->_compareCookie) < 0) + { + insertAfter = prev; + break; + } + else + { + prev = next; + next = this->GetEntryIterNextChecked(next); + } + } + } + } + this->InsertIterationEntry(e, insertAfter); +} + +//=============================================================== +// Implementation of OrderedValuesHashtable +//=============================================================== + +template +status_t +OrderedValuesHashtable::Reposition(const KeyType & key) +{ + typename HashtableBase::HashtableEntryBase * e = this->GetAutoSortEnabled() ? this->GetEntry(this->ComputeHash(key), key) : NULL; + if (e) + { + RepositionAux(e); + return B_NO_ERROR; + } + else return B_ERROR; +} + +template +void +OrderedValuesHashtable::RepositionAux(typename HashtableBase::HashtableEntryBase * e) +{ + if (this->GetAutoSortEnabled() == false) return; + + // If our new value has changed our position in the sort-order, then adjust the traversal list + typename HashtableBase::HashtableEntryBase * b; + if (((b = this->GetEntryIterPrevChecked(e)) != NULL)&&(_compareFunctor.Compare(e->_value, b->_value, this->_compareCookie) < 0)) + { + if (_compareFunctor.Compare(e->_value, this->IndexToEntryUnchecked(this->_iterHeadIdx)->_value, this->_compareCookie) < 0) this->MoveToFrontAux(e); + else + { + typename HashtableBase::HashtableEntryBase * prev; + while(((prev = this->GetEntryIterPrevChecked(b)) != NULL)&&(_compareFunctor.Compare(e->_value, prev->_value, this->_compareCookie) < 0)) b = prev; + this->MoveToBeforeAux(e, b); + } + } + else if (((b = this->GetEntryIterNextChecked(e)) != NULL)&&(_compareFunctor.Compare(e->_value, b->_value, this->_compareCookie) > 0)) + { + if (_compareFunctor.Compare(e->_value, this->IndexToEntryUnchecked(this->_iterTailIdx)->_value, this->_compareCookie) > 0) this->MoveToBackAux(e); + else + { + typename HashtableBase::HashtableEntryBase * next; + while(((next = this->GetEntryIterNextChecked(b)) != NULL)&&(_compareFunctor.Compare(e->_value, next->_value, this->_compareCookie) > 0)) b = next; + this->MoveToBehindAux(e, b); + } + } +} + +template +void +OrderedValuesHashtable::InsertIterationEntryAux(typename HashtableBase::HashtableEntryBase * e) +{ + typename HashtableBase::HashtableEntryBase * insertAfter = this->IndexToEntryChecked(this->_iterTailIdx); // default to appending to the end of the list + if ((this->GetAutoSortEnabled())&&(this->_iterHeadIdx != MUSCLE_HASHTABLE_INVALID_SLOT_INDEX)) + { + // We're in sorted mode, so we'll try to place this guy in the correct position. + if (_compareFunctor.Compare(e->_value, this->IndexToEntryUnchecked(this->_iterHeadIdx)->_value, this->_compareCookie) < 0) insertAfter = NULL; // easy; append to the head of the list + else if (_compareFunctor.Compare(e->_value, this->IndexToEntryUnchecked(this->_iterTailIdx)->_value, this->_compareCookie) < 0) // only iterate through if we're before the tail, otherwise the tail is fine + { + typename HashtableBase::HashtableEntryBase * prev = this->IndexToEntryUnchecked(this->_iterHeadIdx); + typename HashtableBase::HashtableEntryBase * next = this->GetEntryIterNextChecked(prev); // more difficult; find where to insert into the middle + while(next) + { + if (_compareFunctor.Compare(e->_value, next->_value, this->_compareCookie) < 0) + { + insertAfter = prev; + break; + } + else + { + prev = next; + next = this->GetEntryIterNextChecked(next); + } + } + } + } + this->InsertIterationEntry(e, insertAfter); +} + +//=============================================================== +// Implementation of HashtableIterator +//=============================================================== + +template +HashtableIterator::HashtableIterator() : _iterCookie(NULL), _currentKey(NULL), _currentVal(NULL), _flags(0), _owner(NULL), _okayToUnsetThreadID(false) +{ + // empty +} + +template +HashtableIterator::HashtableIterator(const HashtableIterator & rhs) : _flags(0), _owner(NULL), _okayToUnsetThreadID(false) +{ + *this = rhs; +} + +template +HashtableIterator::HashtableIterator(const HashtableBase & table, uint32 flags) : _flags(flags), _owner(&table), _okayToUnsetThreadID(false) +{ + table.InitializeIterator(*this); +} + +template +HT_UniversalSinkKeyRef +HashtableIterator::HashtableIterator(const HashtableBase & table, HT_SinkKeyParam startAt, uint32 flags) : _flags(flags), _owner(&table), _okayToUnsetThreadID(false) +{ + table.InitializeIteratorAt(*this, HT_ForwardKey(startAt)); +} + +template +HashtableIterator::~HashtableIterator() +{ + if (_owner) _owner->UnregisterIterator(this); +} + +template +HashtableIterator & +HashtableIterator:: operator=(const HashtableIterator & rhs) +{ + if (this != &rhs) + { + if (_owner) _owner->UnregisterIterator(this); + _flags = rhs._flags; // must be done while unregistered, in case NOREGISTER flag changes state + _owner = rhs._owner; + _scratchKeyAndValue = rhs._scratchKeyAndValue; + if (_owner) _owner->RegisterIterator(this); + + _iterCookie = rhs._iterCookie; + UpdateKeyAndValuePointers(); + } + return *this; +} + +}; // end namespace muscle + +#endif diff --git a/util/MemoryAllocator.cpp b/util/MemoryAllocator.cpp new file mode 100644 index 00000000..485db1f6 --- /dev/null +++ b/util/MemoryAllocator.cpp @@ -0,0 +1,60 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "util/MemoryAllocator.h" + +namespace muscle { + +status_t ProxyMemoryAllocator :: AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes) +{ + return _slaveRef() ? _slaveRef()->AboutToAllocate(currentlyAllocatedBytes, allocRequestBytes) : B_NO_ERROR; +} + +void ProxyMemoryAllocator :: AboutToFree(size_t currentlyAllocatedBytes, size_t allocRequestBytes) +{ + if (_slaveRef()) _slaveRef()->AboutToFree(currentlyAllocatedBytes, allocRequestBytes); +} + +void ProxyMemoryAllocator :: AllocationFailed(size_t currentlyAllocatedBytes, size_t allocRequestBytes) +{ + if (_slaveRef()) _slaveRef()->AllocationFailed(currentlyAllocatedBytes, allocRequestBytes); +} + +void ProxyMemoryAllocator :: SetAllocationHasFailed(bool hasFailed) +{ + MemoryAllocator::SetAllocationHasFailed(hasFailed); + if (_slaveRef()) _slaveRef()->SetAllocationHasFailed(hasFailed); +} + +size_t ProxyMemoryAllocator :: GetMaxNumBytes() const +{ + return (_slaveRef()) ? _slaveRef()->GetMaxNumBytes() : MUSCLE_NO_LIMIT; +} + +size_t ProxyMemoryAllocator :: GetNumAvailableBytes(size_t currentlyAllocated) const +{ + return (_slaveRef()) ? _slaveRef()->GetNumAvailableBytes(currentlyAllocated) : ((size_t)-1); +} + +UsageLimitProxyMemoryAllocator :: UsageLimitProxyMemoryAllocator(const MemoryAllocatorRef & slaveRef, size_t maxBytes) : ProxyMemoryAllocator(slaveRef), _maxBytes(maxBytes) +{ + // empty +} + +UsageLimitProxyMemoryAllocator :: ~UsageLimitProxyMemoryAllocator() +{ + // empty +} + +status_t UsageLimitProxyMemoryAllocator :: AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes) +{ + return ((allocRequestBytes <= _maxBytes)&&(currentlyAllocatedBytes + allocRequestBytes <= _maxBytes)) ? ProxyMemoryAllocator::AboutToAllocate(currentlyAllocatedBytes, allocRequestBytes) : B_ERROR; +} + +void AutoCleanupProxyMemoryAllocator :: AllocationFailed(size_t currentlyAllocatedBytes, size_t allocRequestBytes) +{ + ProxyMemoryAllocator::AllocationFailed(currentlyAllocatedBytes, allocRequestBytes); + uint32 nc = _callbacks.GetNumItems(); + for (uint32 i=0; iCallback(NULL); +} + +}; // end namespace muscle diff --git a/util/MemoryAllocator.h b/util/MemoryAllocator.h new file mode 100644 index 00000000..430cd1fd --- /dev/null +++ b/util/MemoryAllocator.h @@ -0,0 +1,171 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleMemoryAllocator_h +#define MuscleMemoryAllocator_h + +#include "support/MuscleSupport.h" +#include "util/CountedObject.h" +#include "util/GenericCallback.h" +#include "util/Queue.h" +#include "util/RefCount.h" + +namespace muscle { + +class MemoryAllocator; + +/** Interface class representing an object that can allocate and free blocks of memory. */ +class MemoryAllocator : public RefCountable, private CountedObject +{ +public: + /** Default constructor; no-op */ + MemoryAllocator() : _hasAllocationFailed(false) {/* empty */} + + /** Virtual destructor, to keep C++ honest */ + virtual ~MemoryAllocator() {/* empty */} + + /** This method is called whenever we are about to allocate some memory. + * @param currentlyAllocatedBytes How many bytes the system has allocated currently + * @param allocRequestBytes How many bytes the system would like to allocate. + * @note Implementations of this method shall assume that calls to this method will + * be serialized, so they don't need to do any serialization themselves. + * @return Should return B_NO_ERROR if the allocation may proceed, or B_ERROR if the allocation should fail. + */ + virtual status_t AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes) = 0; + + /** This method is called whenever we are about to free some memory. + * @param currentlyAllocatedBytes How many bytes the system has allocated currently + * @param freeBytes How many bytes the system is about to free. + * @note Implementations of this method shall assume that calls to this method will + * be serialized, so they don't need to do any serialization themselves. + */ + virtual void AboutToFree(size_t currentlyAllocatedBytes, size_t freeBytes) = 0; + + /** Called if an allocation fails (either because AboutToAllocate() returned other than B_NO_ERROR, or + * because malloc() returned NULL). This method does not need to call SetMallocHasFailed(), + * because that will be called separately. + * @param currentlyAllocatedBytes How many bytes the system has allocated currently + * @param allocRequestBytes How many bytes the system wanted to allocate, but couldn't. + * @note Implementations of this method shall assume that calls to this method will + * be serialized, so they don't need to do any serialization themselves. + * @note This method should NOT undo any side effects that were incurred by + * a previous successful AboutToAllocate() method, as that will be done + * separately by a call to AboutToFree(). + */ + virtual void AllocationFailed(size_t currentlyAllocatedBytes, size_t allocRequestBytes) = 0; + + /** Sets the state of the "allocation has failed" flag. Typically this is set true just + * before a call to AllocationFailed(), and set false again later on, after the flag has + * been noticed and dealt with. + */ + virtual void SetAllocationHasFailed(bool hasFailed) {_hasAllocationFailed = hasFailed;} + + /** Should be overridden to return the maximum amount of memory that may be allocated at once. + * If there is no set limit, this method should return MUSCLE_NO_LIMIT. + */ + virtual size_t GetMaxNumBytes() const = 0; + + /** Should be overridden to return the number of bytes still available for allocation, + * given that (currentlyAllocated) bytes have already been allocated. + * If there is no set limit, this method should return MUSCLE_NO_LIMIT. + */ + virtual size_t GetNumAvailableBytes(size_t currentlyAllocated) const = 0; + + /** Returns the current state of the "allocation has failed" flag. */ + bool HasAllocationFailed() const {return _hasAllocationFailed;} + +private: + bool _hasAllocationFailed; +}; +DECLARE_REFTYPES(MemoryAllocator); + +/** Convenience class, used for easy subclassing: holds a slave MemoryAllocator and passes all method + * calls on through to the slave. + */ +class ProxyMemoryAllocator : public MemoryAllocator +{ +public: + /** Constructor. + * @param slaveRef Reference to another MemoryAllocator that we will pass all our method calls on through to. + * If (slaveRef) is NULL, default behaviour (always allow, no-op failures) will be used. + */ + ProxyMemoryAllocator(const MemoryAllocatorRef & slaveRef) : _slaveRef(slaveRef) {/* empty */} + + /** Destructor */ + virtual ~ProxyMemoryAllocator() {/* empty */} + + virtual status_t AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + virtual void AboutToFree(size_t currentlyAllocatedBytes, size_t freeBytes); + virtual void AllocationFailed(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + virtual void SetAllocationHasFailed(bool hasFailed); + virtual size_t GetMaxNumBytes() const; + virtual size_t GetNumAvailableBytes(size_t currentlyAllocated) const; + +private: + MemoryAllocatorRef _slaveRef; +}; + +/** This MemoryAllocator decorates its slave MemoryAllocator to + * enforce a user-defined per-process limit on how much memory may be allocated at any given time. + */ +class UsageLimitProxyMemoryAllocator : public ProxyMemoryAllocator +{ +public: + /** Constructor. + * @param slaveRef Reference to a sub-MemoryAllocator whose methods we will call through to. + * @param maxBytes The maximum number of bytes that we will allow (slave) to allocate. Defaults to no limit. + * This value may be reset later using SetMaxNumBytes(). + */ + UsageLimitProxyMemoryAllocator(const MemoryAllocatorRef & slaveRef, size_t maxBytes = MUSCLE_NO_LIMIT); + + /** Destructor. */ + virtual ~UsageLimitProxyMemoryAllocator(); + + /** Overridden to return false if memory usage would go over maximum due to this allocation. */ + virtual status_t AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + + /** Set a new maximum number of allocatable bytes */ + void SetMaxNumBytes(size_t mb) {_maxBytes = mb;} + + /** Implemented to return our hard-coded size limit, same as GetMaxNumBytes() */ + virtual size_t GetMaxNumBytes() const {return muscleMin(_maxBytes, ProxyMemoryAllocator::GetMaxNumBytes());} + + /** Implemented to return the difference between the maximum allocation and the current number allocated. */ + virtual size_t GetNumAvailableBytes(size_t ca) const {return muscleMin((_maxBytes>ca)?_maxBytes-ca:0, ProxyMemoryAllocator::GetNumAvailableBytes(ca));} + +private: + size_t _maxBytes; +}; + +/** This MemoryAllocator decorates its slave MemoryAllocator to call a list of + * GenericCallback objects when the slave's memory allocation fails. These + * GenericCallback calls should try to free up some memory if possible; + * then this MemoryAllocator will call the slave again and see if the memory + * allocation can now succeed. + */ +class AutoCleanupProxyMemoryAllocator : public ProxyMemoryAllocator +{ +public: + /** Constructor. + * @param slaveRef Reference to a sub-MemoryAllocator whose methods we will call through to. + */ + AutoCleanupProxyMemoryAllocator(const MemoryAllocatorRef & slaveRef) : ProxyMemoryAllocator(slaveRef) {/* empty */} + + /** Destructor. Calls ClearCallbacks(). */ + virtual ~AutoCleanupProxyMemoryAllocator() {/* empty */} + + /** Overridden to call our callbacks in event of a failure. */ + virtual void AllocationFailed(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + + /** Read-write access to our list of out-of-memory callbacks. */ + Queue & GetCallbacksQueue() {return _callbacks;} + + /** Write-only access to our list of out-of-memory callbacks. */ + const Queue & GetCallbacksQueue() const {return _callbacks;} + +private: + Queue _callbacks; +}; + +}; // end namespace muscle + +#endif diff --git a/util/MiscUtilityFunctions.cpp b/util/MiscUtilityFunctions.cpp new file mode 100644 index 00000000..2a98d592 --- /dev/null +++ b/util/MiscUtilityFunctions.cpp @@ -0,0 +1,1137 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#ifdef __APPLE__ +# include +# include +#else +# include +#endif + +#ifdef __linux__ +# include +#endif + +#ifndef WIN32 +# include // for umask() +#endif + +#include "reflector/StorageReflectConstants.h" // for PR_COMMAND_BATCH, PR_NAME_KEYS +#include "util/ByteBuffer.h" +#include "util/MiscUtilityFunctions.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/StringTokenizer.h" + +namespace muscle { + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + extern bool _enableDeadlockFinderPrints; +#endif + +extern bool _mainReflectServerCatchSignals; // from SetupSystem.cpp + +static status_t ParseArgAux(const String & a, Message * optAddToMsg, Queue * optAddToQueue, bool cs) +{ + // Remove any initial dashes + String argName = a.Trim(); + const char * s = argName(); + if (s > argName()) argName = argName.Substring(s-argName); + + if (optAddToQueue) return optAddToQueue->AddTail(argName); + else + { + int equalsAt = argName.IndexOf('='); + String argValue; + if (equalsAt >= 0) + { + argValue = argName.Substring(equalsAt+1).Trim(); // this must be first! + argName = argName.Substring(0, equalsAt).Trim(); + if (cs == false) argName = argName.ToLowerCase(); + } + if (argName.HasChars()) + { + // Don't allow the parsing to fail just because the user specified a section name the same as a param name! + uint32 tc; + static const String _escapedQuote = "\\\""; + static const String _quote = "\""; + argName.Replace(_escapedQuote, _quote); + argValue.Replace(_escapedQuote, _quote); + if ((optAddToMsg->GetInfo(argName, &tc) == B_NO_ERROR)&&(tc != B_STRING_TYPE)) (void) optAddToMsg->RemoveName(argName); + return optAddToMsg->AddString(argName, argValue); + } + else return B_NO_ERROR; + } +} +status_t ParseArg(const String & a, Message & addTo, bool cs) {return ParseArgAux(a, &addTo, NULL, cs);} +status_t ParseArg(const String & a, Queue & addTo, bool cs) {return ParseArgAux(a, NULL, &addTo, cs);} + +String UnparseArgs(const Message & argsMsg) +{ + String ret, next, tmp; + for (MessageFieldNameIterator it(argsMsg, B_STRING_TYPE); it.HasData(); it++) + { + const String & fn = it.GetFieldName(); + const String * ps; + for (int32 i=0; argsMsg.FindString(fn, i, &ps) == B_NO_ERROR; i++) + { + if (ps->HasChars()) + { + next = fn; + next += '='; + + tmp = *ps; + tmp.Replace("\"", "\\\""); + if ((tmp.IndexOf(' ') >= 0)||(tmp.IndexOf('\t') >= 0)||(tmp.IndexOf('\r') >= 0)||(tmp.IndexOf('\n') >= 0)) + { + next += '\"'; + next += tmp; + next += '\"'; + } + else next += tmp; + } + else next = fn; + + if (next.HasChars()) + { + if (ret.HasChars()) ret += ' '; + ret += next; + } + next.Clear(); + } + } + return ret; +} + +String UnparseArgs(const Queue & args, uint32 startIdx, uint32 endIdx) +{ + String ret; + endIdx = muscleMin(endIdx, args.GetNumItems()); + for (uint32 i=startIdx; i= 0) subRet = subRet.Append("\"").Prepend("\""); + if (ret.HasChars()) ret += ' '; + ret += subRet; + } + return ret; +} + +static status_t ParseArgsAux(const String & line, Message * optAddToMsg, Queue * optAddToQueue, bool cs) +{ + TCHECKPOINT; + + const String trimmed = line.Trim(); + uint32 len = trimmed.Length(); + + // First, we'll pre-process the string into a StringTokenizer-friendly + // form, by replacing all quoted spaces with gunk and removing the quotes + String tokenizeThis; + if (tokenizeThis.Prealloc(len) != B_NO_ERROR) return B_ERROR; + const char GUNK_CHAR = (char) 0x01; + bool lastCharWasBackslash = false; + bool inQuotes = false; + for (uint32 i=0; i's + if (cs == false) checkForSection = checkForSection.ToLowerCase(); + if (((checkForSection == "begin")||(checkForSection.StartsWith("begin ")))&&(optAddToMsg)) // the check for (optAddToMsg) isn't really necessary, but it makes clang++ happy + { + checkForSection = checkForSection.Substring(6).Trim(); + int32 hashIdx = checkForSection.IndexOf('#'); + if (hashIdx >= 0) checkForSection = checkForSection.Substring(0, hashIdx).Trim(); + + // Don't allow the parsing to fail just because the user specified a section name the same as a param name! + uint32 tc; + if ((optAddToMsg->GetInfo(checkForSection, &tc) == B_NO_ERROR)&&(tc != B_MESSAGE_TYPE)) (void) optAddToMsg->RemoveName(checkForSection); + + MessageRef subMsg = GetMessageFromPool(); + if ((subMsg() == NULL)||(optAddToMsg->AddMessage(checkForSection, subMsg) != B_NO_ERROR)||(ParseFileAux(optTok, fpIn, subMsg(), optAddToQueue, scratchBuf, bufSize, cs) != B_NO_ERROR)) return B_ERROR; + } + else if ((checkForSection == "end")||(checkForSection.StartsWith("end "))) return B_NO_ERROR; + else if (ParseArgsAux(lineOfText, optAddToMsg, optAddToQueue, cs) != B_NO_ERROR) + { + ret = B_ERROR; + break; + } + } + return ret; +} + +static status_t ParseFileAux(const String * optInStr, FILE * fpIn, Message * optAddToMsg, Queue * optAddToQueue, bool cs) +{ + TCHECKPOINT; + + if (optInStr) + { + StringTokenizer tok(optInStr->Cstr(), "\r\n"); + return (tok.GetRemainderOfString() != NULL) ? ParseFileAux(&tok, NULL, optAddToMsg, optAddToQueue, NULL, 0, cs) : B_ERROR; + } + else + { + const int bufSize = 2048; + char * buf = newnothrow_array(char, bufSize); + if (buf) + { + status_t ret = ParseFileAux(NULL, fpIn, optAddToMsg, optAddToQueue, buf, bufSize, cs); + delete [] buf; + return ret; + } + else + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + } +} +status_t ParseFile(FILE * fpIn, Message & addTo, bool cs) {return ParseFileAux(NULL, fpIn, &addTo, NULL, cs);} +status_t ParseFile(FILE * fpIn, Queue & addTo, bool cs) {return ParseFileAux(NULL, fpIn, NULL, &addTo, cs);} +status_t ParseFile(const String & s, Message & addTo, bool cs) {return ParseFileAux(&s, NULL, &addTo, NULL, cs);} +status_t ParseFile(const String & s, Queue & addTo, bool cs) {return ParseFileAux(&s, NULL, NULL, &addTo, cs);} + +static void AddUnparseFileLine(FILE * optFile, String * optString, const String & indentStr, const String & s) +{ +#ifdef WIN32 + static const char * eol = "\r\n"; +#else + static const char * eol = "\n"; +#endif + + if (optString) + { + *optString += indentStr; + *optString += s; + *optString += eol; + } + else fprintf(optFile, "%s%s%s", indentStr(), s(), eol); +} + +static status_t UnparseFileAux(const Message & readFrom, FILE * optFile, String * optString, uint32 indentLevel) +{ + if ((optFile == NULL)&&(optString == NULL)) return B_ERROR; + + String indentStr = String().Pad(indentLevel); + Message scratchMsg; + for (MessageFieldNameIterator fnIter(readFrom); fnIter.HasData(); fnIter++) + { + const String & fn = fnIter.GetFieldName(); + uint32 tc; + if (readFrom.GetInfo(fn, &tc) == B_NO_ERROR) + { + switch(tc) + { + case B_MESSAGE_TYPE: + { + MessageRef nextVal; + for (uint32 i=0; readFrom.FindMessage(fn, i, nextVal) == B_NO_ERROR; i++) + { + AddUnparseFileLine(optFile, optString, indentStr, String("begin %1").Arg(fn)); + if (UnparseFileAux(*nextVal(), optFile, optString, indentLevel+3) != B_NO_ERROR) return B_ERROR; + AddUnparseFileLine(optFile, optString, indentStr, "end"); + } + } + break; + + case B_STRING_TYPE: + { + const String * nextVal; + for (uint32 i=0; readFrom.FindString(fn, i, &nextVal) == B_NO_ERROR; i++) + { + scratchMsg.Clear(); if (scratchMsg.AddString(fn, *nextVal) != B_NO_ERROR) return B_ERROR; + AddUnparseFileLine(optFile, optString, indentStr, UnparseArgs(scratchMsg)); + } + } + break; + + default: + // do nothing + break; + } + } + else return B_ERROR; // should never happen + } + return B_NO_ERROR; +} + +status_t UnparseFile(const Message & readFrom, FILE * file) {return UnparseFileAux(readFrom, file, NULL, 0);} +String UnparseFile(const Message & readFrom) {String s; return (UnparseFileAux(readFrom, NULL, &s, 0) == B_NO_ERROR) ? s : "";} + +static status_t ParseConnectArgAux(const String & s, uint32 startIdx, uint16 & retPort, bool portRequired) +{ + int32 colIdx = s.IndexOf(':', startIdx); + const char * pStr = (colIdx>=0)?(s()+colIdx+1):NULL; + if ((pStr)&&(muscleInRange(*pStr, '0', '9'))) + { + uint16 p = atoi(pStr); + if (p > 0) retPort = p; + return B_NO_ERROR; + } + else return portRequired ? B_ERROR : B_NO_ERROR; +} + +status_t ParseConnectArg(const Message & args, const String & fn, String & retHost, uint16 & retPort, bool portRequired, uint32 argIdx) +{ + const String * s; + return (args.FindString(fn, argIdx, &s) == B_NO_ERROR) ? ParseConnectArg(*s, retHost, retPort, portRequired) : B_ERROR; +} + +status_t ParseConnectArg(const String & s, String & retHost, uint16 & retPort, bool portRequired) +{ +#ifndef MUSCLE_AVOID_IPV6 + int32 rBracket = s.StartsWith('[') ? s.IndexOf(']') : -1; + if (rBracket >= 0) + { + // If there are brackets, they are assumed to surround the address part, e.g. "[::1]:9999" + retHost = s.Substring(1,rBracket); + return ParseConnectArgAux(s, rBracket+1, retPort, portRequired); + } + else if (s.GetNumInstancesOf(':') != 1) // I assume IPv6-style address strings never have exactly one colon in them + { + retHost = s; + return portRequired ? B_ERROR : B_NO_ERROR; + } +#endif + + retHost = s.Substring(0, ":"); + return ParseConnectArgAux(s, retHost.Length(), retPort, portRequired); +} + +status_t ParsePortArg(const Message & args, const String & fn, uint16 & retPort, uint32 argIdx) +{ + TCHECKPOINT; + + const char * v; + if (args.FindString(fn, argIdx, &v) == B_NO_ERROR) + { + uint16 r = (uint16) atoi(v); + if (r > 0) + { + retPort = r; + return B_NO_ERROR; + } + } + return B_ERROR; +} + +#if defined(__linux__) || defined(__APPLE__) +static void CrashSignalHandler(int sig) +{ + // Uninstall this handler, to avoid the possibility of an infinite regress + signal(SIGSEGV, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGFPE, SIG_DFL); + + printf("MUSCLE CrashSignalHandler called with signal %i... I'm going to print a stack trace, then kill the process.\n", sig); + PrintStackTrace(); + printf("Crashed MUSCLE process aborting now.... bye!\n"); + fflush(stdout); + abort(); +} +#endif + +#if defined(MUSCLE_USE_MSVC_STACKWALKER) && !defined(MUSCLE_INLINE_LOGGING) +extern void _Win32PrintStackTraceForContext(FILE * outFile, CONTEXT * context, uint32 maxDepth); + +LONG Win32FaultHandler(struct _EXCEPTION_POINTERS * ExInfo) +{ + SetUnhandledExceptionFilter(NULL); // uninstall the handler to avoid the possibility of an infinite regress + + const char * faultDesc = ""; + switch(ExInfo->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION : faultDesc = "ACCESS VIOLATION" ; break; + case EXCEPTION_DATATYPE_MISALIGNMENT : faultDesc = "DATATYPE MISALIGNMENT" ; break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO : faultDesc = "FLT DIVIDE BY ZERO" ; break; + default : faultDesc = "(unknown)" ; break; + } + + int faultCode = ExInfo->ExceptionRecord->ExceptionCode; + PVOID CodeAddress = ExInfo->ExceptionRecord->ExceptionAddress; + printf("****************************************************\n"); + printf("*** A Program Fault occurred:\n"); + printf("*** Error code %08X: %s\n", faultCode, faultDesc); + printf("****************************************************\n"); + printf("*** Address: %08X\n", (uintptr)CodeAddress); + printf("*** Flags: %08X\n", ExInfo->ExceptionRecord->ExceptionFlags); + _Win32PrintStackTraceForContext(stdout, ExInfo->ContextRecord, MUSCLE_NO_LIMIT); + printf("Crashed MUSCLE process aborting now.... bye!\n"); + fflush(stdout); + + return EXCEPTION_CONTINUE_SEARCH; // now crash in the usual way +} +#endif + +#ifdef __linux__ +static status_t SetRealTimePriority(const char * priStr, bool useFifo) +{ + struct sched_param schedparam; memset(&schedparam, 0, sizeof(schedparam)); + int pri = (strlen(priStr) > 0) ? atoi(priStr) : 11; + schedparam.sched_priority = pri; + + const char * desc = useFifo ? "SCHED_FIFO" : "SCHED_RR"; + if (sched_setscheduler(0, useFifo?SCHED_FIFO:SCHED_RR, &schedparam) == 0) + { + LogTime(MUSCLE_LOG_INFO, "Set process to real-time (%s) priority %i\n", desc, pri); + return B_NO_ERROR; + } + else + { + LogTime(MUSCLE_LOG_ERROR, "Could not invoke real time (%s) scheduling priority %i (access denied?)\n", desc, pri); + return B_ERROR; + } +} +#endif + +void HandleStandardDaemonArgs(const Message & args) +{ + TCHECKPOINT; + +#ifndef WIN32 + if (args.HasName("disablestderr")) + { + LogTime(MUSCLE_LOG_INFO, "Suppressing all further output to stderr!\n"); + close(STDERR_FILENO); + } + if (args.HasName("disablestdout")) + { + LogTime(MUSCLE_LOG_INFO, "Suppressing all further output to stdout!\n"); + close(STDOUT_FILENO); + } +#endif + + // Do this first, so that the stuff below will affect the right process. + const char * n; + if (args.FindString("daemon", &n) == B_NO_ERROR) + { + LogTime(MUSCLE_LOG_INFO, "Spawning off a daemon-child...\n"); + if (BecomeDaemonProcess(NULL, n[0] ? n : "/dev/null") != B_NO_ERROR) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "Could not spawn daemon-child process!\n"); + ExitWithoutCleanup(10); + } + } + +#ifdef WIN32 + if (args.HasName("console")) Win32AllocateStdioConsole(); +#endif + +#ifdef MUSCLE_ENABLE_DEADLOCK_FINDER + { + const char * df = args.GetCstr("deadlockfinder"); + if (df) _enableDeadlockFinderPrints = ParseBool(df, true); + } +#endif + + const char * value; + if (args.FindString("displaylevel", &value) == B_NO_ERROR) + { + int ll = ParseLogLevelKeyword(value); + if (ll >= 0) SetConsoleLogLevel(ll); + else LogTime(MUSCLE_LOG_INFO, "Error, unknown display log level type [%s]\n", value); + } + + if ((args.FindString("oldlogfilespattern", &value) == B_NO_ERROR)&&(*value != '\0')) SetOldLogFilesPattern(value); + + if ((args.FindString("maxlogfiles", &value) == B_NO_ERROR)||(args.FindString("maxnumlogfiles", &value) == B_NO_ERROR)) + { + uint32 maxNumFiles = atol(value); + if (maxNumFiles > 0) SetMaxNumLogFiles(maxNumFiles); + else LogTime(MUSCLE_LOG_ERROR, "Please specify a maxnumlogfiles value that is greater than zero.\n"); + } + + + if (args.FindString("logfile", &value) == B_NO_ERROR) + { + SetFileLogName(value); + if (GetFileLogLevel() == MUSCLE_LOG_NONE) SetFileLogLevel(MUSCLE_LOG_INFO); // no sense specifying a name and then not logging anything! + } + + if (args.FindString("filelevel", &value) == B_NO_ERROR) + { + int ll = ParseLogLevelKeyword(value); + if (ll >= 0) SetFileLogLevel(ll); + else LogTime(MUSCLE_LOG_INFO, "Error, unknown file log level type [%s]\n", value); + } + + if (args.FindString("maxlogfilesize", &value) == B_NO_ERROR) + { + uint32 maxSizeKB = atol(value); + if (maxSizeKB > 0) SetFileLogMaximumSize(maxSizeKB*1024); + else LogTime(MUSCLE_LOG_ERROR, "Please specify a maxlogfilesize in kilobytes, that is greater than zero.\n"); + } + + if ((args.HasName("compresslogfile"))||(args.HasName("compresslogfiles"))) SetFileLogCompressionEnabled(true); + + if (args.FindString("localhost", &value) == B_NO_ERROR) + { + ip_address ip = Inet_AtoN(value); + if (ip != invalidIP) + { + char ipbuf[64]; Inet_NtoA(ip, ipbuf); + LogTime(MUSCLE_LOG_INFO, "IP address [%s] will be used as the localhost address.\n", ipbuf); + SetLocalHostIPOverride(ip); + } + else LogTime(MUSCLE_LOG_ERROR, "Error parsing localhost IP address [%s]!\n", value); + } + + if (args.FindString("dnscache", &value) == B_NO_ERROR) + { + uint64 micros = ParseHumanReadableTimeIntervalString(value); + if (micros > 0) + { + uint32 maxCacheSize = 1024; + if (args.FindString("dnscachesize", &value) == B_NO_ERROR) maxCacheSize = atol(value); + LogTime(MUSCLE_LOG_INFO, "Setting DNS cache parameters to " UINT32_FORMAT_SPEC " entries, expiration period is %s\n", maxCacheSize, GetHumanReadableTimeIntervalString(micros)()); + SetHostNameCacheSettings(maxCacheSize, micros); + } + else LogTime(MUSCLE_LOG_ERROR, "Unable to parse time interval string [%s] for dnscache argument!\n", value); + } + + if ((args.HasName("debugcrashes"))||(args.HasName("debugcrash"))) + { +#if defined(__linux__) || defined(__APPLE__) + LogTime(MUSCLE_LOG_INFO, "Enabling stack-trace printing when a crash occurs.\n"); + signal(SIGSEGV, CrashSignalHandler); + signal(SIGBUS, CrashSignalHandler); + signal(SIGILL, CrashSignalHandler); + signal(SIGABRT, CrashSignalHandler); + signal(SIGFPE, CrashSignalHandler); +#elif MUSCLE_USE_MSVC_STACKWALKER +# ifndef MUSCLE_INLINE_LOGGING + LogTime(MUSCLE_LOG_INFO, "Enabling stack-trace printing when a crash occurs.\n"); + SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER) Win32FaultHandler); +# endif +#else + LogTime(MUSCLE_LOG_ERROR, "Can't enable stack-trace printing when a crash occurs, that feature isn't supported on this platform!\n"); +#endif + } + +#if defined(__linux__) || defined(__APPLE__) + { + const char * niceStr = NULL; (void) args.FindString("nice", &niceStr); + const char * meanStr = NULL; (void) args.FindString("mean", &meanStr); + + int32 niceLevel = niceStr ? ((strlen(niceStr) > 0) ? atoi(niceStr) : 5) : 0; + int32 meanLevel = meanStr ? ((strlen(meanStr) > 0) ? atoi(meanStr) : 5) : 0; + int32 effectiveLevel = niceLevel-meanLevel; + + if (effectiveLevel) + { + errno = 0; // the only reliable way to check for an error here :^P + int ret = nice(effectiveLevel); // I'm only looking at the return value to shut gcc 4.4.3 up + if (errno != 0) LogTime(MUSCLE_LOG_WARNING, "Could not change process execution priority to " INT32_FORMAT_SPEC " (ret=%i).\n", effectiveLevel, ret); + else LogTime(MUSCLE_LOG_INFO, "Process is now %s (niceLevel=%i)\n", (effectiveLevel<0)?"mean":"nice", effectiveLevel); + } + } +#endif + +#ifdef __linux__ + const char * priStr; + if (args.FindString("realtime", &priStr) == B_NO_ERROR) SetRealTimePriority(priStr, false); + else if (args.FindString("realtime_rr", &priStr) == B_NO_ERROR) SetRealTimePriority(priStr, false); + else if (args.FindString("realtime_fifo", &priStr) == B_NO_ERROR) SetRealTimePriority(priStr, true); +#endif + +#ifdef MUSCLE_CATCH_SIGNALS_BY_DEFAULT +# ifdef MUSCLE_AVOID_SIGNAL_HANDLING +# error "MUSCLE_CATCH_SIGNALS_BY_DEFAULT and MUSCLE_AVOID_SIGNAL_HANDLING are mutually exclusive compiler flags... you can not specify both!" +# endif + if (args.HasName("dontcatchsignals")) + { + _mainReflectServerCatchSignals = false; + LogTime(MUSCLE_LOG_DEBUG, "Controlled shutdowns (via Control-C) disabled in the main thread.\n"); + } +#else + if (args.HasName("catchsignals")) + { +# ifdef MUSCLE_AVOID_SIGNAL_HANDLING + LogTime(MUSCLE_LOG_ERROR, "Can not enable controlled shutdowns, MUSCLE_AVOID_SIGNAL_HANDLING was specified during compilation!\n"); +# else + _mainReflectServerCatchSignals = true; + LogTime(MUSCLE_LOG_DEBUG, "Controlled shutdowns (via Control-C) enabled in the main thread.\n"); +# endif + } +#endif +} + +static bool _isDaemonProcess = false; +bool IsDaemonProcess() {return _isDaemonProcess;} + +/* Source code stolen from UNIX Network Programming, Volume 1 + * Comments from the Unix FAQ + */ +#ifdef WIN32 +status_t SpawnDaemonProcess(bool &, const char *, const char *, bool) +{ + return B_ERROR; // Win32 can't do this trick, he's too lame :^( +} +#else +status_t SpawnDaemonProcess(bool & returningAsParent, const char * optNewDir, const char * optOutputTo, bool createIfNecessary) +{ + TCHECKPOINT; + + // Here are the steps to become a daemon: + // 1. fork() so the parent can exit, this returns control to the command line or shell invoking + // your program. This step is required so that the new process is guaranteed not to be a process + // group leader. The next step, setsid(), fails if you're a process group leader. + pid_t pid = fork(); + if (pid < 0) return B_ERROR; + if (pid > 0) + { + returningAsParent = true; + return B_NO_ERROR; + } + else returningAsParent = false; + + // 2. setsid() to become a process group and session group leader. Since a controlling terminal is + // associated with a session, and this new session has not yet acquired a controlling terminal + // our process now has no controlling terminal, which is a Good Thing for daemons. + setsid(); + + // 3. fork() again so the parent, (the session group leader), can exit. This means that we, as a + // non-session group leader, can never regain a controlling terminal. + signal(SIGHUP, SIG_IGN); + pid = fork(); + if (pid < 0) return B_ERROR; + if (pid > 0) ExitWithoutCleanup(0); + + // 4. chdir("/") can ensure that our process doesn't keep any directory in use. Failure to do this + // could make it so that an administrator couldn't unmount a filesystem, because it was our + // current directory. [Equivalently, we could change to any directory containing files important + // to the daemon's operation.] + if ((optNewDir)&&(chdir(optNewDir) != 0)) return B_ERROR; + + // 5. umask(0) so that we have complete control over the permissions of anything we write. + // We don't know what umask we may have inherited. [This step is optional] + (void) umask(0); + + // 6. close() fds 0, 1, and 2. This releases the standard in, out, and error we inherited from our parent + // process. We have no way of knowing where these fds might have been redirected to. Note that many + // daemons use sysconf() to determine the limit _SC_OPEN_MAX. _SC_OPEN_MAX tells you the maximun open + // files/process. Then in a loop, the daemon can close all possible file descriptors. You have to + // decide if you need to do this or not. If you think that there might be file-descriptors open you should + // close them, since there's a limit on number of concurrent file descriptors. + // 7. Establish new open descriptors for stdin, stdout and stderr. Even if you don't plan to use them, + // it is still a good idea to have them open. The precise handling of these is a matter of taste; + // if you have a logfile, for example, you might wish to open it as stdout or stderr, and open `/dev/null' + // as stdin; alternatively, you could open `/dev/console' as stderr and/or stdout, and `/dev/null' as stdin, + // or any other combination that makes sense for your particular daemon. + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; + int nullfd = open("/dev/null", O_RDWR, mode); + if (nullfd >= 0) dup2(nullfd, STDIN_FILENO); + + int outfd = -1; + if (optOutputTo) + { + outfd = open(optOutputTo, O_WRONLY | (createIfNecessary ? O_CREAT : 0), mode); + if (outfd < 0) LogTime(MUSCLE_LOG_ERROR, "BecomeDaemonProcess(): Could not open %s to redirect stdout, stderr\n", optOutputTo); + } + if (outfd >= 0) (void) dup2(outfd, STDOUT_FILENO); + if (outfd >= 0) (void) dup2(outfd, STDERR_FILENO); + + _isDaemonProcess = true; + return B_NO_ERROR; +} +#endif + +status_t BecomeDaemonProcess(const char * optNewDir, const char * optOutputTo, bool createIfNecessary) +{ + bool isParent; + status_t ret = SpawnDaemonProcess(isParent, optNewDir, optOutputTo, createIfNecessary); + if ((ret == B_NO_ERROR)&&(isParent)) ExitWithoutCleanup(0); + return ret; +} + +void RemoveANSISequences(String & s) +{ + TCHECKPOINT; + + static const char _escapeBuf[] = {0x1B, '[', '\0'}; + static String _escape; if (_escape.IsEmpty()) _escape = _escapeBuf; + + while(true) + { + int32 idx = s.IndexOf(_escape); // find the next escape sequence + if (idx >= 0) + { + const char * data = s()+idx+2; // move past the ESC char and the [ char + switch(data[0]) + { + case 's': case 'u': case 'K': // these are single-letter codes, so + data++; // just skip over them and we are done + break; + + case '=': + data++; + // fall through! + default: + // For numeric codes, keep going until we find a non-digit that isn't a semicolon + while((muscleInRange(*data, '0', '9'))||(*data == ';')) data++; + if (*data) data++; // and skip over the trailing letter too. + break; + } + s = s.Substring(0, idx) + s.Substring((uint32)(data-s())); // remove the escape substring + } + else break; + } +} + +String CleanupDNSLabel(const String & s) +{ + uint32 len = muscleMin(s.Length(), (uint32)63); // DNS spec says maximum 63 chars per label! + String ret; if (ret.Prealloc(len) != B_NO_ERROR) return ret; + + const char * p = s(); + for (uint32 i=0; i>0)&0x0F)+'A'; + retString += ((c>>4)&0x0F)+'A'; + } + return B_NO_ERROR; +} + +status_t NybbleizeData(const ByteBuffer & buf, String & retString) +{ + return NybbleizeData(buf.GetBuffer(), buf.GetNumBytes(), retString); +} + +status_t DenybbleizeData(const String & nybbleizedText, ByteBuffer & retBuf) +{ + uint32 numBytes = nybbleizedText.Length(); + if ((numBytes%2)!=0) + { + LogTime(MUSCLE_LOG_ERROR, "DenybblizeData: Nybblized text [%s] has an odd length; that shouldn't ever happen!\n", nybbleizedText()); + return B_ERROR; + } + + if (retBuf.SetNumBytes(numBytes/2, false) != B_NO_ERROR) return B_ERROR; + + uint8 * b = retBuf.GetBuffer(); + for (uint32 i=0; i 0) ret += ' '; + char b[32]; sprintf(b, "%02x", buf[i]); + ret += b; + } + } + return ret; +} + +String HexBytesToString(const ConstByteBufferRef & bbRef) +{ + return bbRef() ? HexBytesToString(*bbRef()) : String("(null)"); +} + +String HexBytesToString(const ByteBuffer & bb) +{ + return HexBytesToString(bb.GetBuffer(), bb.GetNumBytes()); +} + +String HexBytesToString(const Queue & bytes) +{ + uint32 numBytes = bytes.GetNumItems(); + + String ret; + if (ret.Prealloc(numBytes*3) == B_NO_ERROR) + { + for (uint32 i=0; i 0) ret += ' '; + char b[32]; sprintf(b, "%02x", bytes[i]); + ret += b; + } + } + return ret; +} + +ByteBufferRef ParseHexBytes(const char * buf) +{ + ByteBufferRef bb = GetByteBufferFromPool((uint32)strlen(buf)); + if (bb()) + { + uint8 * b = bb()->GetBuffer(); + uint32 count = 0; + StringTokenizer tok(buf, " \t\r\n"); + const char * next; + while((next = tok()) != NULL) + { + if (strlen(next) > 0) + { + if (next[0] == '/') b[count++] = next[1]; + else b[count++] = (uint8) strtol(next, NULL, 16); + } + } + bb()->SetNumBytes(count, true); + } + return bb; +} + +status_t AssembleBatchMessage(MessageRef & batchMsg, const MessageRef & newMsg) +{ + if (batchMsg() == NULL) + { + batchMsg = newMsg; + return B_NO_ERROR; + } + else if (batchMsg()->what == PR_COMMAND_BATCH) return batchMsg()->AddMessage(PR_NAME_KEYS, newMsg); + else + { + MessageRef newBatchMsg = GetMessageFromPool(PR_COMMAND_BATCH); + if ((newBatchMsg())&&(newBatchMsg()->AddMessage(PR_NAME_KEYS, batchMsg) == B_NO_ERROR)&&(newBatchMsg()->AddMessage(PR_NAME_KEYS, newMsg) == B_NO_ERROR)) + { + batchMsg = newBatchMsg; + return B_NO_ERROR; + } + } + return B_ERROR; +} + +bool FileExists(const char * filePath) +{ + FILE * fp = fopen(filePath, "rb"); + if (fp) fclose(fp); + return (fp != NULL); +} + +status_t RenameFile(const char * oldPath, const char * newPath) +{ + return (rename(oldPath, newPath) == 0) ? B_NO_ERROR : B_ERROR; +} + +status_t CopyFile(const char * oldPath, const char * newPath) +{ + if (strcmp(oldPath, newPath) == 0) return B_NO_ERROR; // Copying something onto itself is a no-op + + FILE * fpIn = fopen(oldPath, "rb"); + if (fpIn == NULL) return B_ERROR; + + status_t ret = B_NO_ERROR; // optimistic default + FILE * fpOut = fopen(newPath, "wb"); + if (fpOut) + { + while(1) + { + char buf[4*1024]; + size_t bytesRead = fread(buf, 1, sizeof(buf), fpIn); + if ((bytesRead < sizeof(buf))&&(feof(fpIn) == false)) + { + ret = B_ERROR; + break; + } + + size_t bytesWritten = fwrite(buf, 1, bytesRead, fpOut); + if (bytesWritten < bytesRead) + { + ret = B_ERROR; + break; + } + if (feof(fpIn)) break; + } + fclose(fpOut); + } + else ret = B_ERROR; + + fclose(fpIn); + + if ((fpOut)&&(ret != B_NO_ERROR)) (void) DeleteFile(newPath); // clean up on error + return ret; +} + +status_t DeleteFile(const char * filePath) +{ +#ifdef _MSC_VER + int unlinkRet = _unlink(filePath); // stupid MSVC! +#else + int unlinkRet = unlink(filePath); +#endif + return (unlinkRet == 0) ? B_NO_ERROR : B_ERROR; +} + +String GetHumanReadableProgramNameFromArgv0(const char * argv0) +{ + String ret = argv0; + +#ifdef __APPLE__ + ret = ret.Substring(0, ".app/"); // we want the user-visible name, not the internal name! +#endif + +#ifdef __WIN32__ + ret = ret.Substring("\\").Substring(0, ".exe"); +#else + ret = ret.Substring("/"); +#endif + return ret; +} + +#ifdef WIN32 +void Win32AllocateStdioConsole() +{ + // Open a console for debug output to appear in + AllocConsole(); + freopen("conin$", "r", stdin); + freopen("conout$", "w", stdout); + freopen("conout$", "w", stderr); +} +#endif + +#if defined(__linux__) || defined(__APPLE__) +static double ParseMemValue(const char * b) +{ + while((*b)&&(muscleInRange(*b, '0', '9') == false)) b++; + return muscleInRange(*b, '0', '9') ? atof(b) : -1.0; +} +#endif + +float GetSystemMemoryUsagePercentage() +{ +#if defined(__linux__) + FILE * fpIn = fopen("/proc/meminfo", "r"); + if (fpIn) + { + double memTotal = -1.0, memFree = -1.0, buffered = -1.0, cached = -1.0; + char buf[512]; + while((fgets(buf, sizeof(buf), fpIn) != NULL)&&((memTotal<=0.0)||(memFree<0.0)||(buffered<0.0)||(cached<0.0))) + { + if (strncmp(buf, "MemTotal:", 9) == 0) memTotal = ParseMemValue(buf+9); + else if (strncmp(buf, "MemFree:", 8) == 0) memFree = ParseMemValue(buf+8); + else if (strncmp(buf, "Buffers:", 8) == 0) buffered = ParseMemValue(buf+8); + else if (strncmp(buf, "Cached:", 7) == 0) cached = ParseMemValue(buf+7); + } + fclose(fpIn); + + if ((memTotal > 0.0)&&(memFree >= 0.0)&&(buffered >= 0.0)&&(cached >= 0.0)) + { + double memUsed = memTotal-(memFree+buffered+cached); + return (float) (memUsed/memTotal); + } + } +#elif defined(__APPLE__) + FILE * fpIn = popen("/usr/bin/vm_stat", "r"); + if (fpIn) + { + double pagesUsed = 0.0, totalPages = 0.0; + char buf[512]; + while(fgets(buf, sizeof(buf), fpIn) != NULL) + { + if (strncmp(buf, "Pages", 5) == 0) + { + double val = ParseMemValue(buf); + if (val >= 0.0) + { + if ((strncmp(buf, "Pages wired", 11) == 0)||(strncmp(buf, "Pages active", 12) == 0)) pagesUsed += val; + totalPages += val; + } + } + else if (strncmp(buf, "Mach Virtual Memory Statistics", 30) != 0) break; // Stop at "Translation Faults", we don't care about anything at or below that + } + pclose(fpIn); + + if (totalPages > 0.0) return (float) (pagesUsed/totalPages); + } +#elif defined(WIN32) && !defined(__MINGW32__) + MEMORYSTATUSEX stat; memset(&stat, 0, sizeof(stat)); + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx(&stat); + return ((float)stat.dwMemoryLoad)/100.0f; +#endif + return -1.0f; +} + +bool ParseBool(const String & word, bool defaultValue) +{ + static const char * _onWords[] = {"on", "enable", "enabled", "true", "t", "y", "yes", "1"}; + static const char * _offWords[] = {"off", "disable", "disabled", "false", "f", "n", "no", "0"}; + + String s = word.Trim().ToLowerCase(); + for (uint32 i=0; i & addTo, bool caseSensitive = false); + +/** Parses settings from the given file. Works similarly to + * ParseArgs() (above), only the values are read from a file + * instead of from the arguments vector. The file may contain + * comments that are prepended with a hash symbol (#); these + * will be safely ignored. + * + * @param file File pointer to read from. This file must be + * opened for reading, and will not be fclosed() by this function. + * @param addTo The Queue to add the arguments to + * @param caseSensitive Defaults to false. If true, the case of the arguments will be retained; if false, they will be forced to lower case. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ParseFile(FILE * file, Queue & addTo, bool caseSensitive = false); + +/** Same as above, except that the file data is passed in as a String + * instead of as a FILE handle. + * @param fileData a String containing the contents of the file to parse. + * @param addTo The Queue to add the arguments to + * @param caseSensitive Defaults to false. If true, the case of the arguments will be retained; if false, they will be forced to lower case. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ParseFile(const String & fileData, Queue & addTo, bool caseSensitive = false); + +/** Parses the single string argument (arg) and adds the results to (addTo). + * Formatting rules for the string are described above in the ParseArgs() documentation. + * @param arg string to parse. + * @param addTo The Queue to add the argument to. + * @param caseSensitive Defaults to false. If true, the case of the arguments will be retained; if false, they will be forced to lower case. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ParseArg(const String & arg, Queue & addTo, bool caseSensitive = false); + +/** Scans a line for multiple arguments and calls ParseArg() on each one found. + * Quotation marks will be used to group tokens if necessary. + * @param arg A line of text, e.g. arg1=blah arg2=borf arg3="quoted borf" + * @param addTo Queue to add the argument results to. + * @param caseSensitive Defaults to false. If true, the case of the arguments will be retained; if false, they will be forced to lower case. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ParseArgs(const String & arg, Queue & addTo, bool caseSensitive = false); + +/** This does the inverse operation of ParseArgs(). That is to say, it + * takes a Queue that was previously created via ParseArgs() and returns + * a String representing the same data. Note that the returned String isn't + * guaranteed to be identical to the one that was previously passed in to + * ParseArgs(); in particular, any comments will have been stripped out. + * @param argQ A Queue containing String field indicating the arguments to add to the String + * @param startIdx Index to start reading the Queue at. Defaults to zero. + * @param afterEndIdx One greater than the largest index in the Queue to read. If this value + * is greater than the length of the queue, it will be treated as if it was the + * length of the Queue. Defaults to MUSCLE_NO_LIMIT. + * @return The resulting String. + */ +String UnparseArgs(const Queue & argQ, uint32 startIdx=0, uint32 afterEndIdx=MUSCLE_NO_LIMIT); + +/** Convenience method: Looks for a given hostname or hostname:port string in + * the given field of the args Message, and returns the appropriate parsed + * values if it is found. + * @param args the Message that was returned by ParseArg(). + * @param fn the field name to look for in (args) + * @param retHost On successful return, the hostname or IP address to connect to will be written here. + * @param retPort On successful return, if a port number was parsed it will be written here. + * @param portRequired If false, this function will succeed even if no port was specified. + * If true, the function will fail if a port was not specified (e.g. "localhost:5555"). + * Defaults to false. + * @param argIdx The field index to use when parsing the argument string from (args). Defaults to zero. + * @returns B_NO_ERROR if an argument was parsed, or B_ERROR if it wasn't. + */ +status_t ParseConnectArg(const Message & args, const String & fn, String & retHost, uint16 & retPort, bool portRequired = false, uint32 argIdx = 0); + +/** Same as above, except that instead of looking for the specified string in a Message, in this + * case the string is passed in directly. + * @param arg The connect string (e.g. "localhost:2960") + * @param retHost On successful return, the hostname or IP address to connect to will be written here. + * @param retPort On successful return, if a port number was parsed it will be written here. + * @param portRequired If false, this function will succeed even if no port was specified. + * If true, the function will fail if a port was not specified (e.g. "localhost:5555"). + * Defaults to false. + * @returns B_NO_ERROR if an argument was parsed, or B_ERROR if it wasn't. + */ +status_t ParseConnectArg(const String & arg, String & retHost, uint16 & retPort, bool portRequired = false); + +/** Given a hostname (or IP address) and a port number, returns the associated connect-string (e.g. "localhost:9999") + * or ("[ff05::1]:5555") + * @param host A hostname or IP address + * @param port A port number. + */ +String GetConnectString(const String & host, uint16 port); + +/** Convenience method: Looks for a port number in the given field of the Message, + * and sets (retPort) if it finds one. + * @param args the Message that was returned by ParseArg(). + * @param fn the field name to look for in (args) + * @param retPort On successful return, if a port number was parsed it will be written here. + * @param argIdx optional index to look in for the string to parse. + * @returns B_NO_ERROR if an argument was parsed, or B_ERROR if it wasn't. + */ +status_t ParsePortArg(const Message & args, const String & fn, uint16 & retPort, uint32 argIdx = 0); + +/** Given an English word representing a boolean (e.g. "on", "off", "enable", "disable", "true", "false", "1", "0", etc), + * returns the corresponding boolean value. + * @param word The english word string to parse. + * @param defaultValue what to return if the passed-in string is not recognized. + * @returns true or false, depending on the word. If the word isn't recognized, (defaultValue) will be returned. Defaults to true. + */ +bool ParseBool(const String & word, bool defaultValue=true); + +/** Looks for some globally useful startup arguments in the (args) + * Message and handles them by calling the appropriate setup routines. + * Recognized arguments currently include the following: + * daemon -- Non-Windows only: Run this process in the background + * localhost=ip -- Treat connections from localhost as if they were coming from (ip) + * displaylevel=levelstr -- Set the stdout output display filter level to (levelstr) + * filelevel=levelstr -- Set the output log file filter level to (levelstr) + * logfile=levelstr -- Force the log file to have this name/location + * nice[=niceLevel] -- Linux/OSX only: makes this process nicer (i.e. lower priority) + * mean[=meanLevel] -- Linux/OSX only: makes this process meaner (i.e. higher priority) + * realtime[=priority] -- Linux only: makes this process real-time (requires root access) + * debugcrashes -- Linux only: print a stack trace when a crash occurs + * console -- Windows only: open a DOS box to display this window's output + * @param args an arguments Message, as produced by ParseArgs() or ParseFile() or etc. + */ +void HandleStandardDaemonArgs(const Message & args); + +/** Similar to the standard exit() call, except that no global object destructors will + * be called. This is sometimes useful, e.g. in fork() situations where you want the + * parent process to just go away without any chance of a crash during cleanup. + * @param exitCode the exit code value that should be passed back to our parent process + * (i.e. the argument to pass to exit() or _exit()) + */ +void ExitWithoutCleanup(int exitCode); + +/** Calls fork(), setsid(), chdir(), umask(), etc, to fork an independent daemon process. + * Also closes all open file descriptors. + * Note that this function will call ExitWithoutCleanup() on the parent process if successful, + * and thus won't ever return in that process. + * @param optNewDir If specified, the daemon will chdir() to the directory specified here. + * @param optOutputTo Where to redirect stderr and stdout to. Defaults to "/dev/null". + * If set to NULL, or if the output device can't be opened, output + * will not be rerouted. + * @param createOutputFileIfNecessary if set true, and (optOutputTo) can't be opened, + * (optOutputTo) will be created. + * @return B_NO_ERROR on success (the child process will see this), B_ERROR on failure. + */ +status_t BecomeDaemonProcess(const char * optNewDir = NULL, const char * optOutputTo = "/dev/null", bool createOutputFileIfNecessary = true); + +/** Returns true iff we are a daemon process created via BecomeDaemonProcess() or SpawnDaemonProcess() */ +bool IsDaemonProcess(); + +/** Same as BecomeDaemonProcess(), except that the parent process returns as well as the child process. + * @param returningAsParent Set to true on return of the parent process, or false on return of the child process. + * @param optNewDir If specified, the child will chdir() to the directory specified here. + * @param optOutputTo Where to redirect stderr and stdout to. Defaults to "/dev/null". + * If set to NULL, or if the output device can't be opened, output + * will not be rerouted. + * @param createOutputFileIfNecessary if set true, and (optOutputTo) can't be opened, + * (optOutputTo) will be created. + * @return B_NO_ERROR (twice!) on success, B_ERROR on failure. + */ +status_t SpawnDaemonProcess(bool & returningAsParent, const char * optNewDir = NULL, const char * optOutputTo = "/dev/null", bool createOutputFileIfNecessary = true); + +/** Convenience function: Removes any ANSI formatting escape-sequences from (s), so + * that (s) can be displayed as plain text without a bunch of garbage showing up in it. + */ +void RemoveANSISequences(String & s); + +/** Given a string, returns that same string except with any symbols that are not illegal + * in a DNS label removed. (According to DNS rules, only letters, digits, and the '-' + * character are legal in a DNS label, and the label must be less than 64 characters long). + * Note that this string cleans up just a single part of a DNS hostname path. + * If you want to clean up a path string (e.g. "www.foo.com"), call CleanupDNSPath() instead. + * @param s A string that is presented as a candidate for being a DNS label. + * @returns the DNS label that most closely resembles (s). + */ +String CleanupDNSLabel(const String & s); + +/** Given a DNS path string (e.g. "www.foo.com") runs each dot-separated portion of the + * path through CleanupDNSLabel() and returns the cleaned up result. + * @param s A string that is presented as a candidate for being a DNS path. + * @returns the DNS path that most closely resembles (s). + */ +String CleanupDNSPath(const String & s); + +/** Convenience function. Given a buffer of arbitrary data, returns a nybble-ized String + * that represents that same data using only the ASCII characters 'A' through 'P. The + * returned String will be twice the length of the passed-in buffer, and the original + * data can be recovered from the String by calling DenybbleizeData(). + * @param buf The data to nybbleize + * @param retString On success, the nybbleized String is written here. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t NybbleizeData(const ByteBuffer & buf, String & retString); + +/** Convenience function. Given a buffer of arbitrary data, returns a nybble-ized String + * that represents that same data using only the ASCII characters 'A' through 'P. The + * returned String will be twice the length of the passed-in buffer, and the original + * data can be recovered from the String by calling DenybbleizeData(). + * @param inBytes Pointer to the bytes to nybbleize. + * @param numInBytes Number of bytes pointed to by (inBytes). + * @param retString On success, the nybbleized String is written here. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t NybbleizeData(const uint8 * inBytes, uint32 numInBytes, String & retString); + +/** Convenience function. Given a String that was produced by NybblizedData(), + * returns the eqivalent ByteBuffer. + * @param nybbleizedText The String to de-nybbleize + * @param retBuf On success, the de-nybbleized data is written here. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t DenybbleizeData(const String & nybbleizedText, ByteBuffer & retBuf); + +/** Convenience function: Returns a string which is a nybbleized representation of + * the passed-in string's contents (not including its NUL terminator byte) + * @param str A string to nybbleize + * @returns A nybbleized equivalent of (str), as described in NybbleizeData(). + */ +String NybbleizeString(const String & str); + +/** Convenience function: Returns a string which is the denybbleized + * representation of the passed-in nybbleized string. + * @param nybStr A string to denybbleize. Note that not all nybbleized + * strings can be de-nybblized correctly back into a + * String object: in particular, if the de-nybbleized + * data contains any NUL bytes, then the String + * returned by this function will be truncated at the first NUL. + * If you need to be able to decode any legal nybbleized string, + * call NybbleizeData() instead of this function. + */ +String DenybbleizeString(const String & nybStr); + +/** This function is like strstr(), except that instead of searching for a substring + * within a string, it looks for a given binary pattern inside a binary buffer. + * @param lookIn The buffer to look for the sub-region inside + * @param numLookInBytes The number of bytes pointed to by (lookIn) + * @param lookFor The byte pattern to look for inside of (lookin) + * @param numLookForBytes The number of bytes pointed to by (lookFor) + * @returns A pointer to the first instance of (lookFor) found inside of (lookIn), or NULL + * if no such pattern was found inside of (lookIn). + */ +const uint8 * MemMem(const uint8 * lookIn, uint32 numLookInBytes, const uint8 * lookFor, uint32 numLookForBytes); + +/** This is a convenience function for debugging. It will print to stdout the + * specified array of bytes in human-readable hexadecimal format, along with + * an ASCII sidebar when possible. + * @param bytes The bytes to print out. May be NULL. + * @param numBytes How many bytes (bytes) points to + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + * @param optFile Optional file to print the output to. If left NULL, printing will go to stdout. + */ +void PrintHexBytes(const void * bytes, uint32 numBytes, const char * optDesc = NULL, uint32 numColumns = 16, FILE * optFile = NULL); + +/** This is a convenience function for debugging. It will print to stdout the + * specified array of bytes in human-readable hexadecimal format, along with + * an ASCII sidebar when possible. + * @param bbRef A ByteBufferRef referencing the bytes to print. May be NULL. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + * @param optFile Optional file to print the output to. If left NULL, printing will go to stdout. + */ +void PrintHexBytes(const ConstByteBufferRef & bbRef, const char * optDesc = NULL, uint32 numColumns = 16, FILE * optFile = NULL); + +/** This is a convenience function for debugging. It will print to stdout the + * specified array of bytes in human-readable hexadecimal format, along with + * an ASCII sidebar when possible. + * @param bb A ByteBufferRef referencing the bytes to print. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + * @param optFile Optional file to print the output to. If left NULL, printing will go to stdout. + */ +void PrintHexBytes(const ByteBuffer & bb, const char * optDesc = NULL, uint32 numColumns = 16, FILE * optFile = NULL); + +/** This is a convenience function for debugging. It will print to stdout the + * specified array of bytes in human-readable hexadecimal format, along with + * an ASCII sidebar when possible. + * @param bytes A Queue of uint8s representing the bytes to print out. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + * @param optFile Optional file to print the output to. If left NULL, printing will go to stdout. + */ +void PrintHexBytes(const Queue & bytes, const char * optDesc = NULL, uint32 numColumns = 16, FILE * optFile = NULL); + +/** This function is the same as PrintHexBytes(), but the output is sent to Log() instead of fprintf(). + * @param logLevel The MUSCLE_LOG_* value indicating the severity level to log the hex bytes at. + * @param bytes The bytes to print out. May be NULL. + * @param numBytes How many bytes (bytes) points to + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +void LogHexBytes(int logLevel, const void * bytes, uint32 numBytes, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is sent to Log() instead of fprintf(). + * @param logLevel The MUSCLE_LOG_* value indicating the severity level to log the hex bytes at. + * @param bbRef Reference to the ByteBuffer to print out. May be a NULL reference. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +void LogHexBytes(int logLevel, const ConstByteBufferRef & bbRef, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is sent to Log() instead of fprintf(). + * @param logLevel The MUSCLE_LOG_* value indicating the severity level to log the hex bytes at. + * @param bb The ByteBuffer to print out. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +void LogHexBytes(int logLevel, const ByteBuffer & bb, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is sent to Log() instead of fprintf(). + * @param logLevel The MUSCLE_LOG_* value indicating the severity level to log the hex bytes at. + * @param bytes A Queue of uint8s representing the bytes to print out. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be printed + * out with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +void LogHexBytes(int logLevel, const Queue & bytes, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is returned as a String. + * @param bytes The bytes to return a structured-text description of. May be NULL. + * @param numBytes How many bytes (bytes) points to + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be generated + * with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +String HexBytesToAnnotatedString(const void * bytes, uint32 numBytes, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is returned as a String. + * @param bbRef Reference to the ByteBuffer to return a structured-text description of. May be a NULL reference. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be generated + * with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +String HexBytesToAnnotatedString(const ConstByteBufferRef & bbRef, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is returned as a String. + * @param bb The ByteBuffer to return a structured-text description of. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be generated + * with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +String HexBytesToAnnotatedString(const ByteBuffer & bb, const char * optDesc = NULL, uint32 numColumns = 16); + +/** This function is the same as PrintHexBytes(), but the output is returned as a String. + * @param bytes A Queue of uint8s representing the bytes to return a structured-text description of. + * @param optDesc if non-NULL, this will be used as a prefix/title string. + * @param numColumns If specified non zero, then the bytes will be generated + * with this many bytes per row. Defaults to 16. + * If set to zero, then all the output will be placed + * on a single line, using a simpler hex-only format. + */ +String HexBytesToAnnotatedString(const Queue & bytes, const char * optDesc = NULL, uint32 numColumns = 16); + +/** Given a string with an ASCII representation of hexadecimal bytes, + * returns the corresponding binary data. + * @param buf A hexadecimal string. Each hex byte should be expressed as + * two ASCII characters (e.g. "f0 1f 7e f7"), or alternatively + * you can enter chars in ASCII if you prepend each one with + * a slash (e.g. "/h /e /l /l /o"). + * @returns a ByteBufferRef containing the corresponding binary data, + * or a NULL ByteBufferRef on failure (out of memory?) + */ +ByteBufferRef ParseHexBytes(const char * buf); + +/** Given a byte buffer, returns an ASCII representation of it. + * @param buf A buffer of bytes + * @param numBytes The number of bytes that (buf) points to. + * @returns a String with human-readable contents: e.g. "5F A3 A2"... + */ +String HexBytesToString(const uint8 * buf, uint32 numBytes); + +/** Given a reference to a byte buffer, returns an ASCII representation of it. + * @param bbRef Reference to a buffer of bytes. + * @returns a String with human-readable contents: e.g. "5F A3 A2"... + */ +String HexBytesToString(const ConstByteBufferRef & bbRef); + +/** Given a byte buffer, returns an ASCII representation of it. + * @param bb a buffer of bytes. + * @returns a String with human-readable contents: e.g. "5F A3 A2"... + */ +String HexBytesToString(const ByteBuffer & bb); + +/** Given a Queue of uint8s, returns an ASCII representation of it. + * @param bytes a Queue of uint8s. + * @returns a String with human-readable contents: e.g. "5F A3 A2"... + */ +String HexBytesToString(const Queue & bytes); + +/** A convenience method, useful to optimally create a single PR_COMMAND_BATCH + * Message out of a set of zero or more other Messages. Here's how it works: + * If (batchMsg) is a NULL ref, then (batchMsg) is set to reference the same + * Message as (newMsg). If (batchMsg) is a PR_COMMAND_BATCH Message, then + * (newMsg) is appended to its PR_NAME_KEYS field. Otherwise, a new + * PR_COMMAND_BATCH Message is created, and both (batchMsg) and (newMsg) are + * added to it. + * @param batchMsg Reference to the Message that you will want to eventually + * send to the server. This Reference will be modified. + * May be a NULL ref. + * @param newMsg Reference to the Message to add to (batchMsg). May not + * be a NULL ref. + * @returns B_NO_ERROR on success, or B_ERROR on error (out of memory?) + */ +status_t AssembleBatchMessage(MessageRef & batchMsg, const MessageRef & newMsg); + +/** Returns true iff the file with the specified path exists. + * @param filePath Path of the file to check for. + * @returns true if the file exists (and is readable), false otherwise. + */ +bool FileExists(const char * filePath); + +/** Attempts to rename from (oldPath) to (newPath). + * @param oldPath the path of an existing file or directory. + * @param newPath the new name that the file should have. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t RenameFile(const char * oldPath, const char * newPath); + +/** Attempts to copy from (oldPath) to (newPath). + * @param oldPath the path of an existing file or directory. + * @param newPath the name that the new file should have. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t CopyFile(const char * oldPath, const char * newPath); + +/** Attempts to delete the file with the specified file path. + * @param filePath Path of the file to delete. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t DeleteFile(const char * filePath); + +/** Given argv[0], returns a human-readable program title based on the file name. + * For example, "c:\Program Files\Blah.exe" is returned as "Blah", or + * "/Users/jaf/MyProg/MyProg.app/Contents/MacOS/MyProg" is returned as "MyProg". + * @param argv0 argv[0], as passed to main(). + * @returns a human-readable title string. + */ +String GetHumanReadableProgramNameFromArgv0(const char * argv0); + +#ifdef WIN32 +/** This function is only available on Win32, and does the + * standard AllocConsole() and freopen() trick to cause a + * Console window to appear and be available for stdin/stdout/stderr + * to operate on. + */ +void Win32AllocateStdioConsole(); +#endif + +/** Returns a value between 0.0f and 1.0f, indicating what percentage of the + * host computer's RAM is currently in use. Does not include filesystem + * cache in this figure. The intent is that values close to 1.0 indicate + * that the system is close to memory exhaustion. + * @note This function is implemented only under Linux, MacOS/X, and Windows. + * @returns a value between 0.0f and 1.0f representing memory usage, + * or a negative value on failure. + */ +float GetSystemMemoryUsagePercentage(); + +/** @} */ // end of miscutilityfunctions doxygen group + +}; // end namespace muscle + +#endif diff --git a/util/NestCount.h b/util/NestCount.h new file mode 100644 index 00000000..de3656fd --- /dev/null +++ b/util/NestCount.h @@ -0,0 +1,75 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleNestCount_h +#define MuscleNestCount_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** This class represents a counter of nested function calls. It is essentially just a uint32, + * but made into a class so that it can be auto-initialized to zero, protected from arbitrary + * value changes, etc. + */ +class NestCount +{ +public: + /** Default constructor. Sets the nest count to zero. */ + NestCount() : _count(0) {/* empty */} + + /** Increments our value, and returns true iff the new value is one. */ + bool Increment() {return (++_count == 1);} + + /** Decrements our value, and returns true iff the new value is zero. */ + bool Decrement() {MASSERT(_count>0, "NestCount Decremented to below zero!"); return (--_count == 0);} + + /** Returns the current value */ + uint32 GetCount() const {return _count;} + + /** Returns true iff nesting is currently active (i.e. if our counter is non-zero) */ + bool IsInBatch() const {return (_count > 0);} + + /** Returns true iff we are in the outermost nesting level of the batch */ + bool IsOutermost() const {return (_count == 1);} + + /** Sets the count to the specified value. In general it should not be necessary to call + * this method, so don't call it unless you know what you are doing! + */ + void SetCount(uint32 c) {_count = c;} + +private: + uint32 _count; +}; + +/** A trivial little class that simply increments the NestCount in its constructor + * and decrements the NestCount in its destructor. It's useful for reliably + * tracking call-nest-counts in functions with multiple return() points. + */ +class NestCountGuard +{ +public: + /** Constructor. Increments the specified counter value. + * @param count A reference to the uint32 that our constructor will + * increment, and our destructor will decrement. + */ + NestCountGuard(NestCount & count) : _count(count) {(void) _count.Increment();} + + /** Destructor. Decrements our associated counter value. */ + ~NestCountGuard() {(void) _count.Decrement();} + + /** Returns our NestCount object's current count. */ + uint32 GetNestCount() const {return _count.GetCount();} + + /** Returns true iff nesting is currently active (i.e. if our counter is non-zero) */ + bool IsInBatch() const {return _count.IsInBatch();} + + /** Returns true iff we are the outermost of the nested calls to our NestCount */ + bool IsOutermost() const {return _count.IsOutermost();} + +private: + NestCount & _count; +}; + +}; // end namespace muscle + +#endif diff --git a/util/NetworkUtilityFunctions.cpp b/util/NetworkUtilityFunctions.cpp new file mode 100644 index 00000000..a96da8eb --- /dev/null +++ b/util/NetworkUtilityFunctions.cpp @@ -0,0 +1,1697 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include + +#include "util/MiscUtilityFunctions.h" // for GetConnectString() (which is deliberately defined here) +#include "util/NetworkUtilityFunctions.h" +#include "util/SocketMultiplexer.h" + +#if defined(__BEOS__) || defined(__HAIKU__) +# include // for snooze() +#elif __ATHEOS__ +# include // for snooze() +#endif + +#ifdef WIN32 +# include +# include // for SIO_UDP_CONNRESET, etc +# include +# ifndef MUSCLE_AVOID_MULTICAST_API +# include // for IP_MULTICAST_LOOP, etc +# endif +# pragma warning(disable: 4800 4018) +typedef char sockopt_arg; // Windows setsockopt()/getsockopt() use char pointers +#else +typedef void sockopt_arg; // Whereas sane operating systems use void pointers +# include +# include +# include +# include +# ifndef BEOS_OLD_NETSERVER +# include +# endif +# include +# ifdef BEOS_OLD_NETSERVER +# include // for the run-time bone check +# include // for the backup run-time bone check +#else +# include +# include +# include +# endif +#endif + +#include + +#if defined(__FreeBSD__) || defined(BSD) || defined(__APPLE__) || defined(__linux__) +# if defined(ANDROID) // JFM +# define USE_SOCKETPAIR 1 +# else +# define USE_GETIFADDRS 1 +# define USE_SOCKETPAIR 1 +# include +# endif +#endif + +#include "system/GlobalMemoryAllocator.h" // for muscleAlloc()/muscleFree() + +namespace muscle { + +#ifndef MUSCLE_AVOID_IPV6 +static bool _automaticIPv4AddressMappingEnabled = true; // if true, we automatically translate IPv4-compatible addresses (e.g. ::192.168.0.1) to IPv4-mapped-IPv6 addresses (e.g. ::ffff:192.168.0.1) and back +void SetAutomaticIPv4AddressMappingEnabled(bool e) {_automaticIPv4AddressMappingEnabled = e;} +bool GetAutomaticIPv4AddressMappingEnabled() {return _automaticIPv4AddressMappingEnabled;} + +# define MUSCLE_SOCKET_FAMILY AF_INET6 +static inline void GET_SOCKADDR_IP(const struct sockaddr_in6 & sockAddr, ip_address & ipAddr) +{ + switch(sockAddr.sin6_family) + { + case AF_INET6: + { + uint32 tmp = sockAddr.sin6_scope_id; // MacOS/X uses __uint32_t, which isn't quite the same somehow + ipAddr.ReadFromNetworkArray(sockAddr.sin6_addr.s6_addr, &tmp); + if ((_automaticIPv4AddressMappingEnabled)&&(ipAddr != localhostIP)&&(IsValidAddress(ipAddr))&&(IsIPv4Address(ipAddr))) ipAddr.SetLowBits(ipAddr.GetLowBits() & ((uint64)0xFFFFFFFF)); // remove IPv4-mapped-IPv6-bits + } + break; + + case AF_INET: + { + uint32 ipAddr32 = ntohl(((const struct sockaddr_in &)sockAddr).sin_addr.s_addr); + ipAddr.SetBits(ipAddr32, 0); + ipAddr.SetInterfaceIndex(0); + } + break; + + default: + // empty + break; + } +} +static inline void SET_SOCKADDR_IP(struct sockaddr_in6 & sockAddr, const ip_address & ipAddr) +{ + uint32 tmp; // MacOS/X uses __uint32_t, which isn't quite the same somehow + if ((_automaticIPv4AddressMappingEnabled)&&(ipAddr != localhostIP)&&(IsValidAddress(ipAddr))&&(IsIPv4Address(ipAddr))) + { + ip_address tmpAddr = ipAddr; + tmpAddr.SetLowBits(tmpAddr.GetLowBits() | (((uint64)0xFFFF)<<32)); // add IPv4-mapped-IPv6-bits + tmpAddr.WriteToNetworkArray(sockAddr.sin6_addr.s6_addr, &tmp); + } + else ipAddr.WriteToNetworkArray(sockAddr.sin6_addr.s6_addr, &tmp); + + sockAddr.sin6_scope_id = tmp; +} +static inline uint16 GET_SOCKADDR_PORT(const struct sockaddr_in6 & addr) +{ + switch(addr.sin6_family) + { + case AF_INET6: return ntohs(addr.sin6_port); + case AF_INET: return ntohs(((const struct sockaddr_in &)addr).sin_port); + default: return 0; + } +} + +static inline void SET_SOCKADDR_PORT(struct sockaddr_in6 & addr, uint16 port) {addr.sin6_port = htons(port);} +static inline uint16 GET_SOCKADDR_FAMILY(const struct sockaddr_in6 & addr) {return addr.sin6_family;} +static inline void SET_SOCKADDR_FAMILY(struct sockaddr_in6 & addr, uint16 family) {addr.sin6_family = family;} +static void InitializeSockAddr6(struct sockaddr_in6 & addr, const ip_address * optFrom, uint16 port) +{ + memset(&addr, 0, sizeof(struct sockaddr_in6)); +#ifdef SIN6_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif + SET_SOCKADDR_FAMILY(addr, MUSCLE_SOCKET_FAMILY); + if (optFrom) SET_SOCKADDR_IP(addr, *optFrom); + if (port) SET_SOCKADDR_PORT(addr, port); +} +# define DECLARE_SOCKADDR(addr, ip, port) struct sockaddr_in6 addr; InitializeSockAddr6(addr, ip, port); +#else +# define MUSCLE_SOCKET_FAMILY AF_INET +static inline void GET_SOCKADDR_IP(const struct sockaddr_in & sockAddr, ip_address & ipAddr) {ipAddr = ntohl(sockAddr.sin_addr.s_addr);} +static inline void SET_SOCKADDR_IP(struct sockaddr_in & sockAddr, const ip_address & ipAddr) {sockAddr.sin_addr.s_addr = htonl(ipAddr);} +static inline uint16 GET_SOCKADDR_PORT(const struct sockaddr_in & addr) {return ntohs(addr.sin_port);} +static inline void SET_SOCKADDR_PORT(struct sockaddr_in & addr, uint16 port) {addr.sin_port = htons(port);} +static inline uint16 GET_SOCKADDR_FAMILY(const struct sockaddr_in & addr) {return addr.sin_family;} +static inline void SET_SOCKADDR_FAMILY(struct sockaddr_in & addr, uint16 family) {addr.sin_family = family;} +static void InitializeSockAddr4(struct sockaddr_in & addr, const ip_address * optFrom, uint16 port) +{ + memset(&addr, 0, sizeof(struct sockaddr_in)); + SET_SOCKADDR_FAMILY(addr, MUSCLE_SOCKET_FAMILY); + if (optFrom) SET_SOCKADDR_IP(addr, *optFrom); + if (port) SET_SOCKADDR_PORT(addr, port); +} +# define DECLARE_SOCKADDR(addr, ip, port) struct sockaddr_in addr; InitializeSockAddr4(addr, ip, port); +#endif + +static GlobalSocketCallback * _globalSocketCallback = NULL; +void SetGlobalSocketCallback(GlobalSocketCallback * cb) {_globalSocketCallback = cb;} +GlobalSocketCallback * GetGlobalSocketCallback() {return _globalSocketCallback;} + +static status_t DoGlobalSocketCallback(uint32 eventType, const ConstSocketRef & s) +{ + if (s() == NULL) return B_ERROR; + if (_globalSocketCallback == NULL) return B_NO_ERROR; + return _globalSocketCallback->SocketCallback(eventType, s); +} + +static ConstSocketRef CreateMuscleSocket(int socketType, uint32 createType) +{ + int s = (int) socket(MUSCLE_SOCKET_FAMILY, socketType, 0); + if (s >= 0) + { + ConstSocketRef ret = GetConstSocketRefFromPool(s); + if (ret()) + { +#ifndef MUSCLE_AVOID_IPV6 + // If we're doing IPv4-mapped IPv6 addresses, we gotta tell Windows 7 that it's okay to use them, otherwise they won't work. :^P + if (_automaticIPv4AddressMappingEnabled) + { + int v6OnlyEnabled = 0; // we want v6-only mode disabled, which is to say we want v6-to-v4 compatibility + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const sockopt_arg *) &v6OnlyEnabled, sizeof(v6OnlyEnabled)) != 0) LogTime(MUSCLE_LOG_DEBUG, "Could not disable v6-only mode for socket %i\n", s); + } +#endif + if (DoGlobalSocketCallback(createType, ret) == B_NO_ERROR) return ret; + } + } + return ConstSocketRef(); +} + +ConstSocketRef CreateUDPSocket() +{ + ConstSocketRef ret = CreateMuscleSocket(SOCK_DGRAM, GlobalSocketCallback::SOCKET_CALLBACK_CREATE_UDP); +#if defined(WIN32) && !defined(__MINGW32__) + if (ret()) + { + // This setup code avoids the UDP WSAECONNRESET problem + // described at http://support.microsoft.com/kb/263823/en-us + DWORD dwBytesReturned = 0; + BOOL bNewBehavior = FALSE; + if (WSAIoctl(ret.GetFileDescriptor(), SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL) != 0) ret.Reset(); + } +#endif + return ret; +} + +status_t BindUDPSocket(const ConstSocketRef & sock, uint16 port, uint16 * optRetPort, const ip_address & optFrom, bool allowShared) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + if (allowShared) + { + const int trueValue = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const sockopt_arg *) &trueValue, sizeof(trueValue)); +#ifdef __APPLE__ + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const sockopt_arg *) &trueValue, sizeof(trueValue)); +#endif + } + + DECLARE_SOCKADDR(saSocket, &optFrom, port); + if (bind(fd, (struct sockaddr *) &saSocket, sizeof(saSocket)) == 0) + { + if (optRetPort) + { + muscle_socklen_t len = sizeof(saSocket); + if (getsockname(fd, (struct sockaddr *)&saSocket, &len) == 0) + { + *optRetPort = GET_SOCKADDR_PORT(saSocket); + return B_NO_ERROR; + } + else return B_ERROR; + } + return B_NO_ERROR; + } + else return B_ERROR; +} + +status_t SetUDPSocketTarget(const ConstSocketRef & sock, const ip_address & remoteIP, uint16 remotePort) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + DECLARE_SOCKADDR(saAddr, &remoteIP, remotePort); + return (connect(fd, (struct sockaddr *) &saAddr, sizeof(saAddr)) == 0) ? B_NO_ERROR : B_ERROR; +} + +status_t SetUDPSocketTarget(const ConstSocketRef & sock, const char * remoteHostName, uint16 remotePort, bool expandLocalhost) +{ + ip_address hostIP = GetHostByName(remoteHostName, expandLocalhost); + return (hostIP != invalidIP) ? SetUDPSocketTarget(sock, hostIP, remotePort) : B_ERROR; +} + +ConstSocketRef CreateAcceptingSocket(uint16 port, int maxbacklog, uint16 * optRetPort, const ip_address & optInterfaceIP) +{ + ConstSocketRef ret = CreateMuscleSocket(SOCK_STREAM, GlobalSocketCallback::SOCKET_CALLBACK_CREATE_ACCEPTING); + if (ret()) + { + int fd = ret.GetFileDescriptor(); + +#ifndef WIN32 + // (Not necessary under windows -- it has the behaviour we want by default) + const int trueValue = 1; + (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const sockopt_arg *) &trueValue, sizeof(trueValue)); +#endif + + DECLARE_SOCKADDR(saSocket, &optInterfaceIP, port); + if ((bind(fd, (struct sockaddr *) &saSocket, sizeof(saSocket)) == 0)&&(listen(fd, maxbacklog) == 0)) + { + if (optRetPort) + { + muscle_socklen_t len = sizeof(saSocket); + *optRetPort = (getsockname(fd, (struct sockaddr *)&saSocket, &len) == 0) ? GET_SOCKADDR_PORT(saSocket) : 0; + } + return ret; + } + } + return ConstSocketRef(); // failure +} + +int32 ReceiveData(const ConstSocketRef & sock, void * buffer, uint32 size, bool bm) +{ + int fd = sock.GetFileDescriptor(); + return (fd >= 0) ? ConvertReturnValueToMuscleSemantics(recv_ignore_eintr(fd, (char *)buffer, size, 0L), size, bm) : -1; +} + +int32 ReadData(const ConstSocketRef & sock, void * buffer, uint32 size, bool bm) +{ +#ifdef WIN32 + return ReceiveData(sock, buffer, size, bm); // Windows doesn't support read(), only recv() +#else + int fd = sock.GetFileDescriptor(); + return (fd >= 0) ? ConvertReturnValueToMuscleSemantics(read_ignore_eintr(fd, (char *)buffer, size), size, bm) : -1; +#endif +} + +int32 ReceiveDataUDP(const ConstSocketRef & sock, void * buffer, uint32 size, bool bm, ip_address * optFromIP, uint16 * optFromPort) +{ + int fd = sock.GetFileDescriptor(); + if (fd >= 0) + { + int r; + if ((optFromIP)||(optFromPort)) + { + DECLARE_SOCKADDR(fromAddr, NULL, 0); + muscle_socklen_t fromAddrLen = sizeof(fromAddr); + r = recvfrom_ignore_eintr(fd, (char *)buffer, size, 0L, (struct sockaddr *) &fromAddr, &fromAddrLen); + if (r >= 0) + { + if (optFromIP) GET_SOCKADDR_IP(fromAddr, *optFromIP); + if (optFromPort) *optFromPort = GET_SOCKADDR_PORT(fromAddr); + } + } + else r = recv_ignore_eintr(fd, (char *)buffer, size, 0L); + + if (r == 0) return 0; // for UDP, zero is a valid recv() size, since there is no EOS + return ConvertReturnValueToMuscleSemantics(r, size, bm); + } + else return -1; +} + +int32 SendData(const ConstSocketRef & sock, const void * buffer, uint32 size, bool bm) +{ + int fd = sock.GetFileDescriptor(); + return (fd >= 0) ? ConvertReturnValueToMuscleSemantics(send_ignore_eintr(fd, (const char *)buffer, size, 0L), size, bm) : -1; +} + +int32 WriteData(const ConstSocketRef & sock, const void * buffer, uint32 size, bool bm) +{ +#ifdef WIN32 + return SendData(sock, buffer, size, bm); // Windows doesn't support write(), only send() +#else + int fd = sock.GetFileDescriptor(); + return (fd >= 0) ? ConvertReturnValueToMuscleSemantics(write_ignore_eintr(fd, (const char *)buffer, size), size, bm) : -1; +#endif +} + +int32 SendDataUDP(const ConstSocketRef & sock, const void * buffer, uint32 size, bool bm, const ip_address & optToIP, uint16 optToPort) +{ +#ifdef DEBUG_SENDING_UDP_PACKETS_ON_INTERFACE_ZERO + if ((optToIP != invalidIP)&&(optToIP.GetInterfaceIndex() == 0)&&(IsIPv4Address(optToIP) == false)&&(IsStandardLoopbackDeviceAddress(optToIP) == false)) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "SendDataUDP: Sending to interface 0! [%s]:%u\n", Inet_NtoA(optToIP)(), optToPort); + PrintStackTrace(); + } +#endif + +#if !defined(MUSCLE_AVOID_IPV6) && !defined(MUSCLE_AVOID_MULTICAST_API) +# define MUSCLE_USE_IFIDX_WORKAROUND 1 +#endif + +#ifdef MUSCLE_USE_IFIDX_WORKAROUND + bool doUnsetInterfaceIndex = false; // and remember to set it back afterwards +#endif + + int fd = sock.GetFileDescriptor(); + if (fd >= 0) + { + int s; + if ((optToPort)||(optToIP != invalidIP)) + { + DECLARE_SOCKADDR(toAddr, NULL, 0); + if ((optToPort == 0)||(optToIP == invalidIP)) + { + // Fill in the values with our socket's current target-values, as defaults + muscle_socklen_t length = sizeof(sockaddr_in); + if ((getpeername(fd, (struct sockaddr *)&toAddr, &length) != 0)||(GET_SOCKADDR_FAMILY(toAddr) != MUSCLE_SOCKET_FAMILY)) return -1; + } + + if (optToIP != invalidIP) + { + SET_SOCKADDR_IP(toAddr, optToIP); +#ifdef MUSCLE_USE_IFIDX_WORKAROUND + // Work-around for MacOS/X problem (?) where the interface index in the specified IP address doesn't get used + if ((optToIP.GetInterfaceIndex() != 0)&&(IsMulticastIPAddress(optToIP))&&(GetSocketMulticastSendInterfaceIndex(sock) == 0)) + { + // temporarily set the socket's interface index to the desired one + if (SetSocketMulticastSendInterfaceIndex(sock, optToIP.GetInterfaceIndex()) != B_NO_ERROR) return -1; + doUnsetInterfaceIndex = true; // and remember to set it back afterwards + } +#endif + } + if (optToPort) SET_SOCKADDR_PORT(toAddr, optToPort); + s = sendto_ignore_eintr(fd, (const char *)buffer, size, 0L, (struct sockaddr *)&toAddr, sizeof(toAddr)); + } + else s = send_ignore_eintr(fd, (const char *)buffer, size, 0L); + + if (s == 0) return 0; // for UDP, zero is a valid send() size, since there is no EOS + int32 ret = ConvertReturnValueToMuscleSemantics(s, size, bm); +#ifdef MUSCLE_USE_IFIDX_WORKAROUND + if (doUnsetInterfaceIndex) (void) SetSocketMulticastSendInterfaceIndex(sock, 0); // gotta do this AFTER computing the return value, as it clears errno! +#endif + return ret; + } + else return -1; +} + +status_t ShutdownSocket(const ConstSocketRef & sock, bool dRecv, bool dSend) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + if ((dRecv == false)&&(dSend == false)) return B_NO_ERROR; // there's nothing we need to do! + + // Since these constants aren't defined everywhere, I'll define my own: + const int MUSCLE_SHUT_RD = 0; + const int MUSCLE_SHUT_WR = 1; + const int MUSCLE_SHUT_RDWR = 2; + + return (shutdown(fd, dRecv?(dSend?MUSCLE_SHUT_RDWR:MUSCLE_SHUT_RD):MUSCLE_SHUT_WR) == 0) ? B_NO_ERROR : B_ERROR; +} + +ConstSocketRef Accept(const ConstSocketRef & sock, ip_address * optRetInterfaceIP) +{ + DECLARE_SOCKADDR(saSocket, NULL, 0); + muscle_socklen_t nLen = sizeof(saSocket); + int fd = sock.GetFileDescriptor(); + if (fd >= 0) + { + ConstSocketRef ret = GetConstSocketRefFromPool((int) accept(fd, (struct sockaddr *)&saSocket, &nLen)); + if (DoGlobalSocketCallback(GlobalSocketCallback::SOCKET_CALLBACK_ACCEPT, ret) != B_NO_ERROR) return ConstSocketRef(); // called separately since accept() created this socket, not CreateMuscleSocket() + + if ((ret())&&(optRetInterfaceIP)) + { + muscle_socklen_t len = sizeof(saSocket); + if (getsockname(ret.GetFileDescriptor(), (struct sockaddr *)&saSocket, &len) == 0) GET_SOCKADDR_IP(saSocket, *optRetInterfaceIP); + else *optRetInterfaceIP = invalidIP; + } + return ret; + } + return ConstSocketRef(); // failure +} + +ConstSocketRef Connect(const char * hostName, uint16 port, const char * debugTitle, bool errorsOnly, uint64 maxConnectTime, bool expandLocalhost) +{ + ip_address hostIP = GetHostByName(hostName, expandLocalhost); + if (hostIP != invalidIP) return Connect(hostIP, port, hostName, debugTitle, errorsOnly, maxConnectTime); + else + { + if (debugTitle) LogTime(MUSCLE_LOG_INFO, "%s: hostname lookup for [%s] failed!\n", debugTitle, hostName); + return ConstSocketRef(); + } +} + +ConstSocketRef Connect(const ip_address & hostIP, uint16 port, const char * optDebugHostName, const char * debugTitle, bool errorsOnly, uint64 maxConnectTime) +{ + char ipbuf[64]; Inet_NtoA(hostIP, ipbuf); + + if ((debugTitle)&&(errorsOnly == false)) + { + LogTime(MUSCLE_LOG_INFO, "%s: Connecting to %s: ", debugTitle, GetConnectString(optDebugHostName?optDebugHostName:ipbuf, port)()); + LogFlush(); + } + + bool socketIsReady = false; + ConstSocketRef s = (maxConnectTime == MUSCLE_TIME_NEVER) ? CreateMuscleSocket(SOCK_STREAM, GlobalSocketCallback::SOCKET_CALLBACK_CONNECT) : ConnectAsync(hostIP, port, socketIsReady); + if (s()) + { + int fd = s.GetFileDescriptor(); + int ret = -1; + if (maxConnectTime == MUSCLE_TIME_NEVER) + { + DECLARE_SOCKADDR(saAddr, &hostIP, port); + ret = connect(fd, (struct sockaddr *) &saAddr, sizeof(saAddr)); + } + else + { + if (socketIsReady) ret = 0; // immediate success, yay! + else + { + // The harder case: the user doesn't want the Connect() call to take more than (so many) microseconds. + // For this, we'll need to go into non-blocking mode and run a SocketMultiplexer loop to get the desired behaviour! + const uint64 deadline = GetRunTime64()+maxConnectTime; + SocketMultiplexer multiplexer; + uint64 now; + while((now = GetRunTime64()) < deadline) + { + multiplexer.RegisterSocketForWriteReady(fd); +#ifdef WIN32 + multiplexer.RegisterSocketForExceptionRaised(fd); +#endif + if (multiplexer.WaitForEvents(deadline) < 0) break; // error out! + else + { +#ifdef WIN32 + if (multiplexer.IsSocketExceptionRaised(fd)) break; // Win32: failed async connect detected! +#endif + if (multiplexer.IsSocketReadyForWrite(fd)) + { + if ((FinalizeAsyncConnect(s) == B_NO_ERROR)&&(SetSocketBlockingEnabled(s, true) == B_NO_ERROR)) ret = 0; + break; + } + } + } + } + } + + if (ret == 0) + { + if ((debugTitle)&&(errorsOnly == false)) Log(MUSCLE_LOG_INFO, "Connected!\n"); + return s; + } + else if (debugTitle) + { + if (errorsOnly) LogTime(MUSCLE_LOG_INFO, "%s: connect() to %s failed!\n", debugTitle, GetConnectString(optDebugHostName?optDebugHostName:ipbuf, port)()); + else Log(MUSCLE_LOG_INFO, "Connection failed!\n"); + } + } + else if (debugTitle) + { + if (errorsOnly) LogTime(MUSCLE_LOG_INFO, "%s: socket() failed!\n", debugTitle); + else Log(MUSCLE_LOG_INFO, "socket() failed!\n"); + } + return ConstSocketRef(); +} + +String GetLocalHostName() +{ + char buf[512]; + return (gethostname(buf, sizeof(buf)) == 0) ? buf : ""; +} + +static bool IsIP4Address(const char * s) +{ + int numDots = 0; + int numDigits = 0; + bool prevWasDot = true; // an initial dot is illegal + while(*s) + { + if (*s == '.') + { + if ((prevWasDot)||(++numDots > 3)) return false; + numDigits = 0; + prevWasDot = true; + } + else + { + if ((prevWasDot)&&(atoi(s) > 255)) return false; + prevWasDot = false; + if ((muscleInRange(*s, '0', '9') == false)||(++numDigits > 3)) return false; + } + s++; + } + return (numDots == 3); +} + +#ifndef MUSCLE_AVOID_IPV6 + +static const char * Inet_NtoP(int af, const void * src, char * dst, muscle_socklen_t size) +{ +#ifdef WIN32 + switch(af) + { + case AF_INET: + { + struct sockaddr_in in; + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + memcpy(&in.sin_addr, src, sizeof(struct in_addr)); + return (getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, size, NULL, 0, NI_NUMERICHOST) == 0) ? dst : NULL; + } + + case AF_INET6: + { + struct sockaddr_in6 in; + memset(&in, 0, sizeof(in)); + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); + return (getnameinfo((struct sockaddr *)&in, sizeof(in), dst, size, NULL, 0, NI_NUMERICHOST) == 0) ? dst : NULL; + } + + default: + return NULL; + } +#else + return inet_ntop(af, src, dst, size); +#endif +} + +static int Inet_PtoN(int af, const char * src, struct in6_addr * dst) +{ +#ifdef WIN32 + struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = af; + hints.ai_flags = AI_NUMERICHOST; + + bool ok = false; // default + struct addrinfo * res; + if (getaddrinfo(src, NULL, &hints, &res) != 0) return -1; + if (res) + { + switch(res->ai_family) + { + case AF_INET: + if (res->ai_addrlen == sizeof(struct sockaddr_in)) + { + struct sockaddr_in * sin = (struct sockaddr_in *) res->ai_addr; + memset(dst, 0, sizeof(*dst)); + // Copy IPv4 bits to the low word of the IPv6 address + memcpy(((char *)dst)+12, &sin->sin_addr.s_addr, sizeof(uint32)); + + // And make it IPv6-mapped-to-IPv4 + memset(((char *)dst)+10, 0xFF, 2); + ok = true; + } + break; + + case AF_INET6: + if (res->ai_addrlen == sizeof(struct sockaddr_in6)) + { + struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *) res->ai_addr; + memcpy(dst, &sin6->sin6_addr, sizeof(*dst)); + ok = true; + } + break; + } + freeaddrinfo(res); + } + return ok ? 1 : -1; +#else + return inet_pton(af, src, dst); +#endif +} + +#endif + +bool IsIPAddress(const char * s) +{ +#ifdef MUSCLE_AVOID_IPV6 + return IsIP4Address(s); +#else + struct in6_addr tmp; + return ((Inet_PtoN(MUSCLE_SOCKET_FAMILY, s, &tmp) > 0)||(Inet_AtoN(s) != invalidIP)||(IsIP4Address(s))); // Inet_AtoN() handles the "@blah" suffixes +#endif +} + +static ip_address _cachedLocalhostAddress = invalidIP; + +static void ExpandLocalhostAddress(ip_address & ipAddress) +{ + if (IsStandardLoopbackDeviceAddress(ipAddress)) + { + ip_address altRet = GetLocalHostIPOverride(); // see if the user manually specified a preferred local address + if (altRet == invalidIP) + { + // If not, try to grab one from the OS + if (_cachedLocalhostAddress == invalidIP) + { + Queue ifs; + (void) GetNetworkInterfaceInfos(ifs, GNII_INCLUDE_ENABLED_INTERFACES|GNII_INCLUDE_NONLOOPBACK_INTERFACES|GNII_INCLUDE_MUSCLE_PREFERRED_INTERFACES); // just to set _cachedLocalhostAddress + } + altRet = _cachedLocalhostAddress; + } + if (altRet != invalidIP) ipAddress = altRet; + } +} + +// This stores the result of a GetHostByName() lookup, along with its expiration time. +class DNSRecord +{ +public: + DNSRecord() : _expirationTime(0) {/* empty */} + DNSRecord(const ip_address & ip, uint64 expTime) : _ipAddress(ip), _expirationTime(expTime) {/* empty */} + + const ip_address & GetIPAddress() const {return _ipAddress;} + uint64 GetExpirationTime() const {return _expirationTime;} + +private: + ip_address _ipAddress; + uint64 _expirationTime; +}; + +static Mutex _hostCacheMutex; // serialize access to the cache data +static uint32 _maxHostCacheSize = 0; // DNS caching is disabled by default +static uint64 _hostCacheEntryLifespan = 0; // how many microseconds before a cache entry is expired +static Hashtable _hostCache; + +void SetHostNameCacheSettings(uint32 maxEntries, uint64 expirationTime) +{ + MutexGuard mg(_hostCacheMutex); + _maxHostCacheSize = expirationTime ? maxEntries : 0; // no sense storing entries that expire instantly + _hostCacheEntryLifespan = expirationTime; + while(_hostCache.GetNumItems() > _maxHostCacheSize) (void) _hostCache.RemoveLast(); +} + +static String GetHostByNameKey(const char * name, bool expandLocalhost) +{ + String ret(name); + ret = ret.ToLowerCase(); + if (expandLocalhost) ret += '!'; // so that a cached result from GetHostByName("foo", false) won't be returned for GetHostByName("foo", true) + return ret; +} + +ip_address GetHostByName(const char * name, bool expandLocalhost) +{ + if (IsIPAddress(name)) + { + // No point in ever caching this result, since Inet_AtoN() is always fast anyway + ip_address ret = Inet_AtoN(name); + if (expandLocalhost) ExpandLocalhostAddress(ret); + return ret; + } + else if (_maxHostCacheSize > 0) + { + String s = GetHostByNameKey(name, expandLocalhost); + MutexGuard mg(_hostCacheMutex); + const DNSRecord * r = _hostCache.Get(s); + if (r) + { + if ((r->GetExpirationTime() == MUSCLE_TIME_NEVER)||(GetRunTime64() < r->GetExpirationTime())) + { + (void) _hostCache.MoveToFront(s); // LRU logic + return r->GetIPAddress(); + } + } + } + + ip_address ret = invalidIP; +#ifdef MUSCLE_AVOID_IPV6 + struct hostent * he = gethostbyname(name); + if (he) ret = ntohl(*((ip_address*)he->h_addr)); +#else + struct addrinfo * result; + struct addrinfo hints; memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // We're not too particular, for now + hints.ai_socktype = SOCK_STREAM; // so we don't get every address twice (once for UDP and once for TCP) + if (getaddrinfo(name, NULL, &hints, &result) == 0) + { + struct addrinfo * next = result; + while(next) + { + switch(next->ai_family) + { + case AF_INET: + ret.SetBits(ntohl(((struct sockaddr_in *) next->ai_addr)->sin_addr.s_addr), 0); // read IPv4 address into low bits of IPv6 address structure + ret.SetLowBits(ret.GetLowBits() | ((uint64)0xFFFF)<<32); // and make it IPv6-mapped (why doesn't AI_V4MAPPED do this?) + break; + + case AF_INET6: + { + struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *) next->ai_addr; + uint32 tmp = sin6->sin6_scope_id; // MacOS/X uses __uint32_t, which isn't quite the same somehow + ret.ReadFromNetworkArray(sin6->sin6_addr.s6_addr, &tmp); + } + break; + } + if (ret == invalidIP) next = next->ai_next; + else break; + } + freeaddrinfo(result); + } +#endif + + if (expandLocalhost) ExpandLocalhostAddress(ret); + + if (_maxHostCacheSize > 0) + { + // Store our result in the cache for later + String s = GetHostByNameKey(name, expandLocalhost); + MutexGuard mg(_hostCacheMutex); + DNSRecord * r = _hostCache.PutAndGet(s, DNSRecord(ret, (_hostCacheEntryLifespan==MUSCLE_TIME_NEVER)?MUSCLE_TIME_NEVER:(GetRunTime64()+_hostCacheEntryLifespan))); + if (r) + { + _hostCache.MoveToFront(s); // LRU logic + while(_hostCache.GetNumItems() > _maxHostCacheSize) (void) _hostCache.RemoveLast(); + } + } + + return ret; +} + +ConstSocketRef ConnectAsync(const ip_address & hostIP, uint16 port, bool & retIsReady) +{ + ConstSocketRef s = CreateMuscleSocket(SOCK_STREAM, GlobalSocketCallback::SOCKET_CALLBACK_CONNECT); + if (s()) + { + if (SetSocketBlockingEnabled(s, false) == B_NO_ERROR) + { + DECLARE_SOCKADDR(saAddr, &hostIP, port); + int result = connect(s.GetFileDescriptor(), (struct sockaddr *) &saAddr, sizeof(saAddr)); +#ifdef WIN32 + bool inProgress = ((result < 0)&&(WSAGetLastError() == WSAEWOULDBLOCK)); +#else + bool inProgress = ((result < 0)&&(errno == EINPROGRESS)); +#endif + if ((result == 0)||(inProgress)) + { + retIsReady = (inProgress == false); + return s; + } + } + } + return ConstSocketRef(); +} + +ip_address GetPeerIPAddress(const ConstSocketRef & sock, bool expandLocalhost, uint16 * optRetPort) +{ + ip_address ipAddress = invalidIP; + int fd = sock.GetFileDescriptor(); + if (fd >= 0) + { + DECLARE_SOCKADDR(saTempAdd, NULL, 0); + muscle_socklen_t length = sizeof(saTempAdd); + if ((getpeername(fd, (struct sockaddr *)&saTempAdd, &length) == 0)&&(GET_SOCKADDR_FAMILY(saTempAdd) == MUSCLE_SOCKET_FAMILY)) + { + GET_SOCKADDR_IP(saTempAdd, ipAddress); + if (optRetPort) *optRetPort = GET_SOCKADDR_PORT(saTempAdd); + if (expandLocalhost) ExpandLocalhostAddress(ipAddress); + } + } + return ipAddress; +} + +/* See the header file for description of what this does */ +status_t CreateConnectedSocketPair(ConstSocketRef & socket1, ConstSocketRef & socket2, bool blocking) +{ + TCHECKPOINT; + +#if defined(USE_SOCKETPAIR) + int temp[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, temp) == 0) + { + socket1 = GetConstSocketRefFromPool(temp[0]); + socket2 = GetConstSocketRefFromPool(temp[1]); + if ((SetSocketBlockingEnabled(socket1, blocking) == B_NO_ERROR)&&(SetSocketBlockingEnabled(socket2, blocking) == B_NO_ERROR)) return B_NO_ERROR; + } +#else + uint16 port; + socket1 = CreateAcceptingSocket(0, 1, &port, localhostIP); + if (socket1()) + { + socket2 = Connect(localhostIP, port); + if (socket2()) + { + ConstSocketRef newfd = Accept(socket1); + if (newfd()) + { + socket1 = newfd; + if ((SetSocketBlockingEnabled(socket1, blocking) == B_NO_ERROR)&&(SetSocketBlockingEnabled(socket2, blocking) == B_NO_ERROR)) + { + (void) SetSocketNaglesAlgorithmEnabled(socket1, false); + (void) SetSocketNaglesAlgorithmEnabled(socket2, false); + return B_NO_ERROR; + } + } + } + } +#endif + + socket1.Reset(); + socket2.Reset(); + return B_ERROR; +} + +status_t SetSocketBlockingEnabled(const ConstSocketRef & sock, bool blocking) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + +#ifdef WIN32 + unsigned long mode = blocking ? 0 : 1; + return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? B_NO_ERROR : B_ERROR; +#else +# ifdef BEOS_OLD_NETSERVER + int b = blocking ? 0 : 1; + return (setsockopt(fd, SOL_SOCKET, SO_NONBLOCK, (const sockopt_arg *) &b, sizeof(b)) == 0) ? B_NO_ERROR : B_ERROR; +# else + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) return B_ERROR; + flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK); + return (fcntl(fd, F_SETFL, flags) == 0) ? B_NO_ERROR : B_ERROR; +# endif +#endif +} + +bool GetSocketBlockingEnabled(const ConstSocketRef & sock) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return false; + +#ifdef WIN32 + // Amazingly enough, Windows has no way to query if a socket is blocking or not. So this function doesn't work under Windows. + LogTime(MUSCLE_LOG_ERROR, "GetSocketBlockingEnabled() not implemented under Win32, returning false for socket %i.\n", fd); + return false; +#else +# ifdef BEOS_OLD_NETSERVER + int b; + int len = sizeof(b); + return (getsockopt(fd, SOL_SOCKET, SO_NONBLOCK, (sockopt_arg *) &b, &len) == 0) ? (b==0) : false; +# else + int flags = fcntl(fd, F_GETFL, 0); + return ((flags >= 0)&&((flags & O_NONBLOCK) == 0)); +# endif +#endif +} + +status_t SetUDPSocketBroadcastEnabled(const ConstSocketRef & sock, bool broadcast) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + int val = (broadcast ? 1 : 0); +#ifdef BEOS_OLD_NETSERVER + return (setsockopt(fd, SOL_SOCKET, INADDR_BROADCAST, (const sockopt_arg *) &val, sizeof(val)) == 0) ? B_NO_ERROR : B_ERROR; +#else + return (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (const sockopt_arg *) &val, sizeof(val)) == 0) ? B_NO_ERROR : B_ERROR; +#endif +} + +bool GetUDPSocketBroadcastEnabled(const ConstSocketRef & sock) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return false; + + int val; + socklen_t len = sizeof(val); +#ifdef BEOS_OLD_NETSERVER + return (getsockopt(fd, SOL_SOCKET, INADDR_BROADCAST, (sockopt_arg *) &val, &len) == 0) ? (val != 0) : false; +#else + return (getsockopt(fd, SOL_SOCKET, SO_BROADCAST, (sockopt_arg *) &val, &len) == 0) ? (val != 0) : false; +#endif +} + +status_t SetSocketNaglesAlgorithmEnabled(const ConstSocketRef & sock, bool enabled) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + +#ifdef BEOS_OLD_NETSERVER + (void) enabled; // prevent 'unused var' warning + return B_ERROR; // old networking stack doesn't support this flag +#else + int enableNoDelay = enabled ? 0 : 1; + return (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const sockopt_arg *) &enableNoDelay, sizeof(enableNoDelay)) == 0) ? B_NO_ERROR : B_ERROR; +#endif +} + +bool GetSocketNaglesAlgorithmEnabled(const ConstSocketRef & sock) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return false; + +#ifdef BEOS_OLD_NETSERVER + return true; // old networking stack doesn't support this flag +#else + int enableNoDelay; + socklen_t len = sizeof(enableNoDelay); + return (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (sockopt_arg *) &enableNoDelay, &len) == 0) ? (enableNoDelay == 0) : false; +#endif +} + +status_t FinalizeAsyncConnect(const ConstSocketRef & sock) +{ + TCHECKPOINT; + + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + +#if defined(BEOS_OLD_NETSERVER) + // net_server and BONE behave COMPLETELY differently as far as finalizing async connects + // go... so we have to do this horrible hack where we figure out whether we're in a + // true net_server or BONE environment at runtime. Pretend you didn't see any of this, + // you'll sleep better. :^P + static bool userIsRunningBone = false; + static bool haveDoneRuntimeCheck = false; + if (haveDoneRuntimeCheck == false) + { + userIsRunningBone = be_roster ? (!be_roster->IsRunning("application/x-vnd.Be-NETS")) : BEntry("/boot/beos/system/lib/libsocket.so").Exists(); + haveDoneRuntimeCheck = true; // only do this check once, it's rather expensive! + } + if (userIsRunningBone) + { + char junk; + return (send_ignore_eintr(fd, &junk, 0, 0L) == 0) ? B_NO_ERROR : B_ERROR; + } + else + { + // net_server just HAS to do things differently from everyone else :^P + struct sockaddr_in junk; + memset(&junk, 0, sizeof(junk)); + return (connect(fd, (struct sockaddr *) &junk, sizeof(junk)) == 0) ? B_NO_ERROR : B_ERROR; + } +#elif defined(__FreeBSD__) || defined(BSD) + // Nathan Whitehorn reports that send() doesn't do this trick under FreeBSD 7, + // so for BSD we'll call getpeername() instead. -- jaf + struct sockaddr_in junk; + socklen_t length = sizeof(junk); + memset(&junk, 0, sizeof(junk)); + return (getpeername(fd, (struct sockaddr *)&junk, &length) == 0) ? B_NO_ERROR : B_ERROR; +#else + // For most platforms, the code below is all we need + char junk; + return (send_ignore_eintr(fd, &junk, 0, 0L) == 0) ? B_NO_ERROR : B_ERROR; +#endif +} + +static status_t SetSocketBufferSizeAux(const ConstSocketRef & sock, uint32 numBytes, int optionName) +{ +#ifdef BEOS_OLD_NETSERVER + (void) sock; + (void) numBytes; + (void) optionName; + return B_ERROR; // not supported! +#else + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + int iSize = (int) numBytes; + return (setsockopt(fd, SOL_SOCKET, optionName, (const sockopt_arg *) &iSize, sizeof(iSize)) == 0) ? B_NO_ERROR : B_ERROR; +#endif +} +status_t SetSocketSendBufferSize( const ConstSocketRef & sock, uint32 sendBufferSizeBytes) {return SetSocketBufferSizeAux(sock, sendBufferSizeBytes, SO_SNDBUF);} +status_t SetSocketReceiveBufferSize(const ConstSocketRef & sock, uint32 recvBufferSizeBytes) {return SetSocketBufferSizeAux(sock, recvBufferSizeBytes, SO_RCVBUF);} + +static int32 GetSocketBufferSizeAux(const ConstSocketRef & sock, int optionName) +{ +#ifdef BEOS_OLD_NETSERVER + (void) sock; + (void) optionName; + return -1; // not supported! +#else + int fd = sock.GetFileDescriptor(); + if (fd < 0) return -1; + + int iSize; + socklen_t len = sizeof(iSize); + return (getsockopt(fd, SOL_SOCKET, optionName, (sockopt_arg *) &iSize, &len) == 0) ? (int32)iSize : -1; +#endif +} +int32 GetSocketSendBufferSize( const ConstSocketRef & sock) {return GetSocketBufferSizeAux(sock, SO_SNDBUF);} +int32 GetSocketReceiveBufferSize(const ConstSocketRef & sock) {return GetSocketBufferSizeAux(sock, SO_RCVBUF);} + +NetworkInterfaceInfo :: NetworkInterfaceInfo() : _ip(invalidIP), _netmask(invalidIP), _broadcastIP(invalidIP), _enabled(false), _copper(false) +{ + // empty +} + +NetworkInterfaceInfo :: NetworkInterfaceInfo(const String &name, const String & desc, const ip_address & ip, const ip_address & netmask, const ip_address & broadcastIP, bool enabled, bool copper) : _name(name), _desc(desc), _ip(ip), _netmask(netmask), _broadcastIP(broadcastIP), _enabled(enabled), _copper(copper) +{ + // empty +} + +String NetworkInterfaceInfo :: ToString() const +{ + return String("Name=[%1] Description=[%2] IP=[%3] Netmask=[%4] Broadcast=[%5] Enabled=%6 Copper=%7").Arg(_name).Arg(_desc).Arg(Inet_NtoA(_ip)).Arg(Inet_NtoA(_netmask)).Arg(Inet_NtoA(_broadcastIP)).Arg(_enabled).Arg(_copper); +} + +uint32 NetworkInterfaceInfo :: HashCode() const +{ + return _name.HashCode() + _desc.HashCode() + GetHashCodeForIPAddress(_ip) + GetHashCodeForIPAddress(_netmask) + GetHashCodeForIPAddress(_broadcastIP) + _enabled + _copper; +} + +#if defined(USE_GETIFADDRS) || defined(WIN32) +static ip_address SockAddrToIPAddr(const struct sockaddr * a) +{ + if (a) + { + switch(a->sa_family) + { + case AF_INET: return ip_address(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)); + +#ifndef MUSCLE_AVOID_IPV6 + case AF_INET6: + { + struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *) a; + ip_address ret; + uint32 tmp = sin6->sin6_scope_id; // MacOS/X uses __uint32_t, which isn't quite the same somehow + ret.ReadFromNetworkArray(sin6->sin6_addr.s6_addr, &tmp); + return ret; + } +#endif + } + } + return invalidIP; +} +#endif + +bool IsIPv4Address(const ip_address & ip) +{ +#ifdef MUSCLE_AVOID_IPV6 + (void) ip; + return true; +#else + if ((ip == invalidIP)||(ip == localhostIP)) return false; // :: and ::1 are considered to be IPv6 addresses + + if (ip.GetHighBits() != 0) return false; + uint64 lb = (ip.GetLowBits()>>32); + return ((lb == 0)||(lb == 0xFFFF)); // 32-bit and IPv4-mapped, respectively +#endif +} + +bool IsValidAddress(const ip_address & ip) +{ +#ifdef MUSCLE_AVOID_IPV6 + return (ip != 0); +#else + return ((ip.GetHighBits() != 0)||(ip.GetLowBits() != 0)); +#endif +} + +bool IsMulticastIPAddress(const ip_address & ip) +{ +#ifndef MUSCLE_AVOID_IPV6 + // In IPv6, any address that starts with 0xFF is a multicast address + if (((ip.GetHighBits() >> 56)&((uint64)0xFF)) == 0xFF) return true; + + const uint64 mapBits = (((uint64)0xFFFF)<<32); + if ((ip.GetHighBits() == 0)&&((ip.GetLowBits() & mapBits) == mapBits)) + { + ip_address temp = ip; temp.SetLowBits(temp.GetLowBits() & ~mapBits); + return IsMulticastIPAddress(temp); // don't count the map-to-IPv6 bits when determining multicast-ness + } +#endif + + // check for IPv4 address-ness + ip_address minMulticastAddress = Inet_AtoN("224.0.0.0"); + ip_address maxMulticastAddress = Inet_AtoN("239.255.255.255"); + return muscleInRange(ip, minMulticastAddress, maxMulticastAddress); +} + +bool IsStandardLoopbackDeviceAddress(const ip_address & ip) +{ +#ifdef MUSCLE_AVOID_IPV6 + return (ip == localhostIP); +#else + // fe80::1 is another name for localhostIP in IPv6 land + static const ip_address localhostIP_linkScope(localhostIP.GetLowBits(), ((uint64)0xFE80)<<48); + return ((ip.EqualsIgnoreInterfaceIndex(localhostIP))||(ip.EqualsIgnoreInterfaceIndex(localhostIP_IPv4))||(ip.EqualsIgnoreInterfaceIndex(localhostIP_linkScope))); +#endif +} + +static bool IsGNIIBitMatch(const ip_address & ip, bool isInterfaceEnabled, uint32 includeBits) +{ + if (((includeBits & GNII_INCLUDE_ENABLED_INTERFACES) == 0)&&( isInterfaceEnabled)) return false; + if (((includeBits & GNII_INCLUDE_DISABLED_INTERFACES) == 0)&&(!isInterfaceEnabled)) return false; + + bool isLoopback = IsStandardLoopbackDeviceAddress(ip); + if (((includeBits & GNII_INCLUDE_LOOPBACK_INTERFACES) == 0)&&( isLoopback)) return false; + if (((includeBits & GNII_INCLUDE_NONLOOPBACK_INTERFACES) == 0)&&(!isLoopback)) return false; + + bool isIPv4 = IsIPv4Address(ip); + if (( isIPv4)&&((includeBits & GNII_INCLUDE_IPV4_INTERFACES) == 0)) return false; + if ((!isIPv4)&&((includeBits & GNII_INCLUDE_IPV6_INTERFACES) == 0)) return false; + + return true; +} + +status_t GetNetworkInterfaceInfos(Queue & results, uint32 includeBits) +{ + uint32 origResultsSize = results.GetNumItems(); + status_t ret = B_ERROR; + +#if defined(USE_GETIFADDRS) + ///////////////////////////////////////////////////////////////////////////////////////////// + // "Apparently, getifaddrs() is gaining a lot of traction at becoming the One True Standard + // way of getting at interface info, so you're likely to find support for it on most modern + // Unix-like systems, at least..." + // + // http://www.developerweb.net/forum/showthread.php?t=5085 + ///////////////////////////////////////////////////////////////////////////////////////////// + + struct ifaddrs * ifap; + + if (getifaddrs(&ifap) == 0) + { + ret = B_NO_ERROR; + { + struct ifaddrs * p = ifap; + while(p) + { + ip_address unicastIP = SockAddrToIPAddr(p->ifa_addr); + ip_address netmask = SockAddrToIPAddr(p->ifa_netmask); + ip_address broadcastIP = SockAddrToIPAddr(p->ifa_broadaddr); + bool isEnabled = ((p->ifa_flags & IFF_UP) != 0); + bool hasCopper = ((p->ifa_flags & IFF_RUNNING) != 0); + if ((unicastIP != invalidIP)&&(IsGNIIBitMatch(unicastIP, isEnabled, includeBits))) + { +#ifndef MUSCLE_AVOID_IPV6 + unicastIP.SetInterfaceIndex(if_nametoindex(p->ifa_name)); // so the user can find out; it will be ignore by the TCP stack +#endif + if (results.AddTail(NetworkInterfaceInfo(p->ifa_name, "", unicastIP, netmask, broadcastIP, isEnabled, hasCopper)) == B_NO_ERROR) + { + if (_cachedLocalhostAddress == invalidIP) _cachedLocalhostAddress = unicastIP; + } + else + { + ret = B_ERROR; // out of memory!? + break; + } + } + p = p->ifa_next; + } + } + freeifaddrs(ifap); + } +#elif defined(WIN32) + // IPv6 implementation, adapted from + // http://msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx + // + SOCKET s = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0); + if (s == INVALID_SOCKET) return B_ERROR; + + INTERFACE_INFO localAddrs[64]; // Assume there will be no more than 64 IP interfaces + DWORD bytesReturned; + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, &localAddrs, sizeof(localAddrs), &bytesReturned, NULL, NULL) == SOCKET_ERROR) + { + closesocket(s); + return B_ERROR; + } + else closesocket(s); + + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + ULONG outBufLen = 0; + while(ret == B_ERROR) // keep going until we succeeded (on failure we'll return directly) + { + DWORD flags = GAA_FLAG_INCLUDE_PREFIX|GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER; + switch(GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen)) + { + case ERROR_BUFFER_OVERFLOW: + if (pAddresses) muscleFree(pAddresses); + + // Add some breathing room so that if the required size + // grows during the time period between the two calls to + // GetAdaptersAddresses(), we won't have to reallocate again. + outBufLen *= 2; + + pAddresses = (IP_ADAPTER_ADDRESSES *) muscleAlloc(outBufLen); + if (pAddresses == NULL) + { + WARN_OUT_OF_MEMORY; + return B_ERROR; + } + break; + + case ERROR_SUCCESS: + for (PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses; pCurrAddresses; ((pCurrAddresses = pCurrAddresses->Next) != NULL)) + { + PIP_ADAPTER_UNICAST_ADDRESS ua = pCurrAddresses->FirstUnicastAddress; + while(ua) + { + ip_address unicastIP = SockAddrToIPAddr(ua->Address.lpSockaddr); + bool isEnabled = true; // for now. TODO: See if GetAdaptersAddresses() reports disabled interfaces + if (IsGNIIBitMatch(unicastIP, isEnabled, includeBits)) + { + ip_address broadcastIP, netmask; + uint32 numLocalAddrs = (bytesReturned/sizeof(INTERFACE_INFO)); + for (int i=0; iIpv6IfIndex); // so the user can find out; it will be ignore by the TCP stack +#endif + + char outBuf[512]; + if (WideCharToMultiByte(CP_UTF8, 0, pCurrAddresses->Description, -1, outBuf, sizeof(outBuf), NULL, NULL) <= 0) outBuf[0] = '\0'; + + if (results.AddTail(NetworkInterfaceInfo(pCurrAddresses->AdapterName, outBuf, unicastIP, netmask, broadcastIP, isEnabled, false)) == B_NO_ERROR) + { + if (_cachedLocalhostAddress == invalidIP) _cachedLocalhostAddress = unicastIP; + } + else + { + if (pAddresses) muscleFree(pAddresses); + return B_ERROR; + } + } + ua = ua->Next; + } + } + if (pAddresses) muscleFree(pAddresses); + ret = B_NO_ERROR; // this will drop us out of the loop + break; + + default: + if (pAddresses) muscleFree(pAddresses); + return B_ERROR; + } + } +#else + (void) results; // for other OS's, this function isn't implemented. +#endif + + return ((ret == B_NO_ERROR)&&(results.GetNumItems() == origResultsSize)&&(includeBits & GNII_INCLUDE_LOOPBACK_INTERFACES_ONLY_AS_LAST_RESORT)) ? GetNetworkInterfaceInfos(results, (includeBits|GNII_INCLUDE_LOOPBACK_INTERFACES)&~(GNII_INCLUDE_LOOPBACK_INTERFACES_ONLY_AS_LAST_RESORT)) : ret; +} + +status_t GetNetworkInterfaceAddresses(Queue & results, uint32 includeBits) +{ + Queue infos; + if ((GetNetworkInterfaceInfos(infos, includeBits) != B_NO_ERROR)||(results.EnsureSize(infos.GetNumItems()) != B_NO_ERROR)) return B_ERROR; + + for (uint32 i=0; i>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF); +} + +void Inet_NtoA(const ip_address & addr, char * ipbuf, bool preferIPv4) +{ +#ifdef MUSCLE_AVOID_IPV6 + (void) preferIPv4; + Inet4_NtoA(addr, ipbuf); +#else + if ((preferIPv4)&&(IsIPv4Address(addr))) Inet4_NtoA(addr.GetLowBits()&0xFFFFFFFF, ipbuf); + else + { + uint32 iIdx = 0; + uint8 ip6[16]; addr.WriteToNetworkArray(ip6, &iIdx); + if (Inet_NtoP(AF_INET6, (const in6_addr *) ip6, ipbuf, 46) != NULL) + { + if (iIdx > 0) + { + // Add the index suffix + char buf[32]; sprintf(buf, "@" UINT32_FORMAT_SPEC, iIdx); + strcat(ipbuf, buf); + } + } + else ipbuf[0] = '\0'; + } +#endif +} + +String Inet_NtoA(const ip_address & ipAddress, bool preferIPv4) +{ + char buf[64]; + Inet_NtoA(ipAddress, buf, preferIPv4); + return buf; +} + +static uint32 Inet4_AtoN(const char * buf) +{ + // net_server inexplicably doesn't have this function; so I'll just fake it + uint32 ret = 0; + int shift = 24; // fill out the MSB first + bool startQuad = true; + while((shift >= 0)&&(*buf)) + { + if (startQuad) + { + uint8 quad = (uint8) atoi(buf); + ret |= (((uint32)quad) << shift); + shift -= 8; + } + startQuad = (*buf == '.'); + buf++; + } + return ret; +} + +#ifndef MUSCLE_AVOID_IPV6 +static ip_address Inet6_AtoN(const char * buf, uint32 iIdx) +{ + struct in6_addr dst; + if (Inet_PtoN(AF_INET6, buf, &dst) > 0) + { + ip_address ret; + ret.ReadFromNetworkArray(dst.s6_addr, &iIdx); + + return ret; + } + else return (IsIP4Address(buf)) ? ip_address(Inet4_AtoN(buf), 0) : invalidIP; +} +#endif + +ip_address Inet_AtoN(const char * buf) +{ +#ifdef MUSCLE_AVOID_IPV6 + return Inet4_AtoN(buf); +#else + const char * at = strchr(buf, '@'); + if (at) + { + // Gah... Inet_PtoN() won't accept the @idx suffix, so + // I have to chop that out and parse it separately. What a pain. + uint32 charsBeforeAt = (uint32)(at-buf); + char * tmp = newnothrow_array(char, 1+charsBeforeAt); + if (tmp) + { + memcpy(tmp, buf, charsBeforeAt); + tmp[charsBeforeAt] = '\0'; + ip_address ret = Inet6_AtoN(tmp, atoi(at+1)); + delete [] tmp; + return ret; + } + else + { + WARN_OUT_OF_MEMORY; + return invalidIP; + } + } + else return Inet6_AtoN(buf, 0); +#endif +} + +static ip_address ResolveIP(const String & s, bool allowDNSLookups) +{ + return allowDNSLookups ? GetHostByName(s()) : Inet_AtoN(s()); +} + +void IPAddressAndPort :: SetFromString(const String & s, uint16 defaultPort, bool allowDNSLookups) +{ +#ifndef MUSCLE_AVOID_IPV6 + int32 rBracket = s.StartsWith('[') ? s.IndexOf(']') : -1; + if (rBracket >= 0) + { + // If there are brackets, they are assumed to surround the address part, e.g. "[::1]:9999" + _ip = ResolveIP(s.Substring(1,rBracket), allowDNSLookups); + + int32 colIdx = s.IndexOf(':', rBracket+1); + _port = ((colIdx >= 0)&&(muscleInRange(s()[colIdx+1], '0', '9'))) ? atoi(s()+colIdx+1) : defaultPort; + return; + } + else if (s.GetNumInstancesOf(':') != 1) // I assume IPv6-style address strings never have exactly one colon in them + { + _ip = ResolveIP(s, allowDNSLookups); + _port = defaultPort; + return; + } +#endif + + // Old style IPv4 parsing (e.g. "192.168.0.1" or "192.168.0.1:2960") + int colIdx = s.IndexOf(':'); + if ((colIdx >= 0)&&(muscleInRange(s()[colIdx+1], '0', '9'))) + { + _ip = ResolveIP(s.Substring(0, colIdx), allowDNSLookups); + _port = atoi(s()+colIdx+1); + } + else + { + _ip = ResolveIP(s, allowDNSLookups); + _port = defaultPort; + } +} + +String IPAddressAndPort :: ToString(bool includePort, bool preferIPv4Style) const +{ + String s = Inet_NtoA(_ip, preferIPv4Style); + + if ((includePort)&&(_port > 0)) + { + char buf[128]; +#ifdef MUSCLE_AVOID_IPV6 + bool useIPv4Style = true; +#else + bool useIPv4Style = ((preferIPv4Style)&&(IsIPv4Address(_ip))); // FogBugz #8985 +#endif + if (useIPv4Style) sprintf(buf, "%s:%u", s(), _port); + else sprintf(buf, "[%s]:%u", s(), _port); + return buf; + } + else return s; +} + +// defined here to avoid having to pull in MiscUtilityFunctions.cpp for everything +String GetConnectString(const String & host, uint16 port) +{ +#ifdef MUSCLE_AVOID_IPV6 + char buf[32]; sprintf(buf, ":%u", port); + return host + buf; +#else + char buf[32]; sprintf(buf, "]:%u", port); + return host.Prepend("[") + buf; +#endif +} + +static ip_address _customLocalhostIP = invalidIP; // disabled by default +void SetLocalHostIPOverride(const ip_address & ip) {_customLocalhostIP = ip;} +ip_address GetLocalHostIPOverride() {return _customLocalhostIP;} + +#ifdef MUSCLE_ENABLE_KEEPALIVE_API + +static inline int KeepAliveMicrosToSeconds(uint64 micros) {return ((micros+(MICROS_PER_SECOND-1))/MICROS_PER_SECOND);} // round up to the nearest second! +static inline uint64 KeepAliveSecondsToMicros(int second) {return (second*MICROS_PER_SECOND);} + +status_t SetSocketKeepAliveBehavior(const ConstSocketRef & sock, uint32 maxProbeCount, uint64 idleTime, uint64 retransmitTime) +{ +#ifdef __linux__ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + int arg = KeepAliveMicrosToSeconds(idleTime); + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &arg, sizeof(arg)) != 0) return B_ERROR; + + arg = (int) maxProbeCount; + if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &arg, sizeof(arg)) != 0) return B_ERROR; + + arg = KeepAliveMicrosToSeconds(retransmitTime); + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &arg, sizeof(arg)) != 0) return B_ERROR; + + arg = (maxProbeCount>0); // true iff we want keepalive enabled + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const sockopt_arg *) &arg, sizeof(arg)) != 0) return B_ERROR; + + return B_NO_ERROR; +#else + // Other OS's don't support these calls, AFAIK + (void) sock; + (void) maxProbeCount; + (void) idleTime; + (void) retransmitTime; + return B_ERROR; +#endif +} + +status_t GetSocketKeepAliveBehavior(const ConstSocketRef & sock, uint32 * retMaxProbeCount, uint64 * retIdleTime, uint64 * retRetransmitTime) +{ +#ifdef __linux__ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + int val; + muscle_socklen_t valLen; + if (retMaxProbeCount) + { + *retMaxProbeCount = 0; + valLen = sizeof(val); if (getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (sockopt_arg *) &val, &valLen) != 0) return B_ERROR; + if (val != 0) // we only set *retMaxProbeCount if SO_KEEPALIVE is enabled, otherwise we return 0 to indicate no-keepalive + { + valLen = sizeof(val); if (getsockopt(fd, SOL_TCP, TCP_KEEPCNT, (sockopt_arg *) &val, &valLen) != 0) return B_ERROR; + *retMaxProbeCount = val; + } + } + + if (retIdleTime) + { + valLen = sizeof(val); if (getsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (sockopt_arg *) &val, &valLen) != 0) return B_ERROR; + *retIdleTime = KeepAliveSecondsToMicros(val); + } + + if (retRetransmitTime) + { + valLen = sizeof(val); if (getsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (sockopt_arg *) &val, &valLen) != 0) return B_ERROR; + *retRetransmitTime = KeepAliveSecondsToMicros(val); + } + + return B_NO_ERROR; +#else + // Other OS's don't support these calls, AFAIK + (void) sock; + (void) retMaxProbeCount; + (void) retIdleTime; + (void) retRetransmitTime; + return B_ERROR; +#endif +} + +#endif + +#ifndef MUSCLE_AVOID_MULTICAST_API + +status_t SetSocketMulticastToSelf(const ConstSocketRef & sock, bool multicastToSelf) +{ + uint8 toSelf = (uint8) multicastToSelf; + int fd = sock.GetFileDescriptor(); +#ifdef MUSCLE_AVOID_IPV6 + return ((fd>=0)&&(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const sockopt_arg *) &toSelf, sizeof(toSelf)) == 0)) ? B_NO_ERROR : B_ERROR; +#else + return ((fd>=0)&&(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const sockopt_arg *) &toSelf, sizeof(toSelf)) == 0)) ? B_NO_ERROR : B_ERROR; +#endif +} + +bool GetSocketMulticastToSelf(const ConstSocketRef & sock) +{ + uint8 toSelf; + muscle_socklen_t size = sizeof(toSelf); + int fd = sock.GetFileDescriptor(); +#ifdef MUSCLE_AVOID_IPV6 + return ((fd>=0)&&(getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (sockopt_arg *) &toSelf, &size) == 0)&&(size == sizeof(toSelf))&&(toSelf)); +#else + return ((fd>=0)&&(getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (sockopt_arg *) &toSelf, &size) == 0)&&(size == sizeof(toSelf))&&(toSelf)); +#endif +} + +status_t SetSocketMulticastTimeToLive(const ConstSocketRef & sock, uint8 ttl) +{ + int fd = sock.GetFileDescriptor(); + int ttl_arg = (int) ttl; // MacOS/X won't take a uint8 +#ifdef MUSCLE_AVOID_IPV6 + return ((fd>=0)&&(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (const sockopt_arg *) &ttl_arg, sizeof(ttl_arg)) == 0)) ? B_NO_ERROR : B_ERROR; +#else + return ((fd>=0)&&(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const sockopt_arg *) &ttl_arg, sizeof(ttl_arg)) == 0)) ? B_NO_ERROR : B_ERROR; +#endif +} + +uint8 GetSocketMulticastTimeToLive(const ConstSocketRef & sock) +{ + int ttl = 0; + muscle_socklen_t size = sizeof(ttl); + int fd = sock.GetFileDescriptor(); +#ifdef MUSCLE_AVOID_IPV6 + return ((fd>=0)&&(getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (sockopt_arg *) &ttl, &size) == 0)&&(size == sizeof(ttl))) ? ttl : 0; +#else + return ((fd>=0)&&(getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (sockopt_arg *) &ttl, &size) == 0)&&(size == sizeof(ttl))) ? ttl : 0; +#endif +} + +#ifdef MUSCLE_AVOID_IPV6 + +// IPv4 multicast implementation + +status_t SetSocketMulticastSendInterfaceAddress(const ConstSocketRef & sock, const ip_address & address) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + struct in_addr localInterface; memset(&localInterface, 0, sizeof(localInterface)); + localInterface.s_addr = htonl(address); + return (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (const sockopt_arg *) &localInterface, sizeof(localInterface)) == 0) ? B_NO_ERROR : B_ERROR; +} + +ip_address GetSocketMulticastSendInterfaceAddress(const ConstSocketRef & sock) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return invalidIP; + + struct in_addr localInterface; memset(&localInterface, 0, sizeof(localInterface)); + muscle_socklen_t len = sizeof(localInterface); + return ((getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (sockopt_arg *) &localInterface, &len) == 0)&&(len == sizeof(localInterface))) ? ntohl(localInterface.s_addr) : invalidIP; +} + +status_t AddSocketToMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress, const ip_address & localInterfaceAddress) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + struct ip_mreq req; memset(&req, 0, sizeof(req)); + req.imr_multiaddr.s_addr = htonl(groupAddress); + req.imr_interface.s_addr = htonl(localInterfaceAddress); + return (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const sockopt_arg *) &req, sizeof(req)) == 0) ? B_NO_ERROR : B_ERROR; +} + +status_t RemoveSocketFromMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress, const ip_address & localInterfaceAddress) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + struct ip_mreq req; memset(&req, 0, sizeof(req)); + req.imr_multiaddr.s_addr = htonl(groupAddress); + req.imr_interface.s_addr = htonl(localInterfaceAddress); + return (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const sockopt_arg *) &req, sizeof(req)) == 0) ? B_NO_ERROR : B_ERROR; +} + +#else // end IPv4 multicast, begin IPv6 multicast + +// Apparently not everyone has settled on the same IPv6 multicast constant names.... :^P +#ifndef IPV6_ADD_MEMBERSHIP +# define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif +#ifndef IPV6_DROP_MEMBERSHIP +# define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif + +status_t SetSocketMulticastSendInterfaceIndex(const ConstSocketRef & sock, uint32 interfaceIndex) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + int idx = interfaceIndex; + return (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const sockopt_arg *) &idx, sizeof(idx)) == 0) ? B_NO_ERROR : B_ERROR; +} + +int32 GetSocketMulticastSendInterfaceIndex(const ConstSocketRef & sock) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return -1; + + int idx = 0; + socklen_t len = sizeof(idx); + return ((getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (sockopt_arg *) &idx, &len) == 0)&&(len == sizeof(idx))) ? idx : -1; +} + +status_t AddSocketToMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + struct ipv6_mreq req; memset(&req, 0, sizeof(req)); + uint32 interfaceIdx; + groupAddress.WriteToNetworkArray((uint8*)(&req.ipv6mr_multiaddr), &interfaceIdx); + req.ipv6mr_interface = interfaceIdx; + return (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const sockopt_arg *) &req, sizeof(req)) == 0) ? B_NO_ERROR : B_ERROR; +} + +status_t RemoveSocketFromMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress) +{ + int fd = sock.GetFileDescriptor(); + if (fd < 0) return B_ERROR; + + struct ipv6_mreq req; memset(&req, 0, sizeof(req)); + uint32 interfaceIdx; + groupAddress.WriteToNetworkArray((uint8*)(&req.ipv6mr_multiaddr), &interfaceIdx); + req.ipv6mr_interface = interfaceIdx; + return (setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, (const sockopt_arg *) &req, sizeof(req)) == 0) ? B_NO_ERROR : B_ERROR; +} + +#endif // IPv6 multicast + +#endif // !MUSCLE_AVOID_MULTICAST_API + +}; // end namespace muscle diff --git a/util/NetworkUtilityFunctions.h b/util/NetworkUtilityFunctions.h new file mode 100644 index 00000000..e95ecb1b --- /dev/null +++ b/util/NetworkUtilityFunctions.h @@ -0,0 +1,1083 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleNetworkUtilityFunctions_h +#define MuscleNetworkUtilityFunctions_h + +#include "support/MuscleSupport.h" +#include "util/CountedObject.h" +#include "util/Queue.h" +#include "util/Socket.h" +#include "util/String.h" +#include "util/TimeUtilityFunctions.h" + +// These includes are here so that people can use select() without having to #include the proper +// things themselves all the time. +#ifndef WIN32 +# include +# include +#endif + +#ifdef BONE +# include // sikosis at bebits.com says this is necessary... hmm. +#endif + +namespace muscle { + +/** @defgroup networkutilityfunctions The NetworkUtilityFunctions function API + * These functions are all defined in NetworkUtilityFunctions(.cpp,.h), and are stand-alone + * functions that do various network-related tasks + * @{ + */ + +// The total maximum size of a packet (including all headers and user data) that can be sent +// over a network without causing packet fragmentation +#ifndef MUSCLE_EXPECTED_MTU_SIZE_BYTES +# define MUSCLE_EXPECTED_MTU_SIZE_BYTES 1500 // Default to the MTU that good old Ethernet uses +#endif + +// Some extra room, just in case some router or VLAN headers need to be added also +#ifndef MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES +# define MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES 64 +#endif + +#ifdef MUSCLE_AVOID_IPV6 +# define MUSCLE_IP_HEADER_SIZE_BYTES 24 // IPv4: assumes worst-case scenario (i.e. that the options field is included) +#else +# define MUSCLE_IP_HEADER_SIZE_BYTES 40 // IPv6: assuming no additional header chunks, of course +#endif + +#define MUSCLE_UDP_HEADER_SIZE_BYTES (4*sizeof(uint16)) // (sourceport, destport, length, checksum) + +#ifndef MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET +# define MUSCLE_MAX_PAYLOAD_BYTES_PER_UDP_ETHERNET_PACKET (MUSCLE_EXPECTED_MTU_SIZE_BYTES-(MUSCLE_IP_HEADER_SIZE_BYTES+MUSCLE_UDP_HEADER_SIZE_BYTES+MUSCLE_POTENTIAL_EXTRA_HEADERS_SIZE_BYTES)) +#endif + +#ifdef MUSCLE_AVOID_IPV6 + +/** IPv4 addressing support */ +typedef uint32 ip_address; + +/** Given an IP address, returns a 32-bit hash code for it. IPv4 implementation. */ +inline uint32 GetHashCodeForIPAddress(const ip_address & ip) {return CalculateHashCode(ip);} + +/** IPv4 Numeric representation of a all-zeroes invalid/guard address */ +const ip_address invalidIP = 0; + +/** IPv4 Numeric representation of localhost (127.0.0.1), for convenience */ +const ip_address localhostIP = ((((uint32)127)<<24)|((uint32)1)); + +/** IPv4 Numeric representation of broadcast (255.255.255.255), for convenience */ +const ip_address broadcastIP = ((uint32)-1); + +#else + +/* IPv6 addressing support */ + +/** This function sets a global flag that indicates whether or not automatic translation of + * IPv4-compatible IPv6 addresses (e.g. ::192.168.0.1) to IPv4-mapped IPv6 addresses (e.g. ::ffff:192.168.0.1) + * should be enabled. This flag is set to true by default; if you want to set it to false you + * would typically do so only at the top of main() and then not set it again. + * This automatic remapping is useful if you want your software to handle both IPv4 and IPv6 + * traffic without having to open separate IPv4 and IPv6 sockets and without having to do any + * special modification of IPv4 addresses. The only time you'd need to set this flag to false + * is if you need to run IPv6 traffic over non-IPv6-aware routers, in which case you'd need + * to use the ::x.y.z.w address space for actual IPv6 traffic instead. + * @param enabled True if the transparent remapping should be enabled (the default state), or false to disable it. + */ +void SetAutomaticIPv4AddressMappingEnabled(bool enabled); + +/** Returns true iff automatic translation of IPv4-compatible IPv6 addresses to IPv4-mapped IPv6 address is enabled. + * @see SetAutomaticIPv4AddressMappingEnabled() for details. + */ +bool GetAutomaticIPv4AddressMappingEnabled(); + +/** This class represents an IPv6 network address, including the 128-bit IP + * address and the interface index field (necessary for connecting to link-local addresses) + */ +class ip_address +{ +public: + ip_address(uint64 lowBits = 0, uint64 highBits = 0, uint32 interfaceIndex = 0) : _lowBits(lowBits), _highBits(highBits), _interfaceIndex(interfaceIndex) {/* empty */} + ip_address(const ip_address & rhs) : _lowBits(rhs._lowBits), _highBits(rhs._highBits), _interfaceIndex(rhs._interfaceIndex) {/* empty */} + + ip_address & operator = (const ip_address & rhs) {_lowBits = rhs._lowBits; _highBits = rhs._highBits; _interfaceIndex = rhs._interfaceIndex; return *this;} + + bool EqualsIgnoreInterfaceIndex(const ip_address & rhs) const {return ((_lowBits == rhs._lowBits)&&(_highBits == rhs._highBits));} + bool operator == (const ip_address & rhs) const {return ((EqualsIgnoreInterfaceIndex(rhs))&&(_interfaceIndex == rhs._interfaceIndex));} + bool operator != (const ip_address & rhs) const {return !(*this == rhs);} + + bool operator < (const ip_address & rhs) const + { + if (_highBits < rhs._highBits) return true; + if (_highBits > rhs._highBits) return false; + if (_lowBits < rhs._lowBits) return true; + if (_lowBits > rhs._lowBits) return false; + return (_interfaceIndex < rhs._interfaceIndex); + } + + bool operator > (const ip_address & rhs) const + { + if (_highBits < rhs._highBits) return false; + if (_highBits > rhs._highBits) return true; + if (_lowBits < rhs._lowBits) return false; + if (_lowBits > rhs._lowBits) return true; + return (_interfaceIndex > rhs._interfaceIndex); + } + + bool operator <= (const ip_address & rhs) const {return (*this == rhs)||(*this < rhs);} + bool operator >= (const ip_address & rhs) const {return (*this == rhs)||(*this > rhs);} + + ip_address operator & (const ip_address & rhs) {return ip_address(_lowBits & rhs._lowBits, _highBits & rhs._highBits, _interfaceIndex);} + ip_address operator | (const ip_address & rhs) {return ip_address(_lowBits | rhs._lowBits, _highBits | rhs._highBits, _interfaceIndex);} + ip_address operator ~ () {return ip_address(~_lowBits, ~_highBits, _interfaceIndex);} + + ip_address operator &= (const ip_address & rhs) {*this = *this & rhs; return *this;} + ip_address operator |= (const ip_address & rhs) {*this = *this | rhs; return *this;} + + void SetBits(uint64 lowBits, uint64 highBits) {_lowBits = lowBits; _highBits = highBits;} + + uint64 GetLowBits() const {return _lowBits;} + uint64 GetHighBits() const {return _highBits;} + + void SetLowBits( uint64 lb) {_lowBits = lb;} + void SetHighBits(uint64 hb) {_highBits = hb;} + + void SetInterfaceIndex(uint32 iidx) {_interfaceIndex = iidx;} + uint32 GetInterfaceIndex() const {return _interfaceIndex;} + + uint32 HashCode() const {return CalculateHashCode(_interfaceIndex)+CalculateHashCode(_lowBits)+CalculateHashCode(_highBits);} + + /** Writes our address into the specified uint8 array, in the required network-friendly order. + * @param networkBuf If non-NULL, the 16-byte network-array to write to. Typically you would pass in + * mySockAddr_in6.sin6_addr.s6_addr as the argument to this function. + * @param optInterfaceIndex If non-NULL, this value will receive a copy of our interface index. Typically + * you would pass a pointer to mySockAddr_in6.sin6_addr.sin6_scope_id here. + */ + void WriteToNetworkArray(uint8 * networkBuf, uint32 * optInterfaceIndex) const + { + if (networkBuf) + { + WriteToNetworkArrayAux(&networkBuf[0], _highBits); + WriteToNetworkArrayAux(&networkBuf[8], _lowBits); + } + if (optInterfaceIndex) *optInterfaceIndex = _interfaceIndex; + } + + /** Reads our address in from the specified uint8 array, in the required network-friendly order. + * @param networkBuf If non-NULL, a 16-byte network-endian-array to read from. Typically you would pass in + * mySockAddr_in6.sin6_addr.s6_addr as the argument to this function. + * @param optInterfaceIndex If non-NULL, this value will be used to set this object's _interfaceIndex value. + */ + void ReadFromNetworkArray(const uint8 * networkBuf, const uint32 * optInterfaceIndex) + { + if (networkBuf) + { + ReadFromNetworkArrayAux(&networkBuf[0], _highBits); + ReadFromNetworkArrayAux(&networkBuf[8], _lowBits); + } + if (optInterfaceIndex) _interfaceIndex = *optInterfaceIndex; + } + +private: + void WriteToNetworkArrayAux( uint8 * out, const uint64 & in ) const {uint64 tmp = B_HOST_TO_BENDIAN_INT64(in); muscleCopyOut(out, tmp);} + void ReadFromNetworkArrayAux(const uint8 * in, uint64 & out) const {uint64 tmp; muscleCopyIn(tmp, in); out = B_BENDIAN_TO_HOST_INT64(tmp);} + + uint64 _lowBits; + uint64 _highBits; + uint32 _interfaceIndex; +}; + +/** Given an IP address, returns a 32-bit hash code for it. For IPv6, this is done by calling the HashCode() method on the ip_address object. */ +inline uint32 GetHashCodeForIPAddress(const ip_address & ip) {return ip.HashCode();} + +/** IPv6 Numeric representation of a all-zeroes invalid/guard address */ +const ip_address invalidIP(0x00); + +/** IPv6 Numeric representation of localhost (::1) for convenience */ +const ip_address localhostIP(0x01); + +/** IPv6 Numeric representation of link-local broadcast (ff02::1), for convenience */ +const ip_address broadcastIP(0x01, ((uint64)0xFF02)<<48); + +#endif + +/** IPv4 Numeric representation of broadcast (255.255.255.255), for convenience + * This constant is defined in both IPv6 and IPv4 modes, since sometimes you + * need to do an IPv4 broadcast even in an IPv6 program + */ +const ip_address broadcastIP_IPv4 = ip_address((uint32)-1); + +/** IPv4 Numeric representation of broadcast (255.255.255.255), for convenience + * This constant is defined in both IPv6 and IPv4 modes, since sometimes you + * need to do an IPv4 broadcast even in an IPv6 program + */ +const ip_address localhostIP_IPv4 = ip_address((((uint32)127)<<24)|((uint32)1)); + +/** This simple class holds an IP address and a port number, and lets you do + * useful things on the two such as using them as key values in a hash table, + * converting them to/from user-readable strings, etc. + */ +class IPAddressAndPort +{ +public: + /** Default constructor. Creates an IPAddressAndPort object with the address field + * set to (invalidIP) and the port field set to zero. + */ + IPAddressAndPort() : _ip(invalidIP), _port(0) {/* empty */} + + /** Explicit constructor + * @param ip The IP address to represent + * @param port The port number to represent + */ + IPAddressAndPort(const ip_address & ip, uint16 port) : _ip(ip), _port(port) {/* empty */} + + /** Convenience constructor. Calling this is equivalent to creating an IPAddressAndPort + * object and then calling SetFromString() on it with the given arguments. + */ + IPAddressAndPort(const String & s, uint16 defaultPort, bool allowDNSLookups) {SetFromString(s(), defaultPort, allowDNSLookups);} + + /** Copy constructor */ + IPAddressAndPort(const IPAddressAndPort & rhs) : _ip(rhs._ip), _port(rhs._port) {/* empty */} + + /** Comparison operator. Returns true iff (rhs) is equal to this object. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator == (const IPAddressAndPort & rhs) const {return (_ip == rhs._ip)&&(_port == rhs._port);} + + /** Comparison operator. Returns true iff (rhs) is not equal to this object. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator != (const IPAddressAndPort & rhs) const {return !(*this==rhs);} + + /** Comparison operator. Returns true iff this object is "less than" (rhs). + * The comparison is done first on the IP address, and if that matches, a sub-comparison is done on the port field. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator < (const IPAddressAndPort & rhs) const {return ((_ip < rhs._ip)||((_ip == rhs._ip)&&(_port < rhs._port)));} + + /** Comparison operator. Returns true iff this object is "greater than" (rhs). + * The comparison is done first on the IP address, and if that matches, a sub-comparison is done on the port field. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator > (const IPAddressAndPort & rhs) const {return ((_ip > rhs._ip)||((_ip == rhs._ip)&&(_port > rhs._port)));} + + /** Comparison operator. Returns true iff this object is "less than or equal to" (rhs). + * The comparison is done first on the IP address, and if that matches, a sub-comparison is done on the port field. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator <= (const IPAddressAndPort & rhs) const {return !(*this>rhs);} + + /** Comparison operator. Returns true iff this object is "greater than or equal to" (rhs). + * The comparison is done first on the IP address, and if that matches, a sub-comparison is done on the port field. + * @param rhs The IPAddressAndPort object to compare this object to. + */ + bool operator >= (const IPAddressAndPort & rhs) const {return !(*thisGetIPAddress() + * will return the IP address of the local network interface that the connection was + * received on, and optRetLocalInfo()->GetPort() will return the port number that + * the connection was received on. Defaults to NULL. + * @return A non-NULL ConstSocketRef if the accept was successful, or a NULL ConstSocketRef if the accept failed. + */ +ConstSocketRef Accept(const ConstSocketRef & sock, ip_address * optRetLocalInfo = NULL); + +/** Reads as many bytes as possible from the given socket and places them into (buffer). + * @param sock The socket to read from. + * @param buffer Location to write the received bytes into. + * @param bufferSizeBytes Number of bytes available at the location indicated by (buffer). + * @param socketIsBlockingIO Pass in true if the given socket is set to use blocking I/O, or false otherwise. + * @return The number of bytes read into (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 ReceiveData(const ConstSocketRef & sock, void * buffer, uint32 bufferSizeBytes, bool socketIsBlockingIO); + +/** Identical to ReceiveData(), except that this function's logic is adjusted to handle UDP semantics properly. + * @param sock The socket to read from. + * @param buffer Location to place the received bytes into. + * @param bufferSizeBytes Number of bytes available at the location indicated by (buffer). + * @param socketIsBlockingIO Pass in true if the given socket is set to use blocking I/O, or false otherwise. + * @param optRetFromIP If set to non-NULL, then on success the ip_address this parameter points to will be filled in + * with the IP address that the received data came from. Defaults to NULL. + * @param optRetFromPort If set to non-NULL, then on success the uint16 this parameter points to will be filled in + * with the source port that the received data came from. Defaults to NULL. + * @return The number of bytes read into (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 ReceiveDataUDP(const ConstSocketRef & sock, void * buffer, uint32 bufferSizeBytes, bool socketIsBlockingIO, ip_address * optRetFromIP = NULL, uint16 * optRetFromPort = NULL); + +/** Similar to ReceiveData(), except that it will call read() instead of recv(). + * This is the function to use if (fd) is referencing a file descriptor instead of a socket. + * @param fd The file descriptor to read from. + * @param buffer Location to place the read bytes into. + * @param bufferSizeBytes Number of bytes available at the location indicated by (buffer). + * @param fdIsBlockingIO Pass in true if the given fd is set to use blocking I/O, or false otherwise. + * @return The number of bytes read into (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 ReadData(const ConstSocketRef & fd, void * buffer, uint32 bufferSizeBytes, bool fdIsBlockingIO); + +/** Transmits as many bytes as possible from the given buffer over the given socket. + * @param sock The socket to transmit over. + * @param buffer Buffer to read the outgoing bytes from. + * @param bufferSizeBytes Number of bytes to send. + * @param socketIsBlockingIO Pass in true if the given socket is set to use blocking I/O, or false otherwise. + * @return The number of bytes sent from (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 SendData(const ConstSocketRef & sock, const void * buffer, uint32 bufferSizeBytes, bool socketIsBlockingIO); + +/** Similar to SendData(), except that this function's logic is adjusted to handle UDP semantics properly. + * @param sock The socket to transmit over. + * @param buffer Buffer to read the outgoing bytes from. + * @param bufferSizeBytes Number of bytes to send. + * @param socketIsBlockingIO Pass in true if the given socket is set to use blocking I/O, or false otherwise. + * @param optDestIP If set to non-zero, the data will be sent to the given IP address. Otherwise it will + * be sent to the socket's current IP address (see SetUDPSocketTarget()). Defaults to zero. + * @param destPort If set to non-zero, the data will be sent to the specified port. Otherwise it will + * be sent to the socket's current destination port (see SetUDPSocketTarget()). Defaults to zero. + * @return The number of bytes sent from (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 SendDataUDP(const ConstSocketRef & sock, const void * buffer, uint32 bufferSizeBytes, bool socketIsBlockingIO, const ip_address & optDestIP = invalidIP, uint16 destPort = 0); + +/** Similar to SendData(), except that the implementation calls write() instead of send(). This + * is the function to use when (fd) refers to a file descriptor instead of a socket. + * @param fd The file descriptor to write the data to. + * @param buffer Buffer to read the outgoing bytes from. + * @param bufferSizeBytes Number of bytes to write. + * @param fdIsBlockingIO Pass in true if the given file descriptor is set to use blocking I/O, or false otherwise. + * @return The number of bytes sent from (buffer), or a negative value if there was an error. + * Note that this value may be smaller than (bufferSizeBytes). + */ +int32 WriteData(const ConstSocketRef & fd, const void * buffer, uint32 bufferSizeBytes, bool fdIsBlockingIO); + +/** This function initiates a non-blocking connection to the given host IP address and port. + * It will return the created socket, which may or may not be fully connected yet. + * If it is connected, (retIsReady) will be to true, otherwise it will be set to false. + * If (retIsReady) is false, then you can use select() to find out when the state of the + * socket has changed: select() will return ready-to-write on the socket when it is + * fully connected (or when the connection fails), at which point you can call + * FinalizeAsyncConnect() on the socket: if FinalizeAsyncConnect() succeeds, the connection + * succeeded; if not, the connection failed. + * @param hostIP 32-bit IP address to connect to (hostname isn't used as hostname lookups can't be made asynchronous AFAIK) + * @param port Port to connect to. + * @param retIsReady On success, this bool is set to true iff the socket is ready to use, or + * false to indicate that an asynchronous connection is now in progress. + * @return A non-NULL ConstSocketRef (which is likely still in the process of connecting) on success, or a NULL ConstSocketRef if the accept failed. + */ +ConstSocketRef ConnectAsync(const ip_address & hostIP, uint16 port, bool & retIsReady); + +/** As above, only this version of ConnectAsync() takes an IPAddressAndPort object instead of separate IP address and port arguments. + * @param iap IP address and port to connect to. + * @param retIsReady On success, this bool is set to true iff the socket is ready to use, or + * false to indicate that an asynchronous connection is now in progress. + * @return A non-NULL ConstSocketRef (which is likely still in the process of connecting) on success, or a NULL ConstSocketRef if the accept failed. + */ +inline ConstSocketRef ConnectAsync(const IPAddressAndPort & iap, bool & retIsReady) {return ConnectAsync(iap.GetIPAddress(), iap.GetPort(), retIsReady);} + +/** When a socket that was connecting asynchronously finally + * selects ready-for-write to indicate that the asynchronous connect + * attempt has reached a conclusion, call this method. It will finalize + * the connection and make it ready for use. + * @param sock The socket that was connecting asynchronously + * @returns B_NO_ERROR if the connection is ready to use, or B_ERROR if the connect failed. + * @note Under Windows, select() won't return ready-for-write if the connection fails... instead + * it will select-notify for your socket on the exceptions fd_set (if you provided one). + * Once this happens, there is no need to call FinalizeAsyncConnect() -- the fact that the + * socket notified on the exceptions fd_set is enough for you to know the asynchronous connection + * failed. Successful asynchronous connect()s do exhibit the standard (select ready-for-write) + * behaviour, though. + */ +status_t FinalizeAsyncConnect(const ConstSocketRef & sock); + +/** Shuts the given socket down. (Note that you don't generally need to call this function; it's generally + * only useful if you need to half-shutdown the socket, e.g. stop the output direction but not the input + * direction) + * @param sock The socket to shutdown communication on. + * @param disableReception If true, further reception of data will be disabled on this socket. + * @param disableTransmission If true, further transmission of data will be disabled on this socket. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t ShutdownSocket(const ConstSocketRef & sock, bool disableReception = true, bool disableTransmission = true); + +/** Convenience function for creating a TCP socket that is listening on a given local port for incoming TCP connections. + * @param port Which port to listen on, or 0 to have the system should choose a port for you + * @param maxbacklog Maximum connection backlog to allow for (defaults to 20) + * @param optRetPort If non-NULL, the uint16 this value points to will be set to the actual port bound to (useful when you want the system to choose a port for you) + * @param optInterfaceIP Optional IP address of the local network interface to listen on. If left unspecified, or + * if passed in as (invalidIP), then this socket will listen on all available network interfaces. + * @return A non-NULL ConstSocketRef if the port was bound successfully, or a NULL ConstSocketRef if the accept failed. + */ +ConstSocketRef CreateAcceptingSocket(uint16 port, int maxbacklog = 20, uint16 * optRetPort = NULL, const ip_address & optInterfaceIP = invalidIP); + +/** Translates the given 4-byte IP address into a string representation. + * @param address The 4-byte IP address to translate into text. + * @param outBuf Buffer where the NUL-terminated ASCII representation of the string will be placed. + * This buffer must be at least 64 bytes long (or at least 16 bytes long if MUSCLE_AVOID_IPV6 is defined) + * @param preferIPv4Style If set true, then IPv4 addresses will be returned as e.g. "192.168.1.1", not "::192.168.1.1" or "::ffff:192.168.1.1". + * Defaults to false. If MUSCLE_AVOID_IPV6 is defined, then this argument isn't used. + */ +void Inet_NtoA(const ip_address & address, char * outBuf, bool preferIPv4Style = false); + +/** A more convenient version of INet_NtoA(). Given an IP address, returns a human-readable String representation of that address. + * @param ipAddress the IP address to return in String form. + * @param preferIPv4Style If set true, then IPv4 addresses will be returned as e.g. "192.168.1.1", not "::192.168.1.1" or "::ffff:192.168.1.1". + * Defaults to false. If MUSCLE_AVOID_IPV6 is defined, then this argument isn't used. + */ +String Inet_NtoA(const ip_address & ipAddress, bool preferIPv4Style = false); + +/** Returns true iff (s) is a well-formed IP address (e.g. "192.168.0.1") + * @param (s) An ASCII string to check the formatting of + * @returns true iff there are exactly four dot-separated integers between 0 and 255 + * and no extraneous characters in the string. + */ +bool IsIPAddress(const char * s); + +/** Given a dotted-quad IP address in ASCII format (e.g. "192.168.0.1"), returns + * the equivalent IP address in ip_address (packed binary) form. + * @param buf numeric IP address in ASCII. + * @returns IP address as a ip_address, or invalidIP on failure. + */ +ip_address Inet_AtoN(const char * buf); + +/** Returns a string that is the local host's primary host name. */ +String GetLocalHostName(); + +/** Reurns the IP address that the given socket is connected to. + * @param sock The socket descriptor to find out info about. + * @param expandLocalhost If true, then if the peer's ip address is reported as 127.0.0.1, this + * function will attempt to determine the host machine's actual primary IP + * address and return that instead. Otherwise, 127.0.0.1 will be + * returned in this case. + * @param optRetPort if non-NULL, the port we are connected to on the remote peer will be written here. Defaults to NULL. + * @return The IP address on success, or invalidIP on failure (such as if the socket isn't valid and connected). + */ +ip_address GetPeerIPAddress(const ConstSocketRef & sock, bool expandLocalhost, uint16 * optRetPort = NULL); + +/** Creates a pair of sockets that are connected to each other, + * so that any bytes you pass into one socket come out the other socket. + * This is useful when you want to wake up a thread that is blocked in select()... + * you have it select() on one socket, and you send a byte on the other. + * @param retSocket1 On success, this value will be set to the socket ID of the first socket. + * @param retSocket2 On success, this value will be set to the socket ID of the second socket. + * @param blocking Whether the two sockets should use blocking I/O or not. Defaults to false. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t CreateConnectedSocketPair(ConstSocketRef & retSocket1, ConstSocketRef & retSocket2, bool blocking = false); + +/** Enables or disables blocking I/O on the given socket. + * (Default for a socket is blocking mode enabled) + * @param sock the socket descriptor to act on. + * @param enabled Whether I/O on this socket should be enabled or not. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketBlockingEnabled(const ConstSocketRef & sock, bool enabled); + +/** Returns true iff the given socket has blocking I/O enabled. + * @param sock the socket descriptor to query. + * @returns true iff the socket is in blocking I/O mode, or false if it is not (or if it is an invalid socket) + * @note this function is not implemented under Windows (as Windows doesn't appear to provide any method to + * obtain this information from a socket). Under Windows, this method will simply log an erorr message + * and return false. + */ +bool GetSocketBlockingEnabled(const ConstSocketRef & sock); + +/** + * Turns Nagle's algorithm (output packet buffering/coalescing) on or off. + * @param sock the socket descriptor to act on. + * @param enabled If true, data will be held momentarily before sending, + * to allow for bigger packets. If false, each Write() + * call will cause a new packet to be sent immediately. + * @return B_NO_ERROR on success, B_ERROR on error. + */ +status_t SetSocketNaglesAlgorithmEnabled(const ConstSocketRef & sock, bool enabled); + +/** Returns true iff the given socket has Nagle's algorithm enabled. + * @param sock the socket descriptor to query. + * @returns true iff the socket is has Nagle's algorithm enabled, or false if it does not (or if it is an invalid socket) + */ +bool GetSocketNaglesAlgorithmEnabled(const ConstSocketRef & sock); + +/** + * Sets the size of the given socket's TCP send buffer to the specified + * size (or as close to that size as is possible). + * @param sock the socket descriptor to act on. + * @param sendBufferSizeBytes New size of the TCP send buffer, in bytes. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketSendBufferSize(const ConstSocketRef & sock, uint32 sendBufferSizeBytes); + +/** + * Returns the current size of the socket's TCP send buffer. + * @param sock The socket to query. + * @return The size of the socket's TCP send buffer, in bytes, or -1 on error (e.g. invalid socket) + */ +int32 GetSocketSendBufferSize(const ConstSocketRef & sock); + +/** + * Sets the size of the given socket's TCP receive buffer to the specified + * size (or as close to that size as is possible). + * @param sock the socket descriptor to act on. + * @param receiveBufferSizeBytes New size of the TCP receive buffer, in bytes. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketReceiveBufferSize(const ConstSocketRef & sock, uint32 receiveBufferSizeBytes); + +/** + * Returns the current size of the socket's TCP receive buffer. + * @param sock The socket to query. + * @return The size of the socket's TCP receive buffer, in bytes, or -1 on error (e.g. invalid socket) + */ +int32 GetSocketReceiveBufferSize(const ConstSocketRef & sock); + +/** This class is an interface to an object that can have its SocketCallback() method called + * at the appropriate times when certain actions are performed on a socket. By installing + * a GlobalSocketCallback object via SetGlobalSocketCallback(), behaviors can be set for all + * sockets in the process, which can be simpler than changing every individual code path + * to install those behaviors on a per-socket basis. + */ +class GlobalSocketCallback : private CountedObject +{ +public: + /** Default constructor */ + GlobalSocketCallback() {/* empty */} + + /** Destructor */ + virtual ~GlobalSocketCallback() {/* empty */} + + /** Called by certain functions in NetworkUtilityFunctions.cpp + * @note this method may be called by different threads, so it needs to be thread-safe. + * @param eventType a SOCKET_CALLBACK_* value indicating what caused this callback call. + * @param sock The socket in question + * @returns B_NO_ERROR on success, or B_ERROR if the calling operation should be aborted. + */ + virtual status_t SocketCallback(uint32 eventType, const ConstSocketRef & sock) = 0; + + enum { + SOCKET_CALLBACK_CREATE_UDP = 0, // socket was just created by CreateUDPSocket() + SOCKET_CALLBACK_CREATE_ACCEPTING, // socket was just created by CreateAcceptingSocket() + SOCKET_CALLBACK_ACCEPT, // socket was just created by Accept() + SOCKET_CALLBACK_CONNECT, // socket was just created by Connect() or ConnectAsync() + NUM_SOCKET_CALLBACKS + }; +}; + +/** Set the global socket-callback object for this process. + * @param cb The object whose SocketCallback() method should be called by Connect(), + * Accept(), FinalizeAsyncConnect(), etc, or NULL to have no more callbacks + */ +void SetGlobalSocketCallback(GlobalSocketCallback * cb); + +/** Returns the currently installed GlobalSocketCallback object, or NULL if there is none installed. */ +GlobalSocketCallback * GetGlobalSocketCallback(); + +#ifdef MUSCLE_ENABLE_KEEPALIVE_API + +/** + * This function modifies the TCP keep-alive behavior for the given TCP socket -- that is, you can use this + * function to control how often the TCP socket checks to see if the remote peer is still accessible when the + * TCP connection is idle. If this function is never called, the default TCP behavior is to never check the + * remote peer when the connection is idle -- it will only check when there is data buffered up to send to the + * remote peer. + * @param sock The TCP socket to adjust the keepalive behavior of. + * @param maxProbeCount The number of keepalive-ping probes that must go unanswered before the TCP connection is closed. + * Passing zero to this argument will disable keepalive-ping behavior. + * @param idleTime The amount of time (in microseconds) of inactivity on the TCP socket that must pass before the + * first keepalive-ping probe is sent. Note that the granularity of the timeout is determined by + * the operating system, so the actual timeout period may be somewhat more or less than the specified number + * of microseconds. (Currently it gets rounded up to the nearest second) + * @param retransmitTime The amount of time (in microseconds) of further inactivity on the TCP socket that must pass before the + * next keepalive-ping probe is sent. Note that the granularity of the timeout is determined by + * the operating system, so the actual timeout period may be somewhat more or less than the specified number + * of microseconds. (Currently it gets rounded up to the nearest second) + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketKeepAliveBehavior(const ConstSocketRef & sock, uint32 maxProbeCount, uint64 idleTime, uint64 retransmitTime); + +/** + * Queries the values previously set on the socket by SetSocketKeepAliveBehavior(). + * @param sock The TCP socket to return the keepalive behavior of. + * @param retMaxProbeCount if non-NULL, the max-probe-count value of the socket will be written into this argument. + * @param retIdleTime if non-NULL, the idle-time of the socket will be written into this argument. + * @param retRetransmitTime if non-NULL, the transmit-time of the socket will be written into this argument. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + * @see SetSocketKeepAliveBehavior() + */ +status_t GetSocketKeepAliveBehavior(const ConstSocketRef & sock, uint32 * retMaxProbeCount, uint64 * retIdleTime, uint64 * retRetransmitTime); + +#endif + +/** Set a user-specified IP address to return from GetHostByName() and GetPeerIPAddress() instead of 127.0.0.1. + * Note that this function does not change the computer's IP address -- it merely changes what + * the aforementioned functions will report. + * @param ip New IP address to return instead of 127.0.0.1, or 0 to disable this override. + */ +void SetLocalHostIPOverride(const ip_address & ip); + +/** Returns the user-specified IP address that was previously set by SetLocalHostIPOverride(), or 0 + * if none was set. Note that this function does not report the local computer's IP address, + * unless you previously called SetLocalHostIPOverride() with that address. + */ +ip_address GetLocalHostIPOverride(); + +/** Creates and returns a socket that can be used for UDP communications. + * Returns a negative value on error, or a non-negative socket handle on + * success. You'll probably want to call BindUDPSocket() or SetUDPSocketTarget() + * after calling this method. + */ +ConstSocketRef CreateUDPSocket(); + +/** Attempts to given UDP socket to the given port. + * @param sock The UDP socket (previously created by CreateUDPSocket()) + * @param port UDP port ID to bind the socket to. If zero, the system will choose a port ID. + * @param optRetPort if non-NULL, then on successful return the value this pointer points to will contain + * the port ID that the socket was bound to. Defaults to NULL. + * @param optFrom If non-zero, then the socket will be bound in such a way that only data + * packets addressed to this IP address will be accepted. Defaults to zero, + * meaning that packets addressed to any of this machine's IP addresses will + * be accepted. (This parameter is typically only useful on machines with + * multiple IP addresses) + * @param allowShared If set to true, the port will be set up so that multiple processes + * can bind to it simultaneously. This is useful for sockets that are + * to be receiving broadcast UDP packets, since then you can run multiple + * UDP broadcast receivers on a single computer. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t BindUDPSocket(const ConstSocketRef & sock, uint16 port, uint16 * optRetPort = NULL, const ip_address & optFrom = invalidIP, bool allowShared = false); + +/** Set the target/destination address for a UDP socket. After successful return + * of this function, any data that is written to the UDP socket will be sent to this + * IP address and port. This function is guaranteed to return quickly. + * @param sock The UDP socket to send to (previously created by CreateUDPSocket()). + * @param remoteIP Remote IP address that data should be sent to. + * @param remotePort Remote UDP port ID that data should be sent to. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetUDPSocketTarget(const ConstSocketRef & sock, const ip_address & remoteIP, uint16 remotePort); + +/** As above, except that the remote host is specified by hostname instead of IP address. + * Note that this function may take involve a DNS lookup, and so may take a significant + * amount of time to complete. + * @param sock The UDP socket to send to (previously created by CreateUDPSocket()). + * @param remoteHostName Name of remote host (e.g. "www.mycomputer.com" or "132.239.50.8") + * @param remotePort Remote UDP port ID that data should be sent to. + * @param expandLocalhost If true, then if (name) corresponds to 127.0.0.1, this function + * will attempt to determine the host machine's actual primary IP + * address and return that instead. Otherwise, 127.0.0.1 will be + * returned in this case. Defaults to false. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetUDPSocketTarget(const ConstSocketRef & sock, const char * remoteHostName, uint16 remotePort, bool expandLocalhost = false); + +/** Enable/disable sending of broadcast packets on the given UDP socket. + * @param sock UDP socket to enable or disable the sending of broadcast UDP packets with. + * (Note that the default state of newly created UDP sockets is broadcast-disabled) + * @param broadcast True if broadcasting should be enabled, false if broadcasting should be disabled. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetUDPSocketBroadcastEnabled(const ConstSocketRef & sock, bool broadcast); + +/** Returns true iff the specified UDP socket has broadcast enabled; or false if it does not. + * @param sock The UDP socket to query. + * @returns true iff the socket is enabled for UDP broadcast; false otherwise. + */ +bool GetUDPSocketBroadcastEnabled(const ConstSocketRef & sock); + +/** This function returns true iff (ip) is one of the standard, well-known addresses for the + * localhost loopback device. (e.g. 127.0.0.1 or ::1 or fe80::1) + * @param ip an IP address. + * @returns true iff (ip) is a well-known loopback device address. Note that this function + * does NOT check to see if an arbitrary IP address actually maps to the local host; + * it merely sees if (ip) is one of a few hard-coded values as described above. + */ +bool IsStandardLoopbackDeviceAddress(const ip_address & ip); + +/** This function returns true iff (ip) is a multicast IP address. + * @param ip an IP address. + * @returns true iff (ip) is a multicast address. + */ +bool IsMulticastIPAddress(const ip_address & ip); + +/** Returns true iff (ip) is a non-zero IP address. + * @param ip an IP adress. + * @returns true iff (ip) is not all zeroes. (Note: In IPv6 mode, the interface index is not considered here) + */ +bool IsValidAddress(const ip_address & ip); + +/** Returns true iff (ip) is an IPv4-style (32-bits-only) or IPv4-mapped + * (32-bits-only plus ffff prefix) address. + * @param ip The IP address to examine. + * @returns treu if (ip) is an IPv4 or IPv4-mapped address, else false. + */ +bool IsIPv4Address(const ip_address & ip); + +/** This little container class is used to return data from the GetNetworkInterfaceInfos() function, below */ +class NetworkInterfaceInfo +{ +public: + /** Default constructor. Sets all member variables to default values. */ + NetworkInterfaceInfo(); + + /** Constructor. Sets all member variables to the values specified in the argument list. + * @param name The name of the interface, as it is known to the computer (e.g. "/dev/eth0"). + * @param desc A human-readable description string describing the interface (e.g. "Ethernet Jack 0", or somesuch). + * @param ip The local IP address associated with the interface. + * @param netmask The netmask being used by this interface. + * @param broadcastIP The broadcast IP address associated with this interface. + * @param enabled True iff the interface is currently enabled; false if it is not. + * @param copper True iff the interface currently has an ethernet cable plugged into it. + */ + NetworkInterfaceInfo(const String & name, const String & desc, const ip_address & ip, const ip_address & netmask, const ip_address & broadcastIP, bool enabled, bool copper); + + /** Returns the name of this interface, or "" if the name is not known. */ + const String & GetName() const {return _name;} + + /** Returns a (human-readable) description of this interface, or "" if a description is unavailable. */ + const String & GetDescription() const {return _desc;} + + /** Returns the IP address of this interface */ + const ip_address & GetLocalAddress() const {return _ip;} + + /** Returns the netmask of this interface */ + const ip_address & GetNetmask() const {return _netmask;} + + /** If this interface is a point-to-point interface, this method returns the IP + * address of the machine at the remote end of the interface. Otherwise, this + * method returns the broadcast address for this interface. + */ + const ip_address & GetBroadcastAddress() const {return _broadcastIP;} + + /** Returns true iff this interface is currently enabled ("up"). */ + bool IsEnabled() const {return _enabled;} + + /** Returns true iff this network interface is currently plugged in to anything + * (i.e. iff the Ethernet cable is connected to the jack). + * Note that copper detection is not currently supported under Windows, so + * under Windows this will always return false. + */ + bool IsCopperDetected() const {return _copper;} + + /** For debugging. Returns a human-readable string describing this interface. */ + String ToString() const; + + /** Returns a hash code for this NetworkInterfaceInfo object. */ + uint32 HashCode() const; + +private: + String _name; + String _desc; + ip_address _ip; + ip_address _netmask; + ip_address _broadcastIP; + bool _enabled; + bool _copper; +}; + +/** Bits that can be passed to GetNetworkInterfaceInfos() or GetNetworkInterfaceAddresses(). */ +enum { + GNII_INCLUDE_IPV4_INTERFACES = 0x01, // If set, IPv4-specific interfaces will be returned + GNII_INCLUDE_IPV6_INTERFACES = 0x02, // If set, IPv6-specific interfaces will be returned + GNII_INCLUDE_LOOPBACK_INTERFACES = 0x04, // If set, loopback interfaces (e.g. lo0/127.0.0.1) will be returned + GNII_INCLUDE_NONLOOPBACK_INTERFACES = 0x08, // If set, non-loopback interfaces (e.g. en0) will be returned + GNII_INCLUDE_ENABLED_INTERFACES = 0x10, // If set, enabled (aka "up") interfaces will be returned + GNII_INCLUDE_DISABLED_INTERFACES = 0x20, // If set, disabled (aka "down") interfaces will be returned + GNII_INCLUDE_LOOPBACK_INTERFACES_ONLY_AS_LAST_RESORT = 0x40, // If set, loopback interfaces will be returned only if no other interfaces are found + + // For convenience, GNII_INCLUDE_MUSCLE_PREFERRED_INTERFACES will specify interfaces of the family specified by MUSCLE_AVOID_IPV6's presence/abscence. +#ifdef MUSCLE_AVOID_IPV6 + GNII_INCLUDE_MUSCLE_PREFERRED_INTERFACES = GNII_INCLUDE_IPV4_INTERFACES, +#else + GNII_INCLUDE_MUSCLE_PREFERRED_INTERFACES = GNII_INCLUDE_IPV6_INTERFACES, +#endif + + GNII_INCLUDE_ALL_INTERFACES = 0xFFFFFFFF, // If set, all interfaces will be returned +}; + +/** This function queries the local OS for information about all available network + * interfaces. Note that this method is only implemented for some OS's (Linux, + * MacOS/X, Windows), and that on other OS's it may just always return B_ERROR. + * @param results On success, zero or more NetworkInterfaceInfo objects will + * be added to this Queue for you to look at. + * @param includeBits A chord of GNII_INCLUDE_* bits indicating which types of network interface you want to be + * included in the returned list. Defaults to GNII_INCLUDE_ALL_INTERFACES, which indicates that + * no filtering of the returned list should be done. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, call not implemented for the current OS, etc) + */ +status_t GetNetworkInterfaceInfos(Queue & results, uint32 includeBits = GNII_INCLUDE_ALL_INTERFACES); + +/** This is a more limited version of GetNetworkInterfaceInfos(), included for convenience. + * Instead of returning all information about the local host's network interfaces, this + * one returns only their IP addresses. It is the same as calling GetNetworkInterfaceInfos() + * and then iterating the returned list to assemble a list only of the IP addresses returned + * by GetBroadcastAddress(). + * @param retAddresses On success, zero or more ip_addresses will be added to this Queue for you to look at. + * @param includeBits A chord of GNII_INCLUDE_* bits indicating which types of network interface you want to be + * included in the returned list. Defaults to GNII_INCLUDE_ALL_INTERFACES, which indicates that + * no filtering of the returned list should be done. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory, + * call not implemented for the current OS, etc) + */ +status_t GetNetworkInterfaceAddresses(Queue & retAddresses, uint32 includeBits = GNII_INCLUDE_ALL_INTERFACES); + +#ifndef MUSCLE_AVOID_MULTICAST_API + +/** Sets whether multicast data sent on this socket should be received by + * this socket or not (IP_MULTICAST_LOOP). Default state is enabled/true. + * @param sock The socket to set the state of the IP_MULTICAST_LOOP flag on. + * @param multicastToSelf If true, enable loopback. Otherwise, disable it. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketMulticastToSelf(const ConstSocketRef & sock, bool multicastToSelf); + +/** Returns true iff multicast packets sent by this socket should be received by this socket. + * Default state is enabled. + * @param sock The socket to query + * @returns true iff this socket has multicast-to-self (IP_MULTICAST_LOOP) enabled. + */ +bool GetSocketMulticastToSelf(const ConstSocketRef & sock); + +/** Set the "time to live" flag for packets sent by this socket. + * Default state is 1, i.e. "local LAN segment only". + * Other possible values include 0 ("localhost only"), 2-31 ("local site only"), + * 32-63 ("local region only"), 64-127 ("local continent only"), or 128-255 ("global"). + * @param sock The socket to set the TTL value for + * @param ttl The ttl value to set (see above). + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketMulticastTimeToLive(const ConstSocketRef & sock, uint8 ttl); + +/** Returns the "time to live" associated with multicast packets sent on + * this socket, or 0 on failure. + * @param sock The socket to query the TTL value of. + */ +uint8 GetSocketMulticastTimeToLive(const ConstSocketRef & sock); + +#ifdef MUSCLE_AVOID_IPV6 + +// IPv4 multicast + +/** Specify the address of the local interface that the given socket should + * send multicast packets on. If this isn't called, the kernel will try to choose + * an appropriate default interface to send on. + * @param sock The socket to set the sending interface for. + * @param address The address of the local interface to send multicast packets on. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketMulticastSendInterfaceAddress(const ConstSocketRef & sock, const ip_address & address); + +/** Returns the address of the local interface that the given socket will + * try to send multicast packets on, or invalidIP on failure. + * @param sock The socket to query the sending interface of. + * @returns the interface's IP address, or invalidIP on error. + */ +ip_address GetSocketMulticastSendInterfaceAddress(const ConstSocketRef & sock); + +/** Attempts to add the specified socket to the specified multicast group. + * @param sock The socket to add to the multicast group + * @param groupAddress The IP address of the multicast group. + * @param localInterfaceAddress Optional IP address of the local interface use for receiving + * data from this group. If left as (invalidIP), an appropriate + * interface will be chosen automatically. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t AddSocketToMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress, const ip_address & localInterfaceAddress = invalidIP); + +/** Attempts to remove the specified socket from the specified multicast group + * that it was previously added to. + * @param sock The socket to add to the multicast group + * @param groupAddress The IP address of the multicast group. + * @param localInterfaceAddress Optional IP address of the local interface used for receiving + * data from this group. If left as (invalidIP), the first matching + * group will be removed. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t RemoveSocketFromMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress, const ip_address & localInterfaceAddress = invalidIP); + +#else // end IPv4 multicast, begin IPv6 multicast + +/** Specify the interface index of the local network interface that the given socket should + * send multicast packets on. If this isn't called, the kernel will choose an appropriate + * default interface to send multicast packets over. + * @param sock The socket to set the sending interface for. + * @param interfaceIndex Optional index of the interface to send the multicast packets on. + * Zero means the default interface, larger values mean another interface. + * You can call GetNetworkInterfaceInfos() to get the list of available interfaces. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t SetSocketMulticastSendInterfaceIndex(const ConstSocketRef & sock, uint32 interfaceIndex); + +/** Returns the index of the local interface that the given socket will + * try to send multicast packets on, or -1 on failure. + * @param sock The socket to query the sending interface index of. + * @returns the socket's interface index, or -1 on error. + */ +int32 GetSocketMulticastSendInterfaceIndex(const ConstSocketRef & sock); + +/** Attempts to add the specified socket to the specified multicast group. + * @param sock The socket to add to the multicast group + * @param groupAddress The IP address of the multicast group. + * @note Under Windows this call will fail unless the socket has already + * been bound to a port (e.g. with BindUDPSocket()). Other OS's don't + * seem to have that requirement. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t AddSocketToMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress); + +/** Attempts to remove the specified socket from the specified multicast group that it was previously added to. + * @param sock The socket to add to the multicast group + * @param groupAddress The IP address of the multicast group. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t RemoveSocketFromMulticastGroup(const ConstSocketRef & sock, const ip_address & groupAddress); + +#endif // end IPv6 multicast + +#endif // !MUSCLE_AVOID_MULTICAST_API + +/// @cond HIDDEN_SYMBOLS + +// Different OS's use different types for pass-by-reference in accept(), etc. +// So I define my own muscle_socklen_t to avoid having to #ifdef all my code +#if defined(__amd64__) || defined(__FreeBSD__) || defined(BSD) || defined(__PPC64__) || defined(__HAIKU__) || defined(ANDROID) || defined(_SOCKLEN_T) +typedef socklen_t muscle_socklen_t; +#elif defined(__BEOS__) || defined(__HAIKU__) || defined(__APPLE__) || defined(__CYGWIN__) || defined(WIN32) || defined(__QNX__) || defined(__osf__) +typedef int muscle_socklen_t; +#else +typedef size_t muscle_socklen_t; +#endif + +static inline long recv_ignore_eintr( int s, void *b, unsigned long numBytes, int flags) {int ret; do {ret = recv(s, (char *)b, numBytes, flags);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +static inline long recvfrom_ignore_eintr(int s, void *b, unsigned long numBytes, int flags, struct sockaddr *a, muscle_socklen_t * alen) {int ret; do {ret = recvfrom(s, (char *)b, numBytes, flags, a, alen);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +static inline long send_ignore_eintr( int s, const void *b, unsigned long numBytes, int flags) {int ret; do {ret = send(s, (char *)b, numBytes, flags);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +static inline long sendto_ignore_eintr( int s, const void *b, unsigned long numBytes, int flags, const struct sockaddr * d, int dlen) {int ret; do {ret = sendto(s, (char *)b, numBytes, flags, d, dlen);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +#ifndef WIN32 +static inline long read_ignore_eintr( int f, void *b, unsigned long nbyte) {int ret; do {ret = read(f, (char *)b, nbyte);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +static inline long write_ignore_eintr( int f, const void *b, unsigned long nbyte) {int ret; do {ret = write(f, (char *)b, nbyte);} while((ret<0)&&(PreviousOperationWasInterrupted())); return ret;} +#endif + +/// @endcond + +/** @} */ // end of networkutilityfunctions doxygen group + +}; // end namespace muscle + +#endif + diff --git a/util/ObjectPool.h b/util/ObjectPool.h new file mode 100644 index 00000000..710b2b9a --- /dev/null +++ b/util/ObjectPool.h @@ -0,0 +1,509 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleObjectPool_h +#define MuscleObjectPool_h + +#include // So we can use typeid().name() in the assertion failures +#include "system/Mutex.h" + +namespace muscle { + +// Uncomment this #define to disable object pools (i.e. turn them into +// simple passthroughs to the new/delete operators). +// This is helpful if you are trying to track down memory leaks. +//#define DISABLE_OBJECT_POOLING 1 + +#ifndef DEFAULT_MUSCLE_POOL_SLAB_SIZE +# define DEFAULT_MUSCLE_POOL_SLAB_SIZE (4*1024) // let's have each slab fit nicely into a 4KB page +#endif + +/** An interface that must be implemented by all ObjectPool classes. + * Used to support polymorphism in pool management. + */ +class AbstractObjectGenerator +{ +public: + /** Default ctor */ + AbstractObjectGenerator() {/* empty */} + + /** Virtual dtor to keep C++ honest */ + virtual ~AbstractObjectGenerator() {/* empty */} + + /** Should be implemented to pass through to ObtainObject(). + * Useful for handling different types of ObjectPool interchangably. + * Note that the caller is responsible for deleting or recycling + * the returned object! + */ + virtual void * ObtainObjectGeneric() = 0; +}; + +/** An interface that must be implemented by all ObjectPool classes. + * Used to support polymorphism in our reference counting. + */ +class AbstractObjectRecycler +{ +public: + /** Default constructor. Registers us with the global recyclers list. */ + AbstractObjectRecycler(); + + /** Default destructor. Unregisters us from the global recyclers list. */ + virtual ~AbstractObjectRecycler(); + + /** Should be implemented to downcast (obj) to the correct type, + * and recycle it (typically by calling ReleaseObject(). + * @param obj Pointer to the object to recycle. Must point to an object of the correct type. + * May be NULL, in which case this method is a no-op. + */ + virtual void RecycleObject(void * obj) = 0; + + /** Should be implemented to destroy all objects we have cached and + * return the number of objects that were destroyed. + */ + virtual uint32 FlushCachedObjects() = 0; + + /** Walks the linked list of all AbstractObjectRecyclers, calling + * FlushCachedObjects() on each one, until all cached objects have been destroyed. + * This method is called by the SetupSystem destructor, to ensure that + * all objects have been deleted before main() returns, so that shutdown + * ordering issues do not cause bad memory writes, etc. + */ + static void GlobalFlushAllCachedObjects(); + +private: + AbstractObjectRecycler * _prev; + AbstractObjectRecycler * _next; +}; + +/** This class is just here to usefully tie together the object generating and + * object recycling capabilities of its two superclasses into a single interface. + */ +class AbstractObjectManager : public AbstractObjectGenerator, public AbstractObjectRecycler +{ + // empty +}; + +/** A thread-safe templated object pooling class that helps reduce the number of + * dynamic allocations and deallocations in your app. Instead of calling 'new Object', + * you call myObjectPool.ObtainObject(), and instead of calling 'delete Object', you call + * myObjectPool.ReleaseObject(). The advantage is that the ObjectPool will + * keep (up to a certain number of) "spare" Objects around, and recycle them back + * to you as needed. + */ +template class ObjectPool : public AbstractObjectManager +{ +public: + /** + * Constructor. + * @param maxPoolSize the approximate maximum number of recycled objects that may be kept around for future reuse at any one time. Defaults to 100. + */ + ObjectPool(uint32 maxPoolSize=100) : _curPoolSize(0), _maxPoolSize(maxPoolSize), _firstSlab(NULL), _lastSlab(NULL) + { + MASSERT(NUM_OBJECTS_PER_SLAB<=65535, "Too many objects per ObjectSlab, uint16 indices will overflow!"); + } + + /** + * Destructor. Deletes all objects in the pool. + */ + virtual ~ObjectPool() + { + while(_firstSlab) + { + if (_firstSlab->IsInUse()) + { + LogTime(MUSCLE_LOG_CRITICALERROR, "~ObjectPool %p (%s): slab %p is still in use when we destroy it!\n", this, _firstSlab->GetObjectClassName(), _firstSlab); + _firstSlab->PrintToStream(); + MCRASH("ObjectPool destroyed while its objects were still in use (CompleteSetupSystem object not declared at the top of main(), or Ref objects were leaked?)"); + } + ObjectSlab * nextSlab = _firstSlab->GetNext(); + delete _firstSlab; + _firstSlab = nextSlab; + } + } + + /** Returns a new Object for use (or NULL if no memory available). + * You are responsible for calling ReleaseObject() on this object + * when you are done with it. + * This method is thread-safe. + * @return a new Object, or NULL if out of memory. + */ + Object * ObtainObject() + { +#ifdef DISABLE_OBJECT_POOLING + Object * ret = newnothrow Object; + if (ret) ret->SetManager(this); + else WARN_OUT_OF_MEMORY; + return ret; +#else + Object * ret = NULL; + if (_mutex.Lock() == B_NO_ERROR) + { + ret = ObtainObjectAux(); + _mutex.Unlock(); + } + if (ret) ret->SetManager(this); + else WARN_OUT_OF_MEMORY; + return ret; +#endif + } + + /** Adds the given object to our "standby" object list to be recycled, or + * deletes it if the "standby" list is already at its maximum size. + * This method is thread-safe. + * @param obj An Object to recycle or delete. May be NULL. + */ + void ReleaseObject(Object * obj) + { + if (obj) + { + MASSERT(obj->GetManager()==this, "ObjectPool::ReleaseObject was passed an object that it never allocated!"); + *obj = GetDefaultObject(); // necessary so that e.g. if (obj) is holding any Refs, it will release them now + obj->SetManager(NULL); + +#ifdef DISABLE_OBJECT_POOLING + delete obj; +#else + if (_mutex.Lock() == B_NO_ERROR) + { + ObjectSlab * slabToDelete = ReleaseObjectAux(obj); + _mutex.Unlock(); + delete slabToDelete; // do this outside the critical section, for better concurrency + } + else WARN_OUT_OF_MEMORY; // critical error -- not really out of memory but still +#endif + } + } + + /** AbstractObjectGenerator API: Useful for polymorphism */ + virtual void * ObtainObjectGeneric() {return ObtainObject();} + + /** AbstractObjectRecycler API: Useful for polymorphism */ + virtual void RecycleObject(void * obj) {ReleaseObject((Object *)obj);} + + /** Implemented to call Drain() and return the number of objects drained. */ + virtual uint32 FlushCachedObjects() {uint32 ret = 0; (void) Drain(&ret); return ret;} + + /** Removes all "spare" objects from the pool and deletes them. + * This method is thread-safe. + * @param optSetNumDrained If non-NULL, this value will be set to the number of objects destroyed. + * @returns B_NO_ERROR on success, or B_ERROR if it couldn't lock the lock for some reason. + */ + status_t Drain(uint32 * optSetNumDrained = NULL) + { + if (_mutex.Lock() == B_NO_ERROR) + { + // This will be our linked list of slabs to delete, later + ObjectSlab * toDelete = NULL; + + // Pull out all the slabs that are not currently in use + ObjectSlab * slab = _firstSlab; + while(slab) + { + ObjectSlab * nextSlab = slab->GetNext(); + if (slab->IsInUse() == false) + { + slab->RemoveFromSlabList(); + slab->SetNext(toDelete); + toDelete = slab; + _curPoolSize -= NUM_OBJECTS_PER_SLAB; + } + slab = nextSlab; + } + (void) _mutex.Unlock(); + + // Do the actual slab deletions outside of the critical section, for better concurrency + uint32 numObjectsDeleted = 0; + while(toDelete) + { + ObjectSlab * nextSlab = toDelete->GetNext(); + numObjectsDeleted += NUM_OBJECTS_PER_SLAB; + + delete toDelete; + toDelete = nextSlab; + } + + if (optSetNumDrained) *optSetNumDrained = numObjectsDeleted; + return B_NO_ERROR; + } + else return B_ERROR; + } + + /** Returns the maximum number of "spare" objects that will be kept + * in the pool, ready to be recycled. This is the value that was + * previously set either in the constructor or by SetMaxPoolSize(). + */ + uint32 GetMaxPoolSize() const {return _maxPoolSize;} + + /** Sets a new maximum size for this pool. Note that changing this + * value will not cause any object to be added or removed to the + * pool immediately; rather the new size will be enforced only + * on future operations. + */ + void SetMaxPoolSize(uint32 mps) {_maxPoolSize = mps;} + + /** Returns a read-only reference to a persistent Object that is default-constructed. */ + const Object & GetDefaultObject() const {return GetDefaultObjectForType();} + + /** Returns the total number of bytes currently taken up by this ObjectPool. + * The returned value is computed by calling GetTotalDataSize() on each object + * in turn (including objects in use and objects in reserve). Note that this + * method will only compile if the Object type has a GetTotalDataSize() method. + */ + uint32 GetTotalDataSize() const + { + uint32 ret = sizeof(*this); + if (_mutex.Lock() == B_NO_ERROR) + { + ObjectSlab * slab = _firstSlab; + while(slab) + { + ret += slab->GetTotalDataSize(); + slab = slab->GetNext(); + } + _mutex.Unlock(); + } + return ret; + } + + /** Returns the total number of items currently allocated by this ObjectPool. + * Note that the returned figure includes both objects in use and objects in reserve. + */ + uint32 GetNumAllocatedItemSlots() const + { + uint32 ret = 0; + if (_mutex.Lock() == B_NO_ERROR) + { + ObjectSlab * slab = _firstSlab; + while(slab) + { + ret += NUM_OBJECTS_PER_SLAB; + slab = slab->GetNext(); + } + _mutex.Unlock(); + } + return ret; + } + +private: + Mutex _mutex; + + class ObjectSlab; + + enum {INVALID_NODE_INDEX = ((uint16)-1)}; // the index-version of a NULL pointer + + class ObjectNode : public Object + { + public: + ObjectNode() : _arrayIndex(INVALID_NODE_INDEX), _nextIndex(INVALID_NODE_INDEX) {/* empty */} + + void SetArrayIndex(uint16 arrayIndex) {_arrayIndex = arrayIndex;} + uint16 GetArrayIndex() const {return _arrayIndex;} + + void SetNextIndex(uint16 nextIndex) {_nextIndex = nextIndex;} + uint16 GetNextIndex() const {return _nextIndex;} + + const char * GetObjectClassName() const {return typeid(Object).name();} + + private: + uint16 _arrayIndex; + uint16 _nextIndex; // only valid when we are in the free list + }; + + friend class ObjectSlab; // for VC++ compatibility, this must be here + + /** The other member items of the ObjectSlab class are held here, so that we can call sizeof(ObjectSlabData) to determine the proper array length */ + class ObjectSlabData + { + public: + ObjectSlabData(ObjectPool * pool) : _pool(pool), _prev(NULL), _next(NULL), _firstFreeNodeIndex(INVALID_NODE_INDEX), _numNodesInUse(0) {/* empty */} + + /** Returns true iff there is at least one ObjectNode available in this slab. */ + bool HasAvailableNodes() const {return (_firstFreeNodeIndex != INVALID_NODE_INDEX);} + + /** Returns true iff there is at least one ObjectNode in use in this slab. */ + bool IsInUse() const {return (_numNodesInUse > 0);} + + void RemoveFromSlabList() + { + (_prev ? _prev->_data._next : _pool->_firstSlab) = _next; + (_next ? _next->_data._prev : _pool->_lastSlab) = _prev; + } + + void AppendToSlabList(ObjectSlab * slab) + { + _prev = _pool->_lastSlab; + _next = NULL; + (_prev ? _prev->_data._next : _pool->_firstSlab) = _pool->_lastSlab = slab; + } + + void PrependToSlabList(ObjectSlab * slab) + { + _prev = NULL; + _next = _pool->_firstSlab; + (_next ? _next->_data._prev : _pool->_lastSlab) = _pool->_firstSlab = slab; + } + + uint16 GetFirstFreeNodeIndex() const {return _firstFreeNodeIndex;} + uint16 GetNumNodesInUse() const {return _numNodesInUse;} + + void PopObjectNode(ObjectNode * node) + { + _firstFreeNodeIndex = node->GetNextIndex(); + ++_numNodesInUse; + node->SetNextIndex(INVALID_NODE_INDEX); // so that it will be seen as 'in use' by the debug/assert code + } + + /** Adds the specified node back to our free-nodes list. */ + void PushObjectNode(ObjectNode * node) + { + node->SetNextIndex(_firstFreeNodeIndex); + _firstFreeNodeIndex = node->GetArrayIndex(); + --_numNodesInUse; + } + + void InitializeObjectNode(ObjectNode * n, uint16 i) + { + n->SetArrayIndex(i); + n->SetNextIndex(_firstFreeNodeIndex); + _firstFreeNodeIndex = i; + } + + void SetNext(ObjectSlab * next) {_next = next;} + ObjectSlab * GetNext() const {return _next;} + + private: + ObjectPool * _pool; + ObjectSlab * _prev; + ObjectSlab * _next; + uint16 _firstFreeNodeIndex; + uint16 _numNodesInUse; + }; + + // All the (int) casts are here so that it the user specifies a slab size of zero, we will get a negative + // number and not a very large positive number that crashes the compiler! + enum { + NUM_OBJECTS_PER_SLAB_AUX = (((int)MUSCLE_POOL_SLAB_SIZE-(int)sizeof(ObjectSlabData))/(int)sizeof(ObjectNode)), + NUM_OBJECTS_PER_SLAB = (NUM_OBJECTS_PER_SLAB_AUX>1)?NUM_OBJECTS_PER_SLAB_AUX:1 + }; + + class ObjectSlab + { + public: + // Note that _prev and _next are deliberately not set here... we don't use them until we are added to the list + ObjectSlab(ObjectPool * pool) : _data(pool) + { + MASSERT((reinterpret_cast(_nodes) == this), "ObjectSlab: _nodes array isn't located at the beginning of the ObjectSlab! ReleaseObjectAux()'s reinterpret_cast won't work correctly!"); + for (uint16 i=0; iGetNextIndex() == INVALID_NODE_INDEX) printf(" " UINT32_FORMAT_SPEC "/" UINT32_FORMAT_SPEC ": %s %p is possibly still in use?\n", i, (uint32)NUM_OBJECTS_PER_SLAB, GetObjectClassName(), n); + } + } + + const char * GetObjectClassName() const {return _nodes[0].GetObjectClassName();} + + bool HasAvailableNodes() const {return _data.HasAvailableNodes();} + bool IsInUse() const {return _data.IsInUse();} + void RemoveFromSlabList() {_data.RemoveFromSlabList();} + void AppendToSlabList() {_data.AppendToSlabList(this);} + void PrependToSlabList() {_data.PrependToSlabList(this);} + + uint32 GetTotalDataSize() const + { + uint32 ret = sizeof(_data); + for (uint32 i=0; iHasAvailableNodes())) + { + ret = _firstSlab->ObtainObjectNode(); + if ((_firstSlab->HasAvailableNodes() == false)&&(_firstSlab != _lastSlab)) + { + // Move _firstSlab out of the way (to the end of the slab list) for next time + ObjectSlab * tmp = _firstSlab; // use temp var since _firstSlab will change + tmp->RemoveFromSlabList(); + tmp->AppendToSlabList(); + } + } + else + { + // Hmm, we must have run out out of non-full slabs. Create a new slab and use it. + ObjectSlab * slab = newnothrow ObjectSlab(this); + if (slab) + { + ret = slab->ObtainObjectNode(); // guaranteed not to fail, since slab is new + if (slab->HasAvailableNodes()) slab->PrependToSlabList(); + else slab->AppendToSlabList(); // could happen, if NUM_OBJECTS_PER_SLAB==1 + _curPoolSize += NUM_OBJECTS_PER_SLAB; + } + // we'll do the WARN_OUT_OF_MEMORY below, outside the mutex lock + } + if (ret) --_curPoolSize; + return ret; + } + + // Must be called with _mutex locked! Returns either NULL, or a pointer + // an ObjectSlab that should be deleted outside of the critical section. + ObjectSlab * ReleaseObjectAux(Object * obj) + { + ObjectNode * objNode = static_cast(obj); + ObjectSlab * objSlab = reinterpret_cast(objNode-objNode->GetArrayIndex()); + + objSlab->ReleaseObjectNode(objNode); // guaranteed to work, since we know (obj) is in use in (objSlab) + + if ((++_curPoolSize > (_maxPoolSize+NUM_OBJECTS_PER_SLAB))&&(objSlab->IsInUse() == false)) + { + _curPoolSize -= NUM_OBJECTS_PER_SLAB; + objSlab->RemoveFromSlabList(); + return objSlab; + } + else if (objSlab != _firstSlab) + { + objSlab->RemoveFromSlabList(); + objSlab->PrependToSlabList(); + } + return NULL; + } + + uint32 _curPoolSize; // tracks the current number of "available" objects + uint32 _maxPoolSize; // the maximum desired number of "available" objects + ObjectSlab * _firstSlab; + ObjectSlab * _lastSlab; +}; + +}; // end namespace muscle + +#endif diff --git a/util/PointerAndBool.h b/util/PointerAndBool.h new file mode 100644 index 00000000..faa28835 --- /dev/null +++ b/util/PointerAndBool.h @@ -0,0 +1,126 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MusclePointerAndBool_h +#define MusclePointerAndBool_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** This class abuses the fact that objects are word-aligned on most hardware to allow us to store + * a boolean value in the low bit of the pointer. This is useful for reducing the amount of memory + * space a class object takes up -- on a 64-bit system it saves 7 and 7/8ths bytes for each + * pointer+boolean pair used. If you are compiling on a system where pointers-to-objects are not + * required to be aligned to even addresses, you can define MUSCLE_AVOID_BITSTUFFING to force the + * boolean to declared as a separate member variable (at the cost of increased memory usage, of course). + */ +template class PointerAndBool +{ +public: + /** Default constructor. */ + PointerAndBool() : _pointer(NULL) +#ifdef MUSCLE_AVOID_BITSTUFFING + , _bool(false) +#endif + { + // empty + } + + /** Constructor. + * @param pointerVal the pointer value to hold + * @param boolVal boolVal the boolean value to hold. + */ + PointerAndBool(T * pointerVal, bool boolVal) +#ifdef MUSCLE_AVOID_BITSTUFFING + : _pointer(pointerVal), _bool(boolVal) +#endif + { +#ifndef MUSCLE_AVOID_BITSTUFFING + SetPointerAndBool(pointerVal, boolVal); +#endif + } + + /** Sets our held pointer value to the specified value. + * @param pointerVal the new pointer value to store. Must be an even value unless MUSCLE_AVOID_BITSTUFFING is defined. + */ + void SetPointer(T * pointerVal) {SetPointerAndBool(pointerVal, GetBool());} + + /** Returns our held pointer value, as previous set in the constructor or via SetPointer(). */ + T * GetPointer() const + { +#ifdef MUSCLE_AVOID_BITSTUFFING + return _pointer; +#else + return WithLowBitCleared(_pointer); +#endif + } + + /** Sets the boolean value we hold. + * @param boolVal The new boolean value to store. + */ + void SetBool(bool boolVal) {SetPointerAndBool(GetPointer(), boolVal);} + + /** Sets both the pointer value and the boolean value at the same time. + * @param pointer The new pointer value to store. + * @param boolVal The new boolean value to store. + */ + void SetPointerAndBool(T * pointer, bool boolVal) + { +#ifdef MUSCLE_AVOID_BITSTUFFING + _pointer = pointer; + _bool = boolVal; +#else + CheckAlignment(pointer); + _pointer = boolVal ? WithLowBitSet(pointer) : pointer; +#endif + } + + /** Returns the boolean value we hold. */ + bool GetBool() const + { +#ifdef MUSCLE_AVOID_BITSTUFFING + return _bool; +#else + return IsLowBitSet(_pointer); +#endif + } + + /** Returns this PointerAndBool to its default state (NULL, false) */ + void Reset() + { + _pointer = NULL; +#ifdef MUSCLE_AVOID_BITSTUFFING + _bool = false; +#endif + } + + /** Swaps this object's state with the state of (rhs) */ + void SwapContents(PointerAndBool & rhs) + { + muscleSwap(_pointer, rhs._pointer); +#ifdef MUSCLE_AVOID_BITSTUFFING + muscleSwap(_bool, rhs._bool); +#endif + } + +private: +#ifndef MUSCLE_AVOID_BITSTUFFING + T * WithLowBitSet( T * ptr) const {return (T*)(((uintptr)ptr)| ((uintptr)0x1));} + T * WithLowBitCleared( T * ptr) const {return (T*)(((uintptr)ptr)&~((uintptr)0x1));} + bool IsLowBitSet( const T * ptr) const {return ((((uintptr)ptr)&((uintptr)0x1))!=0);} + void CheckAlignment( const T * ptr) const + { + (void) ptr; // avoid compiler warning if MASSERT has been defined to a no-op + MASSERT((IsLowBitSet(ptr) == false), "Unaligned pointer detected! PointerAndBool's bit-stuffing code can't handle that. Either align your pointers to even memory addresses, or recompile with -DMUSCLE_AVOID_BITSTUFFING."); + } +#endif + + T * _pointer; +#ifdef MUSCLE_AVOID_BITSTUFFING + bool _bool; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/util/PulseNode.cpp b/util/PulseNode.cpp new file mode 100644 index 00000000..457524a6 --- /dev/null +++ b/util/PulseNode.cpp @@ -0,0 +1,176 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "util/PulseNode.h" + +namespace muscle { + +PulseNode :: PulseNode() : _parent(NULL), _aggregatePulseTime(MUSCLE_TIME_NEVER), _myScheduledTime(MUSCLE_TIME_NEVER), _cycleStartedAt(0), _myScheduledTimeValid(false), _curList(-1), _prevSibling(NULL), _nextSibling(NULL), _maxTimeSlice(MUSCLE_TIME_NEVER), _timeSlicingSuggested(false) +{ + for (uint32 i=0; iRemovePulseChild(this); + ClearPulseChildren(); +} + +uint64 PulseNode :: GetPulseTime(const PulseArgs &) +{ + return MUSCLE_TIME_NEVER; +} + +void PulseNode :: Pulse(const PulseArgs &) +{ + // empty +} + +void PulseNode :: InvalidatePulseTime(bool clearPrevResult) +{ + if (_myScheduledTimeValid) + { + _myScheduledTimeValid = false; + if (clearPrevResult) _myScheduledTime = MUSCLE_TIME_NEVER; + if (_parent) _parent->ReschedulePulseChild(this, LINKED_LIST_NEEDSRECALC); + } +} + +void PulseNode :: GetPulseTimeAux(uint64 now, uint64 & min) +{ + // First, update myself, if necessary... + if (_myScheduledTimeValid == false) + { + _myScheduledTimeValid = true; + _myScheduledTime = GetPulseTime(PulseArgs(now, _myScheduledTime)); + } + + // Then handle any of my kids who need to be recalculated also + PulseNode * & firstNeedy = _firstChild[LINKED_LIST_NEEDSRECALC]; + if (firstNeedy) while(firstNeedy) firstNeedy->GetPulseTimeAux(now, min); // guaranteed to move (firstNeedy) out of the recalc list! + + // Recalculate our effective pulse time + uint64 oldAggregatePulseTime = _aggregatePulseTime; + _aggregatePulseTime = muscleMin(_myScheduledTime, GetFirstScheduledChildTime()); + if ((_parent)&&((_curList == LINKED_LIST_NEEDSRECALC)||(_aggregatePulseTime != oldAggregatePulseTime))) _parent->ReschedulePulseChild(this, (_aggregatePulseTime==MUSCLE_TIME_NEVER)?LINKED_LIST_UNSCHEDULED:LINKED_LIST_SCHEDULED); + + // Update the caller's minimum time value + if (_aggregatePulseTime < min) min = _aggregatePulseTime; +} + +void PulseNode :: PulseAux(uint64 now) +{ + if ((_myScheduledTimeValid)&&(now >= _myScheduledTime)) + { + Pulse(PulseArgs(now, _myScheduledTime)); + _myScheduledTimeValid = false; + } + + PulseNode * p = _firstChild[LINKED_LIST_SCHEDULED]; + while((p)&&(now >= p->_aggregatePulseTime)) + { + p->PulseAux(now); // guaranteed to move (p) to our NEEDSRECALC list + p = _firstChild[LINKED_LIST_SCHEDULED]; // and move on to the next scheduled child + } + + // Make sure we get recalculated no matter what (because we know something happened) + if (_parent) _parent->ReschedulePulseChild(this, LINKED_LIST_NEEDSRECALC); +} + +status_t PulseNode :: PutPulseChild(PulseNode * child) +{ + if (child->_parent) child->_parent->RemovePulseChild(child); + child->_parent = this; + ReschedulePulseChild(child, LINKED_LIST_NEEDSRECALC); + return B_NO_ERROR; +} + +status_t PulseNode :: RemovePulseChild(PulseNode * child) +{ + if (child->_parent == this) + { + bool doResched = (child == _firstChild[LINKED_LIST_SCHEDULED]); + ReschedulePulseChild(child, -1); + child->_parent = NULL; + child->_myScheduledTimeValid = false; + if ((doResched)&&(_parent)) _parent->ReschedulePulseChild(this, LINKED_LIST_NEEDSRECALC); + return B_NO_ERROR; + } + else return B_ERROR; +} + +void PulseNode :: ClearPulseChildren() +{ + for (uint32 i=0; i_curList; + if ((whichList != cl)||(cl == LINKED_LIST_SCHEDULED)) // since we may need to move within the scheduled list + { + // First, remove the child from any list he may currently be in + if (cl >= 0) + { + if (child->_prevSibling) child->_prevSibling->_nextSibling = child->_nextSibling; + if (child->_nextSibling) child->_nextSibling->_prevSibling = child->_prevSibling; + if (child == _firstChild[cl]) _firstChild[cl] = child->_nextSibling; + if (child == _lastChild[cl]) _lastChild[cl] = child->_prevSibling; + child->_prevSibling = child->_nextSibling = NULL; + } + + child->_curList = whichList; + switch(whichList) + { + case LINKED_LIST_SCHEDULED: + { + PulseNode * p = _firstChild[whichList]; + if (p) + { + if (child->_aggregatePulseTime >= _lastChild[whichList]->_aggregatePulseTime) + { + // Shortcut: append to the tail of the list! + child->_prevSibling = _lastChild[whichList]; + _lastChild[whichList]->_nextSibling = child; + _lastChild[whichList] = child; + } + else + { + // Worst case: O(N) walk through the list to find the place to insert (child) + while(p->_aggregatePulseTime < child->_aggregatePulseTime) p = p->_nextSibling; + + // insert (child) just before (p) + child->_nextSibling = p; + child->_prevSibling = p->_prevSibling; + if (p->_prevSibling) p->_prevSibling->_nextSibling = child; + else _firstChild[whichList] = child; // FogBugz #4092(b) + p->_prevSibling = child; + } + } + else _firstChild[whichList] = _lastChild[whichList] = child; + } + break; + + case LINKED_LIST_NEEDSRECALC: + if (_parent) _parent->ReschedulePulseChild(this, LINKED_LIST_NEEDSRECALC); // if our child is rescheduled that reschedules us too! + case LINKED_LIST_UNSCHEDULED: + { + // These lists are unsorted, so we can just quickly append the child to the head of the list + if (_firstChild[whichList]) + { + child->_nextSibling = _firstChild[whichList]; + _firstChild[whichList]->_prevSibling = child; + _firstChild[whichList] = child; + } + else _firstChild[whichList] = _lastChild[whichList] = child; + } + break; + + default: + // do nothing + break; + } + } +} + +}; // end namespace muscle diff --git a/util/PulseNode.h b/util/PulseNode.h new file mode 100644 index 00000000..79aa69fa --- /dev/null +++ b/util/PulseNode.h @@ -0,0 +1,237 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MusclePulseNode_h +#define MusclePulseNode_h + +#include "util/TimeUtilityFunctions.h" +#include "util/CountedObject.h" + +namespace muscle { + +class PulseNode; +class PulseNodeManager; + +/** Interface class for any object that can schedule Pulse() calls for itself via + * its PulseNodeManager. (Typically the PulseNodeManager role is played by + * the ReflectServer class) + */ +class PulseNode : private CountedObject +{ +public: + /** Default constructor */ + PulseNode(); + + /** Destructor. Does not delete any attached child PulseNodes. */ + virtual ~PulseNode(); + +protected: + /** This class is used to encapsulate the arguments to GetPulseTime() and Pulse(), so that + * there is less data to place on the stack in each callback call. + */ + class PulseArgs + { + public: + /** Returns the approximate time (in microseconds) at which our Pulse() method was called. + * Calling this method is cheaper than calling GetRunTime64() directly. + */ + uint64 GetCallbackTime() const {return _callTime;} + + /** Returns the time (in microseconds) at which our Pulse() method was supposed to be called at. + * Note that the actual call time (as returned by GetCallbackTime() will generally be a bit larger + * than the value returned by GetScheduledTime(), as computers are not infinitely fast and therefore + * they will have some latency before scheduled calls are executed. + */ + uint64 GetScheduledTime() const {return _prevTime;} + + private: + friend class PulseNode; + + PulseArgs(uint64 callTime, uint64 prevTime) : _callTime(callTime), _prevTime(prevTime) {/* empty */} + + uint64 _callTime; + uint64 _prevTime; + }; + +public: + /** + * This method can be overridden to tell the PulseNodeManager when we would like + * to have our Pulse() method called. This method is guaranteed to be called only + * during the following times: + *
    + *
  1. When the PulseNode is first probed by the PulseNodeManager
  2. + *
  3. Immediately after our Pulse() method has returned
  4. + *
  5. Soon after InvalidatePulseTime() has been called one or more times + * (InvalidatePulseTime() calls are merged together for efficiency)
  6. + *
+ * The default implementation always returns MUSCLE_TIME_NEVER. + * + * @param args Args is a reference to an object containing the following context + * information regarding this call: + * + * args.GetCallbackTime() The current wall-clock time-value, in microseconds. + * This will be roughly the same value as returned by + * GetRunTime64(), but it's cheaper to call this method. + * args.GetScheduledTime() The value that this method returned the last time it was + * called. The very first time this method is called, this value + * will be passed in as MUSCLE_TIME_NEVER. + * @return Return MUSCLE_TIME_NEVER if you don't wish to schedule a future call to Pulse(); + * or return the time at which you want Pulse() to be called. Returning values less + * than or equal to (now) will cause Pulse() to be called as soon as possible. + */ + virtual uint64 GetPulseTime(const PulseArgs & args); + + /** + * Will be called at the time specified previously by GetPulseTime(). GetPulseTime() + * will be called again immediately after this call, to check if you want to schedule + * another Pulse() call for later. + * Default implementation is a no-op. + * + * @param args Args is a reference to an object containing the following context + * information regarding this call: + * + * args.GetCallbackTime() The current wall-clock time-value, in microseconds. + * This will be roughly the same value as returned by + * GetRunTime64(), but it's cheaper to call this method. + * args.GetScheduledTime() The time this Pulse() call was scheduled to occur at, in + * microseconds, as previously returned by GetPulseTime(). Note + * that unless your computer is infinitely fast, this time will + * always be at least a bit less than (now), since there is a delay + * between when the program gets woken up to service the next Pulse() + * call, and when the call actually happens. (you may be able to + * use this value to compensate for the slippage, if it bothers you) + */ + virtual void Pulse(const PulseArgs & args); + + /** + * Adds the given child into our set of child PulseNodes. Any PulseNode in our + * set of children will have its pulsing needs taken care of by us, but it is + * not considered "owned" by this PulseNode--it will not be deleted when we are. + * @param child The child to place into our set of child PulseNodes. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory). + */ + status_t PutPulseChild(PulseNode * child); + + /** Attempts to remove the given child from our set of child PulseNodes. + * @param child The child to remove + * @returns B_NO_ERROR on success, or B_ERROR on failure (child wasn't in our set) + */ + status_t RemovePulseChild(PulseNode * child); + + /** Removes all children from our set of child PulseNodes */ + void ClearPulseChildren(); + + /** Returns true iff the given child is in our set of child PulseNodes. + * @param child the child to look for. + */ + bool ContainsPulseChild(PulseNode * child) const {return ((child)&&(child->_parent == this));} + + /** Returns when this object wants its call to Pulse() scheduled next, or MUSCLE_TIME_NEVER + * if it has no call to Pulse() currently scheduled. + */ + uint64 GetScheduledPulseTime() const {return _myScheduledTime;} + + /** Returns the run-time at which the PulseNodeManager started calling our callbacks. + * Useful for any object that wants to limit the maximum duration of its timeslice + * in the PulseNodeManager's event loop. + */ + uint64 GetCycleStartTime() const {return _parent ? _parent->GetCycleStartTime() : _cycleStartedAt;} + + /** Sets the maximum number of microseconds that this class should allow its callback + * methods to execute for (relative to the cycle start time, as shown above). Note + * that this value is merely a suggestion; it remains up to the subclass's callback + * methods to ensure that the suggestion is actually followed. + * @param maxUsecs Maximum number of microseconds that the time slice should last for. + * If set to MUSCLE_TIME_NEVER, that indicates that there is no suggested limit. + */ + void SetSuggestedMaximumTimeSlice(uint64 maxUsecs) {_maxTimeSlice = maxUsecs; _timeSlicingSuggested = (_maxTimeSlice != MUSCLE_TIME_NEVER);} + + /** Returns the current suggested maximum duration of our time slice, or MUSCLE_TIME_NEVER + * if there is no suggested limit. Default value is MUSCLE_TIME_NEVER. + */ + uint64 GetSuggestedMaximumTimeSlice() const {return _maxTimeSlice;} + + /** Convenience method -- returns true iff the current value of the run-time + * clock (GetRunTime64()) indicates that our suggested time slice has expired. + * This method is cheap to call often. + */ + bool IsSuggestedTimeSliceExpired() const {return ((_timeSlicingSuggested)&&(GetRunTime64() >= (_cycleStartedAt+_maxTimeSlice)));} + + /** + * Sets a flag to indicate that GetPulseTime() should be called on this object. + * Call this whenever you've decided to reschedule your pulse time outside + * of a Pulse() event. + * @param clearPrevResult if true, this call will also clear the stored prevResult + * value, so that the next time GetPulseTime() is called, + * prevResult is passed in as MUSCLE_TIME_NEVER. If false, + * the prevResult value will be left alone. + */ + void InvalidatePulseTime(bool clearPrevResult = true); + + /** Returns a pointer to this PulseNode's parent PulseNode, if any. */ + PulseNode * GetPulseParent() const {return _parent;} + +private: + void ReschedulePulseChild(PulseNode * child, int toList); + uint64 GetFirstScheduledChildTime() const {return _firstChild[LINKED_LIST_SCHEDULED] ? _firstChild[LINKED_LIST_SCHEDULED]->_aggregatePulseTime : MUSCLE_TIME_NEVER;} + void GetPulseTimeAux(uint64 now, uint64 & min); + void PulseAux(uint64 now); + + // Sets the cycle-started-at time for this object, as returned by GetCycleStartTime(). + void SetCycleStartTime(uint64 st) {_cycleStartedAt = st;} + + PulseNode * _parent; + + uint64 _aggregatePulseTime; // time when we need PulseAux() to be called next + uint64 _myScheduledTime; // time when our own personal Pulse() should be called next + uint64 _cycleStartedAt; // time when the PulseNodeManager started serving us. + bool _myScheduledTimeValid; // true iff _myScheduledTime doesn't need to be recalculated + + // Linked list that this node is in (or NULL if we're not in any linked list) + int _curList; // index of the list we are part of, or -1 if we're not in any list + PulseNode * _prevSibling; + PulseNode * _nextSibling; + + enum { + LINKED_LIST_SCHEDULED = 0, // list of children with known upcoming pulse-times (sorted) + LINKED_LIST_UNSCHEDULED, // list of children with known MUSCLE_TIME_NEVER pulse-times (unsorted) + LINKED_LIST_NEEDSRECALC, // list of children whose pulse-times need to be recalculated (unsorted) + NUM_LINKED_LISTS + }; + + // Linked list of child nodes with finite pulse times, sorted by their pulse times + PulseNode * _firstChild[NUM_LINKED_LISTS]; + PulseNode * _lastChild[NUM_LINKED_LISTS]; + + uint64 _maxTimeSlice; + bool _timeSlicingSuggested; + + friend class PulseNodeManager; +}; + +/** Subclasses of this class are allowed to manage PulseNode objects by + * calling their GetPulseTimeAux() and PulseAux() methods (indirectly). + * Most code won't need to use this class. + */ +class PulseNodeManager +{ +public: + /** Default constructor */ + PulseNodeManager() {/* empty */} + + /** Destructor */ + ~PulseNodeManager() {/* empty */} + +protected: + /** Passes the call on through to the given PulseNode */ + inline void CallGetPulseTimeAux(PulseNode & p, uint64 now, uint64 & min) const {p.GetPulseTimeAux(now, min);} + + /** Passes the call on through to the given PulseNode */ + inline void CallPulseAux(PulseNode & p, uint64 now) const {if (now >= p._aggregatePulseTime) p.PulseAux(now);} + + /** Passes the call on through to the given PulseNode */ + inline void CallSetCycleStartTime(PulseNode & p, uint64 now) const {p.SetCycleStartTime(now);} +}; + +}; // end namespace muscle + +#endif diff --git a/util/Queue.h b/util/Queue.h new file mode 100644 index 00000000..1632e57d --- /dev/null +++ b/util/Queue.h @@ -0,0 +1,1640 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleQueue_h +#define MuscleQueue_h + +#include "support/MuscleSupport.h" + +#ifdef MUSCLE_USE_CPLUSPLUS11 +# include +#endif + +namespace muscle { + +#ifndef SMALL_QUEUE_SIZE +# define SMALL_QUEUE_SIZE 3 +#endif + +#ifdef MUSCLE_USE_CPLUSPLUS11 +// Enable move semantics (when possible) for C++11 +# define QQ_UniversalSinkItemRef template +# define QQ_SinkItemParam ItemParam && +# define QQ_PlunderItem(item) std::move(item) +# define QQ_ForwardItem(item) std::forward(item) +#else +// For earlier versions of C++, use the traditional copy/ref semantics +# define QQ_UniversalSinkItemRef +# define QQ_SinkItemParam const ItemType & +# define QQ_PlunderItem(item) (item) +# define QQ_ForwardItem(item) (item) +#endif + +/** This class implements a templated double-ended-queue data structure. + * Adding or removing items from the head or tail of a Queue is (on average) + * an O(1) operation. A Queue can also serve as a reasonably efficient resizable-array + * class (aka Vector) if that is all you need. + */ +template class Queue +{ +public: + /** Default constructor. */ + Queue(); + + /** Copy constructor. */ + Queue(const Queue& copyMe); + + /** Destructor. */ + virtual ~Queue(); + + /** Assigment operator. */ + Queue & operator=(const Queue & from); + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** Initialize list Constructor */ + Queue(const std::initializer_list & list) : _queue(NULL), _queueSize(0), _itemCount(0) {(void) AddTailMulti(list);} + + /** C++11 Move Constructor */ + Queue(Queue && rhs) : _queue(NULL), _queueSize(0), _itemCount(0) {if (rhs._queue == rhs._smallQueue) *this = rhs; else SwapContents(rhs);} + + /** C++11 Move Assignment Operator */ + Queue & operator =(Queue && rhs) {if (rhs._queue == rhs._smallQueue) *this = rhs; else SwapContents(rhs); return *this;} +#endif + + /** Equality operator. Queues are equal if they are the same length, and + * every nth item in this queue is == to the corresponding item in (rhs). */ + bool operator==(const Queue & rhs) const; + + /** Returns the negation of the equality operator */ + bool operator!=(const Queue & rhs) const {return !(*this == rhs);} + + /** Similar to the assignment operator, except this method returns a status code. + * @param rhs This Queue's contents will become a copy of (rhs)'s items. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?). + */ + status_t CopyFrom(const Queue & rhs); + + /** Appends (item) to the end of the queue. Queue size grows by one. + * @param item The item to append. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + QQ_UniversalSinkItemRef status_t AddTail(QQ_SinkItemParam item) {return (AddTailAndGet(QQ_ForwardItem(item)) != NULL) ? B_NO_ERROR : B_ERROR;} + + /** As above, except that a default item is appended. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t AddTail() {return (AddTailAndGet() != NULL) ? B_NO_ERROR : B_ERROR;} + + /** Appends some or all items in (queue) to the end of our queue. Queue size + * grows by at most (queue.GetNumItems()). + * For example: + * Queue a; // contains 1, 2, 3, 4 + * Queue b; // contains 5, 6, 7, 8 + * a.AddTail(b); // a now contains 1, 2, 3, 4, 5, 6, 7, 8 + * @param queue The queue to append to our queue. + * @param startIndex Index in (queue) to start adding at. Default to zero. + * @param numItems Number of items to add. If this number is too large, it will be capped appropriately. Default is to add all items. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t AddTailMulti(const Queue & queue, uint32 startIndex = 0, uint32 numItems = MUSCLE_NO_LIMIT); + + /** Adds the given array of items to the tail of the Queue. Equivalent + * to adding them to the tail of the Queue one at a time, but somewhat + * more efficient. On success, the queue size grows by (numItems). + * @param items Pointer to an array of items to add to the Queue. + * @param numItems Number of items in the array + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory) + */ + status_t AddTailMulti(const ItemType * items, uint32 numItems); + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** Available in C++11 only: Appends the items specified in the initializer + * list to this Queue. + * @param list The C++11 initializer list of items (e.g. {1,2,3,4,5} to add. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t AddTailMulti(const std::initializer_list & list) + { + if (EnsureCanAdd(list.size()) != B_NO_ERROR) return B_ERROR; + for (auto i : list) (void) AddTail(i); + return B_NO_ERROR; + } +#endif + + /** Appends (item) to the end of the queue. Queue size grows by one. + * @param item The item to append. + * @return A pointer to the appended item on success, or a NULL pointer on failure. + */ + QQ_UniversalSinkItemRef ItemType * AddTailAndGet(QQ_SinkItemParam item); + + /** As above, except that a default item is appended. + * @return A pointer to the appended item on success, or a NULL pointer on failure. + */ + ItemType * AddTailAndGet(); + + /** Prepends (item) to the head of the queue. Queue size grows by one. + * @param item The item to prepend. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + QQ_UniversalSinkItemRef status_t AddHead(QQ_SinkItemParam item) {return (AddHeadAndGet(QQ_ForwardItem(item)) != NULL) ? B_NO_ERROR : B_ERROR;} + + /** As above, except a default item is prepended. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t AddHead() {return (AddHeadAndGet() != NULL) ? B_NO_ERROR : B_ERROR;} + + /** Concatenates (queue) to the head of our queue. + * Our queue size grows by at most (queue.GetNumItems()). + * For example: + * Queue a; // contains 1, 2, 3, 4 + * Queue b; // contains 5, 6, 7, 8 + * a.AddHead(b); // a now contains 5, 6, 7, 8, 1, 2, 3, 4 + * @param queue The queue to prepend to our queue. + * @param startIndex Index in (queue) to start adding at. Default to zero. + * @param numItems Number of items to add. If this number is too large, it will be capped appropriately. Default is to add all items. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t AddHeadMulti(const Queue & queue, uint32 startIndex = 0, uint32 numItems = MUSCLE_NO_LIMIT); + + /** Concatenates the given array of items to the head of the Queue. + * The semantics are the same as for AddHeadMulti(const Queue &). + * @param items Pointer to an array of items to add to the Queue. + * @param numItems Number of items in the array + * @return B_NO_ERROR on success, or B_ERROR on failure (out of memory) + */ + status_t AddHeadMulti(const ItemType * items, uint32 numItems); + + /** Prepends (item) to the beginning of the queue. Queue size grows by one. + * @param item The item to prepend. + * @return A pointer to the prepend item on success, or a NULL pointer on failure. + */ + QQ_UniversalSinkItemRef ItemType * AddHeadAndGet(QQ_SinkItemParam item); + + /** As above, except that a default item is prepended. + * @return A pointer to the prepend item on success, or a NULL pointer on failure. + */ + ItemType * AddHeadAndGet(); + + /** Removes the item at the head of the queue. + * @return B_NO_ERROR on success, B_ERROR if the queue was empty. + */ + status_t RemoveHead(); + + /** Removes the item at the head of the queue and returns it. + * If the Queue was empty, a default item is returned. + */ + ItemType RemoveHeadWithDefault(); + + /** Removes up to (numItems) items from the head of the queue. + * @param numItems The desired number of items to remove + * @returns the actual number of items removed (may be less than (numItems) if the Queue was too short) + */ + uint32 RemoveHeadMulti(uint32 numItems); + + /** Removes the item at the head of the queue and places it into (returnItem). + * @param returnItem On success, the removed item is copied into this object. + * @return B_NO_ERROR on success, B_ERROR if the queue was empty + */ + status_t RemoveHead(ItemType & returnItem); + + /** Removes the item at the tail of the queue. + * @return B_NO_ERROR on success, B_ERROR if the queue was empty. + */ + status_t RemoveTail(); + + /** Removes the item at the tail of the queue and places it into (returnItem). + * @param returnItem On success, the removed item is copied into this object. + * @return B_NO_ERROR on success, B_ERROR if the queue was empty + */ + status_t RemoveTail(ItemType & returnItem); + + /** Removes the item at the tail of the queue and returns it. + * If the Queue was empty, a default item is returned. + */ + ItemType RemoveTailWithDefault(); + + /** Removes up to (numItems) items from the tail of the queue. + * @param numItems The desired number of items to remove + * @returns the actual number of items removed (may be less than (numItems) if the Queue was too short) + */ + uint32 RemoveTailMulti(uint32 numItems); + + /** Removes the item at the (index)'th position in the queue. + * @param index Which item to remove--can range from zero + * (head of the queue) to GetNumItems()-1 (tail of the queue). + * @return B_NO_ERROR on success, B_ERROR on failure (i.e. bad index) + * Note that this method is somewhat inefficient for indices that + * aren't at the head or tail of the queue (i.e. O(n) time) + */ + status_t RemoveItemAt(uint32 index); + + /** Removes the item at the (index)'th position in the queue, and copies it into (returnItem). + * @param index Which item to remove--can range from zero + * (head of the queue) to (GetNumItems()-1) (tail of the queue). + * @param returnItem On success, the removed item is copied into this object. + * @return B_NO_ERROR on success, B_ERROR on failure (i.e. bad index) + */ + status_t RemoveItemAt(uint32 index, ItemType & returnItem); + + /** Removes the nth item in the Queue and returns it. + * If there was no nth item in the Queue, a default item is returned. + * @param index Index of the item to remove and return. + */ + ItemType RemoveItemAtWithDefault(uint32 index); + + /** Copies the (index)'th item into (returnItem). + * @param index Which item to get--can range from zero + * (head of the queue) to (GetNumItems()-1) (tail of the queue). + * @param returnItem On success, the retrieved item is copied into this object. + * @return B_NO_ERROR on success, B_ERROR on failure (e.g. bad index) + */ + status_t GetItemAt(uint32 index, ItemType & returnItem) const; + + /** Returns a pointer to an item in the array (i.e. no copying of the item is done). + * Included for efficiency; be careful with this: modifying the queue can invalidate + * the returned pointer! + * @param index Index of the item to return a pointer to. + * @return a pointer to the internally held item, or NULL if (index) was invalid. + */ + ItemType * GetItemAt(uint32 index) const {return (index<_itemCount)?GetItemAtUnchecked(index):NULL;} + + /** The same as GetItemAt(), except this version doesn't check to make sure + * (index) is valid. + * @param index Index of the item to return a pointer to. Must be a valid index! + * @return a pointer to the internally held item. The returned value is undefined + * if the index isn't valid, so be careful! + */ + ItemType * GetItemAtUnchecked(uint32 index) const {return &_queue[InternalizeIndex(index)];} + + /** Returns a reference to the (index)'th item in the Queue, if such an item exists, + * or a reference to a default item if it doesn't. Unlike the [] operator, + * it is okay to call this method with any value of (index). + * @param index Which item to return. + */ + const ItemType & GetWithDefault(uint32 index) const {return (index<_itemCount)?(*this)[index]:GetDefaultItem();} + + /** Returns a reference to the (index)'th item in the Queue, if such an item exists, + * or the supplied default item if it doesn't. Unlike the [] operator, + * it is okay to call this method with any value of (index). + * @param index Which item to return. + * @param defItem An item to return if (index) isn't a valid value. + */ + const ItemType & GetWithDefault(uint32 index, const ItemType & defItem) const {return (index<_itemCount)?(*this)[index]:defItem;} + + /** Replaces the (index)'th item in the queue with (newItem). + * @param index Which item to replace--can range from zero + * (head of the queue) to (GetNumItems()-1) (tail of the queue). + * @param newItem The item to place into the queue at the (index)'th position. + * @return B_NO_ERROR on success, B_ERROR on failure (e.g. bad index) + */ + QQ_UniversalSinkItemRef status_t ReplaceItemAt(uint32 index, QQ_SinkItemParam newItem); + + /** As above, except the specified item is replaced with a default item. + * @param index Which item to replace--can range from zero + * (head of the queue) to (GetNumItems()-1) (tail of the queue). + * @return B_NO_ERROR on success, B_ERROR on failure (e.g. bad index) + */ + status_t ReplaceItemAt(uint32 index) {return ReplaceItemAt(index, GetDefaultItem());} + + /** Inserts (item) into the (nth) slot in the array. InsertItemAt(0) + * is the same as AddHead(item), InsertItemAt(GetNumItems()) is the same + * as AddTail(item). Other positions will involve an O(n) shifting of contents. + * @param index The position at which to insert the new item. + * @param newItem The item to insert into the queue. + * @return B_NO_ERROR on success, B_ERROR on failure (i.e. bad index). + */ + QQ_UniversalSinkItemRef status_t InsertItemAt(uint32 index, QQ_SinkItemParam newItem); + + /** As above, except that a default item is inserted. + * @param index The position at which to insert the new item. + * @return B_NO_ERROR on success, B_ERROR on failure (i.e. bad index). + */ + status_t InsertItemAt(uint32 index) {return InsertItemAt(index, GetDefaultItem());} + + /** Inserts some or all of the items in (queue) at the specified position in our queue. + * Queue size grows by at most (queue.GetNumItems()). + * For example: + * Queue a; // contains 1, 2, 3, 4 + * Queue b; // contains 5, 6, 7, 8 + * a.InsertItemsAt(2, b); // a now contains 1, 2, 5, 6, 7, 8, 3, 4 + * @param index The index into this Queue that items from (queue) should be inserted at. + * @param queue The queue whose items are to be inserted into this queue. + * @param startIndex Index in (queue) to start reading items from. Defaults to zero. + * @param numNewItems Number of items to insert. If this number is too large, it will be capped appropriately. Default is to add all items. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t InsertItemsAt(uint32 index, const Queue & queue, uint32 startIndex = 0, uint32 numNewItems = MUSCLE_NO_LIMIT); + + /** Inserts the items pointed at by (items) at the specified position in our queue. + * Queue size grows (numNewItems). + * For example: + * Queue a; // contains 1, 2, 3, 4 + * const int b[] = {5, 6, 7}; + * a.InsertItemsAt(2, b, ARRAYITEMS(b)); // a now contains 1, 2, 5, 6, 7, 3, 4 + * @param index The index into this Queue that items from (queue) should be inserted at. + * @param items Pointer to the items to insert into this queue. + * @param numNewItems Number of items to insert. + * @return B_NO_ERROR on success, B_ERROR on failure (out of memory) + */ + status_t InsertItemsAt(uint32 index, const ItemType * items, uint32 numNewItems); + + /** Removes all items from the queue. + * @param releaseCachedBuffers If true, we will immediately free any buffers that we may be holding. Otherwise + * we will keep them around so that they can be re-used in the future. + */ + void Clear(bool releaseCachedBuffers = false); + + /** This version of Clear() merely sets our held item-count to zero; it doesn't actually modify the + * state of any items in the Queue. This is very efficient, but be careful when using it with non-POD + * types; for example, if you have a Queue and you call FastClear() on it, the Messages + * referenced by the MessageRef objects will not get recycled during the Clear() call because the + * MessageRefs still exist in the Queue's internal array, even though they aren't readily accessible + * anymore. Only call this method if you know what you are doing! + */ + void FastClear() {_itemCount = _headIndex = _tailIndex = 0;} + + /** Returns the number of items in the queue. (This number does not include pre-allocated space) */ + uint32 GetNumItems() const {return _itemCount;} + + /** Returns the total number of item-slots we have allocated space for. Note that this is NOT + * the same as the value returned by GetNumItems() -- it is generally larger, since we often + * pre-allocate additional slots in advance, in order to cut down on the number of re-allocations + * we need to peform. + */ + uint32 GetNumAllocatedItemSlots() const {return _queueSize;} + + /** Returns the number of "extra" (i.e. currently unoccupied) array slots we currently have allocated. + * Attempting to add more than (this many) additional items to this Queue will cause a memory reallocation. + */ + uint32 GetNumUnusedItemSlots() const {return _queueSize-_itemCount;} + + /** Returns the number of bytes of memory taken up by this Queue's data */ + uint32 GetTotalDataSize() const {return sizeof(*this)+(GetNumAllocatedItemSlots()*sizeof(ItemType));} + + /** Convenience method: Returns true iff there are no items in the queue. */ + bool IsEmpty() const {return (_itemCount == 0);} + + /** Convenience method: Returns true iff there is at least one item in the queue. */ + bool HasItems() const {return (_itemCount > 0);} + + /** Returns a read-only reference the head item in the queue. You must not call this when the queue is empty! */ + const ItemType & Head() const {return *GetItemAtUnchecked(0);} + + /** Returns a read-only reference the tail item in the queue. You must not call this when the queue is empty! */ + const ItemType & Tail() const {return *GetItemAtUnchecked(_itemCount-1);} + + /** Returns a read-only reference the head item in the queue, or a default item if the Queue is empty. */ + const ItemType & HeadWithDefault() const {return HasItems() ? Head() : GetDefaultItem();} + + /** Returns a read-only reference the head item in the queue, or to the supplied default item if the Queue is empty. + * @param defaultItem An item to return if the Queue is empty. + */ + const ItemType & HeadWithDefault(const ItemType & defaultItem) const {return HasItems() ? Head() : defaultItem;} + + /** Returns a read-only reference the tail item in the queue, or a default item if the Queue is empty. */ + const ItemType & TailWithDefault() const {return HasItems() ? Tail() : GetDefaultItem();} + + /** Returns a read-only reference the tail item in the queue, or to the supplied default item if the Queue is empty. + * @param defaultItem An item to return if the Queue is empty. + */ + const ItemType & TailWithDefault(const ItemType & defaultItem) const {return HasItems() ? Tail() : defaultItem;} + + /** Returns a writable reference the head item in the queue. You must not call this when the queue is empty! */ + ItemType & Head() {return *GetItemAtUnchecked(0);} + + /** Returns a writable reference the tail item in the queue. You must not call this when the queue is empty! */ + ItemType & Tail() {return *GetItemAtUnchecked(_itemCount-1);} + + /** Returns a pointer to the first item in the queue, or NULL if the queue is empty */ + ItemType * HeadPointer() const {return GetItemAt(0);} + + /** Returns a pointer to the last item in the queue, or NULL if the queue is empty */ + ItemType * TailPointer() const {return GetItemAt(_itemCount-1);} + + /** Convenient read-only array-style operator (be sure to only use valid indices!) */ + const ItemType & operator [](uint32 Index) const; + + /** Convenient read-write array-style operator (be sure to only use valid indices!) */ + ItemType & operator [](uint32 Index); + + /** Makes sure there is enough space allocated for at least (numSlots) items. + * You only need to call this if you wish to minimize the number of data re-allocations done, + * or wish to add or remove a large number of default items at once (by specifying setNumItems=true). + * @param numSlots the minimum amount of items to pre-allocate space for in the Queue. + * @param setNumItems If true, the length of the Queue will be altered by adding or removing + * items to (from) the tail of the Queue until the Queue is the specified size. + * If false (the default), more slots may be pre-allocated, but the + * number of items officially in the Queue remains the same as before. + * @param extraReallocItems If we have to do an array reallocation, this many extra slots will be + * added to the newly allocated array, above and beyond what is strictly + * necessary. That way the likelihood of another reallocation being necessary + * in the near future is reduced. Default value is zero, indicating that + * no extra slots will be allocated. This argument is ignored if (setNumItems) is true. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory) + */ + status_t EnsureSize(uint32 numSlots, bool setNumItems = false, uint32 extraReallocItems = 0) {return EnsureSizeAux(numSlots, setNumItems, extraReallocItems, NULL);} + + /** Convenience wrapper around EnsureSize(): This method ensures that this Queue has enough + * extra space allocated to fit another (numExtras) items without having to do a reallocation. + * If it doesn't, it will do a reallocation so that it does have at least that much extra space. + * @param numExtras How many extra items we want to ensure room for. Defaults to 1. + * @returns B_NO_ERROR if the extra space now exists, or B_ERROR on failure (out of memory?) + */ + status_t EnsureCanAdd(uint32 numExtras = 1) {return EnsureSize(GetNumItems()+numExtras);} + + /** Convenience method -- works the same as IndexOf() but returns a boolean instead of an int32 index. + * @param item The item to look for. + * @param startAt The first index in the list to look at. Defaults to zero. + * @param endAtPlusOne One more than the final index to look at. If this value is greater than + * the number of items in the list, it will be clamped internally to be equal + * to the number of items in the list. Defaults to MUSCLE_NO_LIMIT. + * @return True if the item was found in the specified range, or false otherwise. + */ + bool Contains(const ItemType & item, uint32 startAt = 0, uint32 endAtPlusOne = MUSCLE_NO_LIMIT) const {return (IndexOf(item, startAt, endAtPlusOne) >= 0);} + + /** Returns the first index of the given (item), or -1 if (item) is not found in the list. O(n) search time. + * @param item The item to look for. + * @param startAt The first index in the list to look at. Defaults to zero. + * @param endAtPlusOne One more than the final index to look at. If this value is greater than + * the number of items in the list, it will be clamped internally to be equal + * to the number of items in the list. Defaults to MUSCLE_NO_LIMIT. + * @return The index of the first item found, or -1 if no such item was found in the specified range. + */ + int32 IndexOf(const ItemType & item, uint32 startAt = 0, uint32 endAtPlusOne = MUSCLE_NO_LIMIT) const; + + /** Returns the last index of the given (item), or -1 if (item) is not found in the list. O(n) search time. + * This method is different from IndexOf() in that this method searches backwards in the list. + * @param item The item to look for. + * @param startAt The initial index in the list to look at. If this value is greater than or equal to + * the size of the list, it will be clamped down to (numItems-1). Defaults to MUSCLE_NO_LIMIT. + * @param endAt The final index in the list to look at. Defaults to zero, which means + * to search back to the beginning of the list, if necessary. + * @return The index of the first item found in the reverse search, or -1 if no such item was found in the specified range. + */ + int32 LastIndexOf(const ItemType & item, uint32 startAt = MUSCLE_NO_LIMIT, uint32 endAt = 0) const; + + /** + * Swaps the values of the two items at the given indices. This operation + * will involve three copies of the held items. + * @param fromIndex First index to swap. + * @param toIndex Second index to swap. + */ + void Swap(uint32 fromIndex, uint32 toIndex); + + /** + * Reverses the ordering of the items in the given subrange. + * (e.g. if the items were A,B,C,D,E, this would change them to E,D,C,B,A) + * @param from Index of the start of the subrange. Defaults to zero. + * @param to Index of the next item after the end of the subrange. If greater than + * the number of items currently in the queue, this value will be clipped + * to be equal to that number. Defaults to the largest possible uint32. + */ + void ReverseItemOrdering(uint32 from=0, uint32 to = MUSCLE_NO_LIMIT); + + /** + * Does an in-place, stable sort on a subrange of the contents of this Queue. (The sort algorithm is a smart, in-place merge sort) + * @param compareFunctor the item-comparison functor to use when sorting the items in the Queue. + * @param from Index of the start of the subrange. Defaults to zero. + * @param to Index of the next item after the end of the subrange. + * If greater than the number of items currently in the queue, + * the subrange will extend to the last item. Defaults to the largest possible uint32. + * @param optCookie A user-defined value that will be passed to the comparison functor. + */ + template void Sort(const CompareFunctorType & compareFunctor, uint32 from=0, uint32 to = MUSCLE_NO_LIMIT, void * optCookie = NULL); + + /** + * Same as above, except that the default item-comparison functor for our ItemType is implicitly used. + * @param from Index of the start of the subrange. Defaults to zero. + * @param to Index of the next item after the end of the subrange. + * If greater than the number of items currently in the queue, + * the subrange will extend to the last item. Defaults to the largest possible uint32. + * @param optCookie A user-defined value that will be passed to the comparison functor. + */ + void Sort(uint32 from=0, uint32 to = MUSCLE_NO_LIMIT, void * optCookie = NULL) {Sort(CompareFunctor(), from, to, optCookie);} + + /** + * Inserts the specified item at the position necessary to keep the Queue in sorted order. + * Note that this method assumes the Queue is already in sorted order before the insertion. + * @param item The item to insert into the Queue. + * @param optCookie optional value to pass to the comparison functor. Defaults to NULL. + * @returns The index at which the item was inserted on success, or -1 on failure (out of memory)? + */ + QQ_UniversalSinkItemRef int32 InsertItemAtSortedPosition(QQ_SinkItemParam item, void * optCookie = NULL) {return InsertItemAtSortedPosition(CompareFunctor(), item, optCookie);} + + /** + * Inserts the specified item at the position necessary to keep the Queue in sorted order. + * Note that this method assumes the Queue is already in sorted order before the insertion. + * @param compareFunctor a functor object whose Compare() method is called to do item comparisons. + * @param item The item to insert into the Queue. + * @param optCookie optional value to pass to the comparison functor. Defaults to NULL. + * @returns The index at which the item was inserted on success, or -1 on failure (out of memory)? + */ + QQ_UniversalSinkItemRef int32 InsertItemAtSortedPosition(const CompareFunctor & compareFunctor, QQ_SinkItemParam item, void * optCookie = NULL); + + /** + * Swaps our contents with the contents of (that), in an efficient manner. + * @param that The queue whose contents are to be swapped with our own. + */ + void SwapContents(Queue & that); + + /** + * Goes through the array and removes every item that is equal to (val). + * @param val the item to look for and remove + * @return The number of instances of (val) that were found and removed during this operation. + */ + uint32 RemoveAllInstancesOf(const ItemType & val); + + /** + * Goes through the array and removes all duplicate items. + * @param assumeAlreadySorted If set to true, the algorithm will assume the array is + * already sorted by value. Otherwise it will sort the array + * (so that duplicates are next to each other) before doing the + * duplicate-removal step. Defaults to false. + * @return The number of duplicate items that were found and removed during this operation. + */ + uint32 RemoveDuplicateItems(bool assumeAlreadySorted = false); + + /** + * Goes through the array and removes the first item that is equal to (val). + * @param val the item to look for and remove + * @return B_NO_ERROR if a matching item was found and removed, or B_ERROR if it wasn't found. + */ + status_t RemoveFirstInstanceOf(const ItemType & val); + + /** + * Goes through the array and removes the last item that is equal to (val). + * @param val the item to look for and remove + * @return B_NO_ERROR if a matching item was found and removed, or B_ERROR if it wasn't found. + */ + status_t RemoveLastInstanceOf(const ItemType & val); + + /** Returns true iff the first item in our queue is equal to (prefix). */ + bool StartsWith(const ItemType & prefix) const {return ((HasItems())&&(Head() == prefix));} + + /** Returns true iff the (prefixQueue) is a prefix of this queue. */ + bool StartsWith(const Queue & prefixQueue) const; + + /** Returns true iff the last item in our queue is equal to (suffix). */ + bool EndsWith(const ItemType & suffix) const {return ((HasItems())&&(Tail() == suffix));} + + /** Returns true iff the (suffixQueue) is a suffix of this queue. */ + bool EndsWith(const Queue & suffixQueue) const; + + /** + * Returns a pointer to the nth internally-held contiguous-Item-sub-array, to allow efficient + * array-style access to groups of items in this Queue. In the current implementation + * there may be as many as two such sub-arrays present, depending on the internal state of the Queue. + * @param whichArray Index of the internal array to return a pointer to. Typically zero or one. + * @param retLength The number of items in the returned sub-array will be written here. + * @return Pointer to the first item in the sub-array on success, or NULL on failure. + * Note that this array is only guaranteed valid as long as no items are + * added or removed from the Queue. + */ + ItemType * GetArrayPointer(uint32 whichArray, uint32 & retLength) {return const_cast(GetArrayPointerAux(whichArray, retLength));} + + /** Read-only version of the above */ + const ItemType * GetArrayPointer(uint32 whichArray, uint32 & retLength) const {return GetArrayPointerAux(whichArray, retLength);} + + /** Normalizes the layout of the items held in this Queue so that they are guaranteed to be contiguous + * in memory. This is useful if you want to pass pointers to items in this array in to functions + * that expect C arrays. Note that prepending items to this Queue may de-normalize it again. + * Note also that this method is O(N) if the array needs normalizing. + * (It's a no-op if the Queue is already normalized, of course) + */ + void Normalize(); + + /** Returns true iff this Queue is currently normalized -- that is, if its contents are arranged + * in the normal C-array ordering. Returns false otherwise. Call Normalize() if you want to + * make sure that the data is normalized. + */ + bool IsNormalized() const {return ((_itemCount == 0)||(_headIndex <= _tailIndex));} + + /** Returns true iff (val) is physically located in this container's internal items array. + * @param val Reference to an item. + */ + bool IsItemLocatedInThisContainer(const ItemType & val) const {return ((HasItems())&&((uintptr)((&val)-_queue) < (uintptr)GetNumItems()));} + + /** Returns a read-only reference to a default-constructed item of this Queue's type. This item will be valid as long as this Queue is valid. */ + const ItemType & GetDefaultItem() const {return GetDefaultObjectForType();} + + /** Returns a pointer to our internally held array of items. Note that this array's items are not guaranteed + * to be stored in order -- in particular, the items may be "wrapped around" the end of the array, with the + * first items in the sequence appearing near the end of the array. Only use this function if you know exactly + * what you are doing! + */ + ItemType * GetRawArrayPointer() {return _queue;} + + /** As above, but provides read-only access */ + const ItemType * GetRawArrayPointer() const {return _queue;} + +private: + status_t EnsureSizeAux(uint32 numSlots, ItemType ** optRetOldArray) {return EnsureSizeAux(numSlots, false, 0, optRetOldArray);} + status_t EnsureSizeAux(uint32 numSlots, bool setNumItems, uint32 extraReallocItems, ItemType ** optRetOldArray); + const ItemType * GetArrayPointerAux(uint32 whichArray, uint32 & retLength) const; + void SwapContentsAux(Queue & that); + + inline uint32 NextIndex(uint32 idx) const {return (idx >= _queueSize-1) ? 0 : idx+1;} + inline uint32 PrevIndex(uint32 idx) const {return (idx == 0) ? _queueSize-1 : idx-1;} + + // Translates a user-index into an index into the _queue array. + inline uint32 InternalizeIndex(uint32 idx) const {return (_headIndex + idx) % _queueSize;} + + // Helper methods, used for sorting (stolen from http://www-ihm.lri.fr/~thomas/VisuTri/inplacestablesort.html) + template void Merge(const CompareFunctorType & compareFunctor, uint32 from, uint32 pivot, uint32 to, uint32 len1, uint32 len2, void * optCookie); + template uint32 Lower(const CompareFunctorType & compareFunctor, uint32 from, uint32 to, const ItemType & val, void * optCookie) const; + template uint32 Upper(const CompareFunctorType & compareFunctor, uint32 from, uint32 to, const ItemType & val, void * optCookie) const; + + ItemType _smallQueue[SMALL_QUEUE_SIZE]; // small queues can be stored inline in this array + ItemType * _queue; // points to _smallQueue, or to a dynamically alloc'd array + uint32 _queueSize; // number of slots in the _queue array + uint32 _itemCount; // number of valid items in the array + uint32 _headIndex; // index of the first filled slot (meaningless if _itemCount is zero) + uint32 _tailIndex; // index of the last filled slot (meaningless if _itemCount is zero) +}; + +template +Queue::Queue() + : _queue(NULL), _queueSize(0), _itemCount(0) +{ + // empty +} + +template +Queue::Queue(const Queue& rhs) + : _queue(NULL), _queueSize(0), _itemCount(0) +{ + *this = rhs; +} + +template +bool +Queue::operator ==(const Queue& rhs) const +{ + if (this == &rhs) return true; + if (GetNumItems() != rhs.GetNumItems()) return false; + + for (int i = GetNumItems()-1; i>=0; i--) if (((*this)[i] == rhs[i]) == false) return false; + + return true; +} + +template +Queue & +Queue::operator =(const Queue& rhs) +{ + if (this != &rhs) + { + uint32 numItems = rhs.GetNumItems(); + if (EnsureSize(numItems, true) == B_NO_ERROR) for (uint32 i=0; i +status_t +Queue::CopyFrom(const Queue & rhs) +{ + if (this == &rhs) return B_NO_ERROR; + + uint32 numItems = rhs.GetNumItems(); + if (EnsureSize(numItems, true) == B_NO_ERROR) + { + for (uint32 i=0; i +ItemType & +Queue::operator[](uint32 i) +{ + MASSERT(i<_itemCount, "Invalid index to Queue::[]"); + return _queue[InternalizeIndex(i)]; +} + +template +const ItemType & +Queue::operator[](uint32 i) const +{ + MASSERT(i<_itemCount, "Invalid index to Queue::[]"); + return *GetItemAtUnchecked(i); +} + +template +Queue::~Queue() +{ + if (_queue != _smallQueue) delete [] _queue; +} + +template +QQ_UniversalSinkItemRef +ItemType * +Queue:: +AddTailAndGet(QQ_SinkItemParam item) +{ + ItemType * oldArray; + if (EnsureSizeAux(_itemCount+1, false, _itemCount+1, &oldArray) != B_NO_ERROR) return NULL; + + if (_itemCount == 0) _headIndex = _tailIndex = 0; + else _tailIndex = NextIndex(_tailIndex); + _itemCount++; + ItemType * ret = &_queue[_tailIndex]; + *ret = item; + delete [] oldArray; // must do this AFTER the last reference to (item), in case (item) was part of (oldArray) + return ret; +} + +template +ItemType * +Queue:: +AddTailAndGet() +{ + if (EnsureSize(_itemCount+1, false, _itemCount+1) != B_NO_ERROR) return NULL; + if (_itemCount == 0) _headIndex = _tailIndex = 0; + else _tailIndex = NextIndex(_tailIndex); + _itemCount++; + return &_queue[_tailIndex]; +} + +template +status_t +Queue:: +AddTailMulti(const Queue & queue, uint32 startIndex, uint32 numNewItems) +{ + uint32 hisSize = queue.GetNumItems(); + numNewItems = muscleMin(numNewItems, (startIndex < hisSize) ? (hisSize-startIndex) : 0); + + uint32 mySize = GetNumItems(); + uint32 newSize = mySize+numNewItems; + if (EnsureSize(newSize, true) != B_NO_ERROR) return B_ERROR; + for (uint32 i=mySize; i +status_t +Queue:: +AddTailMulti(const ItemType * items, uint32 numItems) +{ + uint32 mySize = GetNumItems(); + uint32 newSize = mySize+numItems; + uint32 rhs = 0; + + ItemType * oldArray; + if (EnsureSizeAux(newSize, true, 0, &oldArray) != B_NO_ERROR) return B_ERROR; + for (uint32 i=mySize; i +QQ_UniversalSinkItemRef +ItemType * +Queue:: +AddHeadAndGet(QQ_SinkItemParam item) +{ + ItemType * oldArray; + if (EnsureSizeAux(_itemCount+1, false, _itemCount+1, &oldArray) != B_NO_ERROR) return NULL; + if (_itemCount == 0) _headIndex = _tailIndex = 0; + else _headIndex = PrevIndex(_headIndex); + _itemCount++; + ItemType * ret = &_queue[_headIndex]; + *ret = QQ_ForwardItem(item); + delete [] oldArray; // must be done after all references to (item)! + return ret; +} + +template +ItemType * +Queue:: +AddHeadAndGet() +{ + if (EnsureSize(_itemCount+1, false, _itemCount+1) != B_NO_ERROR) return NULL; + if (_itemCount == 0) _headIndex = _tailIndex = 0; + else _headIndex = PrevIndex(_headIndex); + _itemCount++; + return &_queue[_headIndex]; +} + +template +status_t +Queue:: +AddHeadMulti(const Queue & queue, uint32 startIndex, uint32 numNewItems) +{ + uint32 hisSize = queue.GetNumItems(); + numNewItems = muscleMin(numNewItems, (startIndex < hisSize) ? (hisSize-startIndex) : 0); + + if (EnsureSize(numNewItems+GetNumItems()) != B_NO_ERROR) return B_ERROR; + for (int i=((int)startIndex+numNewItems)-1; i>=(int32)startIndex; i--) (void) AddHead(queue[i]); // guaranteed not to fail + return B_NO_ERROR; +} + +template +status_t +Queue:: +AddHeadMulti(const ItemType * items, uint32 numItems) +{ + ItemType * oldArray; + if (EnsureSizeAux(_itemCount+numItems, &oldArray) != B_NO_ERROR) return B_ERROR; + for (int i=((int)numItems)-1; i>=0; i--) (void) AddHead(items[i]); // guaranteed not to fail + delete [] oldArray; // must be done last! + return B_NO_ERROR; +} + +template +status_t +Queue:: +RemoveHead(ItemType & returnItem) +{ + if (_itemCount == 0) return B_ERROR; + returnItem = _queue[_headIndex]; + return RemoveHead(); +} + +template +uint32 +Queue::RemoveHeadMulti(uint32 numItems) +{ + numItems = muscleMin(numItems, _itemCount); + if (numItems == _itemCount) Clear(); + else for (uint32 i=0; i +uint32 +Queue::RemoveTailMulti(uint32 numItems) +{ + numItems = muscleMin(numItems, _itemCount); + if (numItems == _itemCount) Clear(); + else for (uint32 i=0; i +status_t +Queue:: +RemoveHead() +{ + if (_itemCount == 0) return B_ERROR; + int oldHeadIndex = _headIndex; + _headIndex = NextIndex(_headIndex); + _itemCount--; + _queue[oldHeadIndex] = GetDefaultItem(); // this must be done last, as queue state must be coherent when we do this + return B_NO_ERROR; +} + +template +ItemType +Queue:: +RemoveHeadWithDefault() +{ + if (IsEmpty()) return GetDefaultItem(); + else + { + ItemType ret = Head(); + (void) RemoveHead(); + return ret; + } +} + +template +status_t +Queue:: +RemoveTail(ItemType & returnItem) +{ + if (_itemCount == 0) return B_ERROR; + returnItem = _queue[_tailIndex]; + return RemoveTail(); +} + +template +status_t +Queue:: +RemoveTail() +{ + if (_itemCount == 0) return B_ERROR; + int removedItemIndex = _tailIndex; + _tailIndex = PrevIndex(_tailIndex); + _itemCount--; + _queue[removedItemIndex] = GetDefaultItem(); // this must be done last, as queue state must be coherent when we do this + return B_NO_ERROR; +} + +template +ItemType +Queue:: +RemoveTailWithDefault() +{ + if (IsEmpty()) return GetDefaultItem(); + else + { + ItemType ret = Tail(); + (void) RemoveTail(); + return ret; + } +} + +template +status_t +Queue:: +GetItemAt(uint32 index, ItemType & returnItem) const +{ + const ItemType * p = GetItemAt(index); + if (p) + { + returnItem = *p; + return B_NO_ERROR; + } + else return B_ERROR; +} + +template +status_t +Queue:: +RemoveItemAt(uint32 index, ItemType & returnItem) +{ + if (index >= _itemCount) return B_ERROR; + returnItem = _queue[InternalizeIndex(index)]; + return RemoveItemAt(index); +} + +template +status_t +Queue:: +RemoveItemAt(uint32 index) +{ + if (index >= _itemCount) return B_ERROR; + + uint32 internalizedIndex = InternalizeIndex(index); + uint32 indexToClear; + + if (index < _itemCount/2) + { + // item is closer to the head: shift everything forward one, ending at the head + while(internalizedIndex != _headIndex) + { + uint32 prev = PrevIndex(internalizedIndex); + _queue[internalizedIndex] = _queue[prev]; + internalizedIndex = prev; + } + indexToClear = _headIndex; + _headIndex = NextIndex(_headIndex); + } + else + { + // item is closer to the tail: shift everything back one, ending at the tail + while(internalizedIndex != _tailIndex) + { + uint32 next = NextIndex(internalizedIndex); + _queue[internalizedIndex] = _queue[next]; + internalizedIndex = next; + } + indexToClear = _tailIndex; + _tailIndex = PrevIndex(_tailIndex); + } + + _itemCount--; + _queue[indexToClear] = GetDefaultItem(); // this must be done last, as queue state must be coherent when we do this + return B_NO_ERROR; +} + +template +ItemType +Queue:: +RemoveItemAtWithDefault(uint32 index) +{ + if (index >= GetNumItems()) return GetDefaultItem(); + else + { + ItemType ret = (*this)[index]; + (void) RemoveItemAt(index); + return ret; + } +} + +template +QQ_UniversalSinkItemRef +status_t +Queue:: +ReplaceItemAt(uint32 index, QQ_SinkItemParam newItem) +{ + if (index >= _itemCount) return B_ERROR; + _queue[InternalizeIndex(index)] = QQ_ForwardItem(newItem); + return B_NO_ERROR; +} + +template +QQ_UniversalSinkItemRef +status_t +Queue:: +InsertItemAt(uint32 index, QQ_SinkItemParam newItem) +{ + if ((GetNumUnusedItemSlots() < 1)&&(IsItemLocatedInThisContainer(newItem))) + { + ItemType temp = QQ_ForwardItem(newItem); // avoid dangling pointer issue by copying the item to a temporary location + return InsertItemAt(index, temp); + } + + // Simple cases + if (index > _itemCount) return B_ERROR; + if (index == _itemCount) return AddTail(QQ_ForwardItem(newItem)); + if (index == 0) return AddHead(QQ_ForwardItem(newItem)); + + // Harder case: inserting into the middle of the array + if (index < _itemCount/2) + { + // Add a space at the front, and shift things back + if (AddHead() != B_NO_ERROR) return B_ERROR; // allocate an extra slot + for (uint32 i=0; i((int32)index); i--) ReplaceItemAt(i, QQ_ForwardItem(*GetItemAtUnchecked(i-1))); + } + return ReplaceItemAt(index, QQ_ForwardItem(newItem)); +} + +template +status_t +Queue:: +InsertItemsAt(uint32 index, const Queue & queue, uint32 startIndex, uint32 numNewItems) +{ + uint32 hisSize = queue.GetNumItems(); + numNewItems = muscleMin(numNewItems, (startIndex < hisSize) ? (hisSize-startIndex) : 0); + if (numNewItems == 0) return B_NO_ERROR; + if (index > _itemCount) return B_ERROR; + if (numNewItems == 1) + { + if (index == 0) return AddHead(queue.Head()); + if (index == _itemCount) return AddTail(queue.Head()); + } + + uint32 oldSize = GetNumItems(); + uint32 newSize = oldSize+numNewItems; + + if (EnsureSize(newSize, true) != B_NO_ERROR) return B_ERROR; + for (uint32 i=index; i +status_t +Queue:: +InsertItemsAt(uint32 index, const ItemType * items, uint32 numNewItems) +{ + if (numNewItems == 0) return B_NO_ERROR; + if (index > _itemCount) return B_ERROR; + if (numNewItems == 1) + { + if (index == 0) return AddHead(*items); + if (index == _itemCount) return AddTail(*items); + } + + uint32 oldSize = GetNumItems(); + uint32 newSize = oldSize+numNewItems; + + ItemType * oldItems; + if (EnsureSizeAux(newSize, true, &oldItems) != B_NO_ERROR) return B_ERROR; + int32 si = 0; + for (uint32 i=index; i +void +Queue:: +Clear(bool releaseCachedBuffers) +{ + if ((releaseCachedBuffers)&&(_queue != _smallQueue)) + { + delete [] _queue; + _queue = NULL; + _queueSize = 0; + _itemCount = 0; + } + else if (HasItems()) + { + for (uint32 i=0; i<2; i++) + { + uint32 arrayLen = 0; // set to zero just to shut the compiler up + const ItemType & defaultItem = GetDefaultItem(); + ItemType * p = GetArrayPointer(i, arrayLen); + if (p) {for (uint32 j=0; j +status_t +Queue:: +EnsureSizeAux(uint32 size, bool setNumItems, uint32 extraPreallocs, ItemType ** retOldArray) +{ + if (retOldArray) *retOldArray = NULL; // default value, will be set non-NULL iff the old array needs deleting later + + if ((_queue == NULL)||(_queueSize < size)) + { + const uint32 sqLen = ARRAYITEMS(_smallQueue); + uint32 temp = size + extraPreallocs; + uint32 newQLen = muscleMax((uint32)SMALL_QUEUE_SIZE, ((setNumItems)||(temp <= sqLen)) ? muscleMax(sqLen,temp) : temp); + + ItemType * newQueue = ((_queue == _smallQueue)||(newQLen > sqLen)) ? newnothrow_array(ItemType,newQLen) : _smallQueue; + if (newQueue == NULL) {WARN_OUT_OF_MEMORY; return B_ERROR;} + if (newQueue == _smallQueue) newQLen = sqLen; + + // The (_queueSize > 0) check below isn't strictly necessary, but it makes clang++ feel better + if (_queueSize > 0) for (uint32 i=0; i<_itemCount; i++) newQueue[i] = QQ_PlunderItem(*GetItemAtUnchecked(i)); // we know that (_itemCount < size) + + if (setNumItems) _itemCount = size; + _headIndex = 0; + _tailIndex = _itemCount-1; + + if (_queue == _smallQueue) for (uint32 i=0; i _itemCount) + { + // We can do this quickly because the "new" items are already initialized properly + _tailIndex = PrevIndex((_headIndex+size)%_queueSize); + _itemCount = size; + } + else (void) RemoveTailMulti(_itemCount-size); + } + + return B_NO_ERROR; +} + +template +int32 +Queue:: +IndexOf(const ItemType & item, uint32 startAt, uint32 endAtPlusOne) const +{ + if (startAt >= GetNumItems()) return -1; + + endAtPlusOne = muscleMin(endAtPlusOne, GetNumItems()); + for (uint32 i=startAt; i +int32 +Queue:: +LastIndexOf(const ItemType & item, uint32 startAt, uint32 endAt) const +{ + if (endAt >= GetNumItems()) return -1; + + startAt = muscleMin(startAt, GetNumItems()-1); + for (int32 i=(int32)startAt; i>=((int32)endAt); i--) if (*GetItemAtUnchecked(i) == item) return i; + return -1; +} + +template +void +Queue:: +Swap(uint32 fromIndex, uint32 toIndex) +{ + muscleSwap((*this)[fromIndex], (*this)[toIndex]); +} + +template +QQ_UniversalSinkItemRef +int32 +Queue:: +InsertItemAtSortedPosition(const CompareFunctor & compareFunctor, QQ_SinkItemParam item, void * optCookie) +{ + int32 insertAfter = GetNumItems(); + if ((insertAfter > 0)&&(compareFunctor.Compare(item, Head(), optCookie) >= 0)) + while(--insertAfter >= 0) + if (compareFunctor.Compare(item, (*this)[insertAfter], optCookie) >= 0) + return (InsertItemAt(insertAfter+1, QQ_ForwardItem(item)) == B_NO_ERROR) ? (insertAfter+1) : -1; + return (AddHead(QQ_ForwardItem(item)) == B_NO_ERROR) ? 0 : -1; +} + +template +template +void +Queue:: +Sort(const CompareFunctorType & compareFunctor, uint32 from, uint32 to, void * optCookie) +{ + uint32 size = GetNumItems(); + if (to > size) to = size; + if (to > from) + { + if (to < from+12) + { + // too easy, just do a bubble sort (base case) + if (to > from+1) + { + for (uint32 i=from+1; ifrom; j--) + { + int ret = compareFunctor.Compare(*(GetItemAtUnchecked(j)), *(GetItemAtUnchecked(j-1)), optCookie); + if (ret < 0) Swap(j, j-1); + else break; + } + } + } + } + else + { + // Okay, do the real thing + uint32 middle = (from + to)/2; + Sort(compareFunctor, from, middle, optCookie); + Sort(compareFunctor, middle, to, optCookie); + Merge(compareFunctor, from, middle, to, middle-from, to-middle, optCookie); + } + } +} + +template +void +Queue:: +ReverseItemOrdering(uint32 from, uint32 to) +{ + uint32 size = GetNumItems(); + if (size > 0) + { + to--; // make it inclusive + if (to >= size) to = size-1; + while (from < to) Swap(from++, to--); + } +} + +template +status_t +Queue:: +RemoveFirstInstanceOf(const ItemType & val) +{ + uint32 ni = GetNumItems(); + for (uint32 i=0; i +status_t +Queue:: +RemoveLastInstanceOf(const ItemType & val) +{ + for (int32 i=((int32)GetNumItems())-1; i>=0; i--) if ((*this)[i] == val) return RemoveItemAt(i); + return B_ERROR; +} + +template +uint32 +Queue:: +RemoveAllInstancesOf(const ItemType & val) +{ + if (IsItemLocatedInThisContainer(val)) + { + // avoid having the item erased while we are still using it + ItemType temp = val; + return RemoveAllInstancesOf(temp); + } + + // Efficiently collapse all non-matching slots up to the top of the list + uint32 ret = 0; + uint32 writeTo = 0; + uint32 origSize = GetNumItems(); + for(uint32 readFrom=0; readFrom writeTo) (*this)[writeTo] = nextRead; + writeTo++; + } + } + + // Now get rid of any now-surplus slots + for (; writeTo +uint32 +Queue:: +RemoveDuplicateItems(bool assumeAlreadySorted) +{ + if (IsEmpty()) return 0; // nothing to do! + if (assumeAlreadySorted == false) Sort(); + + uint32 numWrittenItems = 1; // we'll always keep the first item + uint32 totalItems = GetNumItems(); + for (uint32 i=0; i +template +void +Queue:: +Merge(const CompareFunctorType & compareFunctor, uint32 from, uint32 pivot, uint32 to, uint32 len1, uint32 len2, void * optCookie) +{ + if ((len1)&&(len2)) + { + if (len1+len2 == 2) + { + if (compareFunctor.Compare(*(GetItemAtUnchecked(pivot)), *(GetItemAtUnchecked(from)), optCookie) < 0) Swap(pivot, from); + } + else + { + uint32 first_cut, second_cut; + uint32 len11, len22; + if (len1 > len2) + { + len11 = len1/2; + first_cut = from + len11; + second_cut = Lower(compareFunctor, pivot, to, *GetItemAtUnchecked(first_cut), optCookie); + len22 = second_cut - pivot; + } + else + { + len22 = len2/2; + second_cut = pivot + len22; + first_cut = Upper(compareFunctor, from, pivot, *GetItemAtUnchecked(second_cut), optCookie); + len11 = first_cut - from; + } + + // do a rotation + if ((pivot!=first_cut)&&(pivot!=second_cut)) + { + // find the greatest common denominator of (pivot-first_cut) and (second_cut-first_cut) + uint32 n = pivot-first_cut; + { + uint32 m = second_cut-first_cut; + while(n!=0) + { + uint32 t = m % n; + m=n; + n=t; + } + n = m; + } + + while(n--) + { + ItemType val = QQ_PlunderItem(*GetItemAtUnchecked(first_cut+n)); + uint32 shift = pivot - first_cut; + uint32 p1 = first_cut+n; + uint32 p2 = p1+shift; + while (p2 != first_cut + n) + { + ReplaceItemAt(p1, QQ_PlunderItem(*GetItemAtUnchecked(p2))); + p1 = p2; + if (second_cut - p2 > shift) p2 += shift; + else p2 = first_cut + (shift - (second_cut - p2)); + } + ReplaceItemAt(p1, QQ_PlunderItem(val)); + } + } + + uint32 new_mid = first_cut+len22; + Merge(compareFunctor, from, first_cut, new_mid, len11, len22, optCookie); + Merge(compareFunctor, new_mid, second_cut, to, len1 - len11, len2 - len22, optCookie); + } + } +} + + +template +template +uint32 +Queue:: +Lower(const CompareFunctorType & compareFunctor, uint32 from, uint32 to, const ItemType & val, void * optCookie) const +{ + if (to > from) + { + uint32 len = to - from; + while (len > 0) + { + uint32 half = len/2; + uint32 mid = from + half; + if (compareFunctor.Compare(*(GetItemAtUnchecked(mid)), val, optCookie) < 0) + { + from = mid+1; + len = len - half - 1; + } + else len = half; + } + } + return from; +} + +template +template +uint32 +Queue:: +Upper(const CompareFunctorType & compareFunctor, uint32 from, uint32 to, const ItemType & val, void * optCookie) const +{ + if (to > from) + { + uint32 len = to - from; + while (len > 0) + { + uint32 half = len/2; + uint32 mid = from + half; + if (compareFunctor.Compare(val, *(GetItemAtUnchecked(mid)), optCookie) < 0) len = half; + else + { + from = mid+1; + len = len - half -1; + } + } + } + return from; +} + +template +const ItemType * +Queue :: GetArrayPointerAux(uint32 whichArray, uint32 & retLength) const +{ + if (_itemCount > 0) + { + switch(whichArray) + { + case 0: + retLength = (_headIndex <= _tailIndex) ? (_tailIndex-_headIndex)+1 : (_queueSize-_headIndex); + return &_queue[_headIndex]; + break; + + case 1: + if (_headIndex > _tailIndex) + { + retLength = _tailIndex+1; + return &_queue[0]; + } + break; + } + } + + retLength = 0; + return NULL; +} + +template +void +Queue::SwapContents(Queue & that) +{ + if (&that == this) return; // no point trying to swap with myself + + bool thisSmall = (_queue == _smallQueue); + bool thatSmall = (that._queue == that._smallQueue); + + if ((thisSmall)&&(thatSmall)) + { + // First, move any extra items from the longer queue to the shorter one... + uint32 commonSize = muscleMin(GetNumItems(), that.GetNumItems()); + int32 sizeDiff = ((int32)GetNumItems())-((int32)that.GetNumItems()); + Queue & copyTo = (sizeDiff > 0) ? that : *this; + Queue & copyFrom = (sizeDiff > 0) ? *this : that; + (void) copyTo.AddTailMulti(copyFrom, commonSize); // guaranteed not to fail + (void) copyFrom.EnsureSize(commonSize, true); // remove the copied items from (copyFrom) + + // Then just swap the elements that are present in both arrays + for (uint32 i=0; i +void +Queue::SwapContentsAux(Queue & largeThat) +{ + // First, copy over our (small) contents to his small-buffer + uint32 ni = GetNumItems(); + for (uint32 i=0; i 0) + { + largeThat._queue = largeThat._smallQueue; + largeThat._queueSize = ARRAYITEMS(largeThat._smallQueue); + largeThat._headIndex = 0; + largeThat._tailIndex = ni-1; + } + else + { + largeThat._queue = NULL; + largeThat._queueSize = 0; + // headIndex and tailIndex are undefined in this case anyway + } + + muscleSwap(_itemCount, largeThat._itemCount); +} + +template +bool +Queue::StartsWith(const Queue & prefixQueue) const +{ + if (prefixQueue.GetNumItems() > GetNumItems()) return false; + uint32 prefixQueueLen = prefixQueue.GetNumItems(); + for (uint32 i=0; i +bool +Queue::EndsWith(const Queue & suffixQueue) const +{ + if (suffixQueue.GetNumItems() > GetNumItems()) return false; + int32 lastToCheck = GetNumItems()-suffixQueue.GetNumItems(); + for (int32 i=GetNumItems()-1; i>=lastToCheck; i--) if (!(suffixQueue[i] == (*this)[i])) return false; + return true; +} + +template +void +Queue::Normalize() +{ + if (IsNormalized() == false) + { + if (_itemCount*2 <= _queueSize) + { + // There's enough space in the middle of the + // array to just copy the items over, yay! This + // is more efficient when there are just a few + // valid items in a large array. + uint32 startAt = _tailIndex+1; + for (uint32 i=0; i<_itemCount; i++) + { + ItemType & from = (*this)[i]; + _queue[startAt+i] = from; + from = GetDefaultItem(); // clear the old slot to avoid leaving extra Refs, etc + } + _headIndex = startAt; + _tailIndex = startAt+_itemCount-1; + } + else + { + // There's not enough room to do a simple copy, + // so we'll rotate the entire array using this + // algorithm that was written by Paul Hsieh, taken + // from http://www.azillionmonkeys.com/qed/case8.html + uint32 c = 0; + for (uint32 v = 0; c<_queueSize; v++) + { + uint32 t = v; + uint32 tp = v + _headIndex; + ItemType tmp = _queue[v]; + c++; + while(tp != v) + { + _queue[t] = _queue[tp]; + t = tp; + tp += _headIndex; + if (tp >= _queueSize) tp -= _queueSize; + c++; + } + _queue[t] = tmp; + } + _headIndex = 0; + _tailIndex = _itemCount-1; + } + } +} + +}; // end namespace muscle + +#endif diff --git a/util/RefCount.h b/util/RefCount.h new file mode 100644 index 00000000..656988b6 --- /dev/null +++ b/util/RefCount.h @@ -0,0 +1,470 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleRefCount_h +#define MuscleRefCount_h + +#include "util/Cloneable.h" +#include "util/ObjectPool.h" +#include "util/PointerAndBool.h" +#include "system/AtomicCounter.h" + +namespace muscle { + +class RefCountable; + +// This macro lets you easily declare Reference classes fora give RefCountable type in the standard way, +// which is that for a RefCountable class Named XXX you would have XXXRef and ConstXXXRef as simpler ways +// to express Ref and ConstRef, respectively. +#define DECLARE_REFTYPES(RefCountableClassName) \ + typedef ConstRef Const##RefCountableClassName##Ref; \ + typedef Ref RefCountableClassName##Ref + +/** This class represents objects that can be reference-counted using the Ref class. + * Note that any object that can be reference-counted can also be cached and recycled via an ObjectPool. + */ +class RefCountable +{ +public: + /** Default constructor. Refcount begins at zero. */ + RefCountable() : _manager(NULL) {/* empty */} + + /** Copy constructor -- ref count and manager settings are deliberately not copied over! */ + RefCountable(const RefCountable &) : _manager(NULL) {/* empty */} + + /** Virtual destructor, to keep C++ honest. Don't remove this unless you like crashing */ + virtual ~RefCountable() {/* empty */} + + /** Assigment operator. deliberately implemented as a no-op! */ + inline RefCountable &operator=(const RefCountable &) {return *this;} + + /** Increments the counter. Thread safe. */ + inline void IncrementRefCount() const {_refCount.AtomicIncrement();} + + /** Decrements the counter and returns true iff the new value is zero. Thread safe. */ + inline bool DecrementRefCount() const {return _refCount.AtomicDecrement();} + + /** Sets the recycle-pointer for this object. If set to non-NULL, this pointer + * is used by the ObjectPool class to recycle this object when it is no longer + * in use, so as to avoid the overhead of having to delete it and re-create it + * later on. The RefCountable class itself does nothing with this pointer. + * Default value is NULL. + * @param manager Pointer to the new manager object to use, or NULL to use no manager. + */ + void SetManager(AbstractObjectManager * manager) {_manager = manager;} + + /** Returns this object's current recyler pointer. */ + AbstractObjectManager * GetManager() const {return _manager;} + + /** Returns this object's current reference count. Note that + * the value returned by this method is volatile in multithreaded + * environments, so it may already be wrong by the time it is returned. + * Be careful! + */ + uint32 GetRefCount() const {return _refCount.GetCount();} + +private: + mutable AtomicCounter _refCount; + AbstractObjectManager * _manager; +}; +template class Ref; // forward reference +template class ConstRef; // forward reference +DECLARE_REFTYPES(RefCountable); + +/** + * This is a ref-count token object that works with any instantiation + * type that is a subclass of RefCountable. The RefCountable object + * that this ConstRef item holds is considered read-only; if you want + * it to be modfiable, you should use the Ref class instead. + */ +template class ConstRef +{ +public: + typedef ObjectPool ItemPool; /**< type of an ObjectPool of user data structures */ + + /** + * Default constructor. + * Creates a NULL reference (suitable for later initialization with SetRef(), or the assignment operator) + */ + ConstRef() : _item(NULL, true) {/* empty */} + + /** + * Creates a new reference-count for the given item. + * Once referenced, (item) will be automatically deleted (or recycled) when the last ConstRef that references it goes away. + * @param item A dynamically allocated object that the ConstRef class will assume responsibility for deleting. May be NULL. + * @param doRefCount If set false, then this ConstRef will not do any ref-counting on (item); rather it + * just acts as a fancy version of a normal C++ pointer. Specifically, it will not + * modify the object's reference count, nor will it ever delete the object. + * Setting this argument to false can be useful if you want to supply a + * ConstRef to an item that wasn't dynamically allocated from the heap. + * But if you do that, it allows the possibility of the object going away while + * other Refs are still using it, so be careful! + */ + explicit ConstRef(const Item * item, bool doRefCount = true) : _item(item, doRefCount) + { + RefItem(); + } + + /** Copy constructor. Creates an additional reference to the object referenced by (copyMe). + * The referenced object won't be deleted until ALL Refs that reference it are gone. + */ + ConstRef(const ConstRef & copyMe) : _item(NULL, true) + { + *this = copyMe; + } + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Constructor */ + ConstRef(ConstRef && rhs) {this->SwapContents(rhs);} +#endif + + /** Attempts to set this reference by downcasting the reference in the provided ConstRefCountableRef. + * If the downcast cannot be done (via dynamic_cast) then we become a NULL reference. + * @param ref The read-only RefCountable reference to set ourselves from. + * @param junk This parameter is ignored; it is just here to disambiguate constructors. + */ + ConstRef(const ConstRefCountableRef & ref, bool junk) : _item(NULL, true) + { + (void) junk; + (void) SetFromRefCountableRef(ref); + } + + /** Unreferences the held data item. If this is the last ConstRef that + * references the held data item, the data item will be deleted or recycled at this time. + */ + ~ConstRef() {UnrefItem();} + + /** Convenience method; unreferences any item this ConstRef is currently referencing; and + * causes this ConstRef to reference the given Item instead. Very similar to the assignment operator. + * (in fact the assignment operator just calls this method) + * @param item A dynamically allocated object that this ConstRef object will assume responsibility for deleting. + * May be NULL, in which case the effect is just to unreference the current item. + * @param doRefCount If set false, then this ConstRef will not do any ref-counting on (item); rather it + * just acts as a fancy version of a normal C++ pointer. Specifically, it will not + * modify the object's reference count, nor will it ever delete the object. + * Setting this argument to false can be useful if you want to supply a + * ConstRef to an item that wasn't dynamically allocated from the heap. + * But if you do that, it allows the possibility of the object going away while + * other Refs are still using it, so be careful! + */ + void SetRef(const Item * item, bool doRefCount = true) + { + if (item == this->GetItemPointer()) + { + if (doRefCount != this->IsRefCounting()) + { + if (doRefCount) + { + // We weren't ref-counting before, but the user wants us to start + SetRefCounting(true); + RefItem(); + } + else + { + // We were ref-counting before, but the user wants us to drop it + UnrefItem(); + SetRefCounting(false); + } + } + } + else + { + // switch items + UnrefItem(); + _item.SetPointerAndBool(item, doRefCount); + RefItem(); + } + } + + /** Assigment operator. + * Unreferences the previous held data item, and adds a reference to the data item of (rhs). + */ + inline ConstRef &operator=(const ConstRef & rhs) {this->SetRef(rhs.GetItemPointer(), rhs.IsRefCounting()); return *this;} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Assignment operator */ + inline ConstRef &operator=(ConstRef && rhs) {this->SwapContents(rhs); return *this;} +#endif + + /** Similar to the == operator, except that this version will also call the comparison operator + * on the objects themselves if necessary, to determine exact equality. (This is different than + * the behavior of the == operator, which only compares the pointers, not the objects themselves) + */ + bool IsDeeplyEqualTo(const ConstRef & rhs) const + { + const Item * myItem = this->GetItemPointer(); + const Item * hisItem = rhs.GetItemPointer(); + if (myItem == hisItem) return true; + if ((myItem != NULL) != (hisItem != NULL)) return false; + return ((myItem == NULL)||(*myItem == *hisItem)); + } + + /** Returns true iff both Refs are referencing the same data. */ + bool operator ==(const ConstRef &rhs) const {return this->GetItemPointer() == rhs.GetItemPointer();} + + /** Returns true iff both Refs are not referencing the same data. */ + bool operator !=(const ConstRef &rhs) const {return this->GetItemPointer() != rhs.GetItemPointer();} + + /** Compares the pointers the two Refs are referencing. */ + bool operator < (const ConstRef &rhs) const {return this->GetItemPointer() < rhs.GetItemPointer();} + + /** Compares the pointers the two Refs are referencing. */ + bool operator > (const ConstRef &rhs) const {return this->GetItemPointer() > rhs.GetItemPointer();} + + /** Returns the ref-counted data item. The returned data item + * is only guaranteed valid for as long as this RefCount object exists. + */ + const Item * GetItemPointer() const {return _item.GetPointer();} + + /** Convenience synonym for GetItemPointer(). */ + const Item * operator()() const {return this->GetItemPointer();} + + /** Unreferences our held data item (if any), and turns this object back into a NULL reference. + * (equivalent to *this = ConstRef();) + */ + void Reset() {UnrefItem();} + + /** Equivalent to Reset(), except that this method will not delete or recycle + * the held object under any circumstances. Use with caution, as use of this + * method can result in memory leaks. + */ + void Neutralize() + { + const Item * item = this->IsRefCounting() ? this->GetItemPointer() : NULL; + if (item) (void) item->DecrementRefCount(); // remove our ref-count from the item but deliberately never delete the item + _item.SetPointer(NULL); + } + + /** Swaps this ConstRef's contents with those of the specified ConstRef. + * @param swapWith ConstRef to swap state with. + */ + void SwapContents(ConstRef & swapWith) {this->_item.SwapContents(swapWith._item);} + + /** Returns true iff we are refcounting our held object, or false + * if we are merely pointing to it (see constructor documentation for details) + */ + bool IsRefCounting() const {return _item.GetBool();} + + /** Convenience method: Returns a ConstRefCountableRef object referencing the same RefCountable as this typed ref. */ + ConstRefCountableRef GetRefCountableRef() const {return ConstRefCountableRef(this->GetItemPointer(), this->IsRefCounting());} + + /** Convenience method; attempts to set this typed ConstRef to be referencing the same item as the given ConstRefCountableRef. + * If the conversion cannot be done, our state will remain unchanged. + * @param refCountableRef The ConstRefCountableRef to set ourselves from. + * @returns B_NO_ERROR if the conversion was successful, or B_ERROR if the ConstRefCountableRef's item + * type is incompatible with our own item type (as dictated by dynamic_cast) + */ + status_t SetFromRefCountableRef(const ConstRefCountableRef & refCountableRef) + { + const RefCountable * refCountableItem = refCountableRef(); + if (refCountableItem) + { + const Item * typedItem = dynamic_cast(refCountableItem); + if (typedItem == NULL) return B_ERROR; + SetRef(typedItem, refCountableRef.IsRefCounting()); + } + else Reset(); + + return B_NO_ERROR; + } + + /** Same as SetFromRefCountableRef(), but uses a static_cast instead of a dynamic_cast to + * do the type conversion. This is faster that SetFromRefCountableRef(), but it is up to + * the user's code to guarantee that the conversion is valid. If (refCountableRef) + * references an object of the wrong type, undefined behaviour (read: crashing) + * will likely occur. + */ + inline void SetFromRefCountableRefUnchecked(const ConstRefCountableRef & refCountableRef) + { + SetRef(static_cast(refCountableRef()), refCountableRef.IsRefCounting()); + } + + /** Returns true iff we are pointing to a valid item (i.e. if (GetItemPointer() != NULL)) */ + bool IsValid() const {return (this->GetItemPointer() != NULL);} + + /** Returns true iff we are not pointing to a valid item (i.e. if (GetItemPointer() == NULL)) */ + bool IsNull() const {return (this->GetItemPointer() == NULL);} + + /** Returns true only if we are certain that no other Refs are pointing + * at the same RefCountable object that we are. If this Ref's do-reference-counting + * flag is false, then this method will always return false, since we can't + * be sure about sharing unless we are reference counting. If this ConstRef is + * a NULL Ref, then this method will return true. + */ + bool IsRefPrivate() const + { + const Item * item = this->GetItemPointer(); + return ((item == NULL)||((this->IsRefCounting())&&(item->GetRefCount() == 1))); + } + + /** This method will check our referenced object to see if there is any + * chance that it is shared by other ConstRef objects. If it is, it will + * make a copy of the referenced object and set this ConstRef to reference + * the copy instead of the original. The upshot of this is that once + * this method returns B_NO_ERROR, you can safely modify the referenced + * object without having to worry about race conditions caused by sharing + * data with other threads. This method is thread safe -- it may occasionally + * make a copy that wasn't strictly necessary, but it will never fail to + * make a copy when making a copy is necessary. + * @returns B_NO_ERROR on success (i.e. the object was successfully copied, + * or a copy turned out to be unnecessary), or B_ERROR + * on failure (i.e. out of memory) + */ + status_t EnsureRefIsPrivate() + { + if (IsRefPrivate() == false) + { + Ref copyRef = this->Clone(); + if (copyRef() == NULL) return B_ERROR; + *this = copyRef; + } + return B_NO_ERROR; + } + + /** Makes a copy of our held Item and returns it in a non-const Ref object, + * or returns a NULL Ref on out-of-memory (or if we aren't holding an Item). + */ + Ref Clone() const + { + const Item * item = this->GetItemPointer(); + if (item) + { + AbstractObjectManager * m = item->GetManager(); + Item * newItem = m ? static_cast(m->ObtainObjectGeneric()) : static_cast(CloneObject(*item)); + if (newItem) return Ref(newItem); + else WARN_OUT_OF_MEMORY; + } + return Ref(); + } + + /** This method allows Refs to be keys in Hashtables. Node that we hash on the pointer's value, not the object it points to! */ + uint32 HashCode() const {return CalculateHashCode(this->GetItemPointer());} + +private: + void SetRefCounting(bool rc) {_item.SetBool(rc);} + + void RefItem() + { + const Item * item = this->IsRefCounting() ? this->GetItemPointer() : NULL; + if (item) item->IncrementRefCount(); + } + + void UnrefItem() + { + const Item * item = this->GetItemPointer(); + if (item) + { + bool isRefCounting = this->IsRefCounting(); + if ((isRefCounting)&&(item->DecrementRefCount())) + { + AbstractObjectManager * m = item->GetManager(); + if (m) m->RecycleObject(const_cast(item)); + else delete item; + } + _item.SetPointer(NULL); + } + } + + PointerAndBool _item; +}; + +/** When we compare references, we really want to be comparing what those references point to */ +template class CompareFunctor > +{ +public: + /** Compares the two ConstRef's strcmp() style, returning zero if they are equal, a negative value if (item1) comes first, or a positive value if (item2) comes first. */ + int Compare(const ConstRef & item1, const ConstRef & item2, void * cookie) const {return CompareFunctor().Compare(item1(), item2(), cookie);} +}; + +/** + * This is a ref-count token object that works with any instantiation + * type that is a subclass of RefCountable. The RefCountable object + * that this ConstRef item holds is considered modifiable; if you want + * it to be read-only, you should use the ConstRef class instead. + */ +template class Ref : public ConstRef +{ +public: + /** + * Default constructor. + * Creates a NULL reference (suitable for later initialization with SetRef(), or the assignment operator) + */ + Ref() : ConstRef() {/* empty */} + + /** + * Creates a new reference-count for the given item. + * Once referenced, (item) will be automatically deleted (or recycled) when the last Ref that references it goes away. + * @param item A dynamically allocated object that the Ref class will assume responsibility for deleting. May be NULL. + * @param doRefCount If set false, then this Ref will not do any ref-counting on (item); rather it + * just acts as a fancy version of a normal C++ pointer. Specifically, it will not + * modify the object's reference count, nor will it ever delete the object. + * Setting this argument to false can be useful if you want to supply a + * Ref to an item that wasn't dynamically allocated from the heap. + * But if you do that, it allows the possibility of the object going away while + * other Refs are still using it, so be careful! + */ + explicit Ref(Item * item, bool doRefCount = true) : ConstRef(item, doRefCount) {/* empty */} + + /** Copy constructor. Creates an additional reference to the object referenced by (copyMe). + * The referenced object won't be deleted until ALL Refs that reference it are gone. + */ + Ref(const Ref & copyMe) : ConstRef(copyMe) {/* empty */} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Constructor */ + Ref(Ref && rhs) {this->SwapContents(rhs);} +#endif + + /** Attempts to set this reference by downcasting the reference in the provided RefCountableRef. + * If the downcast cannot be done (via dynamic_cast) then we become a NULL reference. + * @param ref The RefCountable reference to set ourselves from. + * @param junk This parameter is ignored; it is just here to disambiguate constructors. + */ + Ref(const RefCountableRef & ref, bool junk) : ConstRef(ref, junk) {/* empty */} + + /** Returns the ref-counted data item. The returned data item + * is only guaranteed valid for as long as this RefCount object exists. + */ + Item * GetItemPointer() const {return const_cast((static_cast *>(this))->GetItemPointer());} + + /** Read-write implementation of the convenience synonym for GetItemPointer(). */ + Item * operator()() const {return GetItemPointer();} + + /** Convenience method: Returns a read/write RefCountableRef object referencing the same RefCountable as this typed ref. */ + RefCountableRef GetRefCountableRef() const {return RefCountableRef(GetItemPointer(), this->IsRefCounting());} + + /** Redeclared here so that the AutoChooseHashFunctor code will see it */ + uint32 HashCode() const {return this->ConstRef::HashCode();} + + /** Assigment operator. + * Unreferences the previous held data item, and adds a reference to the data item of (rhs). + */ + inline Ref &operator=(const Ref & rhs) {this->SetRef(rhs.GetItemPointer(), rhs.IsRefCounting()); return *this;} + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Assignment operator */ + inline Ref &operator=(Ref && rhs) {this->SwapContents(rhs); return *this;} +#endif +}; + +/** This function works similarly to ConstRefCount::GetItemPointer(), except that this function + * can be safely called with a NULL ConstRef object as an argument. + * @param rt Pointer to a const reference object, or NULL. + * @returns If rt is NULL, this function returns NULL. Otherwise it returns rt->GetItemPointer(). + */ +template inline const Item * CheckedGetItemPointer(const ConstRef * rt) {return rt ? rt->GetItemPointer() : NULL;} + +/** This function works similarly to RefCount::GetItemPointer(), except that this function + * can be safely called with a NULL Ref object as an argument. + * @param rt Pointer to a const reference object, or NULL. + * @returns If rt is NULL, this function returns NULL. Otherwise it returns rt->GetItemPointer(). + */ +template inline Item * CheckedGetItemPointer(const Ref * rt) {return rt ? rt->GetItemPointer() : NULL;} + +/** Convenience method for converting a ConstRef into a non-const Ref. Only call this method if you are sure you know what you are doing, + * because usually the original ConstRef was declared as a ConstRef for a good reason! + */ +template inline Ref CastAwayConstFromRef(const ConstRef & constItem) {return Ref(const_cast(constItem()), constItem.IsRefCounting());} + +}; // end namespace muscle + +#endif diff --git a/util/SharedFilterSessionFactory.cpp b/util/SharedFilterSessionFactory.cpp new file mode 100644 index 00000000..740d811f --- /dev/null +++ b/util/SharedFilterSessionFactory.cpp @@ -0,0 +1,85 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "util/SharedFilterSessionFactory.h" +#include "system/SharedMemory.h" +#include "util/NetworkUtilityFunctions.h" +#include "util/MiscUtilityFunctions.h" + +namespace muscle { + +SharedFilterSessionFactory :: SharedFilterSessionFactory(const ReflectSessionFactoryRef & slaveRef, const String & sharedMemName, bool isGrantList, bool defaultPass) : ProxySessionFactory(slaveRef), _sharedMemName(sharedMemName), _isGrantList(isGrantList), _defaultPass(defaultPass) +{ + // empty +} + +SharedFilterSessionFactory :: ~SharedFilterSessionFactory() +{ + // empty +} + +AbstractReflectSessionRef SharedFilterSessionFactory :: CreateSession(const String & clientIP, const IPAddressAndPort & iap) +{ + TCHECKPOINT; + return ((GetSlave()())&&(IsAccessAllowedForIP(_sharedMemName, IsStandardLoopbackDeviceAddress(iap.GetIPAddress())?localhostIP:Inet_AtoN(clientIP()), _isGrantList, _defaultPass))) ? GetSlave()()->CreateSession(clientIP, iap) : AbstractReflectSessionRef(); +} + +bool SharedFilterSessionFactory :: IsAccessAllowedForIP(const String & sharedMemName, const ip_address & ip, bool isGrantList, bool defaultPass) +{ + bool allowAccess = defaultPass; + if (ip != invalidIP) + { + SharedMemory sm; + if (sm.SetArea(sharedMemName(), 0, true) == B_NO_ERROR) + { + Queue ifs; + bool gotIFs = false; // we'll demand-allocate them + + allowAccess = !isGrantList; // if there is a list, you're off it unless you're on it! + + const ip_address * ips = (const ip_address *) sm(); + uint32 numIPs = sm.GetAreaSize()/sizeof(ip_address); + for (uint32 i=0; i= 0)&&(_memberID < numSlots)) sa[_memberID] = 0; + _localCachedBytes = _localAllocated = 0; +} + +status_t SharedUsageLimitProxyMemoryAllocator :: ChangeDaemonCounter(int32 byteDelta) +{ + if (byteDelta > 0) + { + if (byteDelta > _localCachedBytes) + { + // Hmm, we don't have enough bytes locally, better ask for some from the shared region + int32 wantBytes = ((byteDelta/CACHE_BYTES)+1)*CACHE_BYTES; // round up to nearest multiple + if (ChangeDaemonCounterAux(wantBytes) == B_NO_ERROR) _localCachedBytes += wantBytes; + } + if (byteDelta > _localCachedBytes) return B_ERROR; // still not enough!? + _localCachedBytes -= byteDelta; + } + else + { + _localCachedBytes -= byteDelta; // actually adds, since byteDelta is negative + if (_localCachedBytes > 2*CACHE_BYTES) + { + int32 diffBytes = _localCachedBytes-CACHE_BYTES; // FogBugz #4569 -- reduce cache to our standard cache size + if (ChangeDaemonCounterAux(-diffBytes) == B_NO_ERROR) _localCachedBytes -= diffBytes; + } + } + return B_NO_ERROR; +} + +// Locks the shared memory region and adjusts our counter there. This is a bit expensive, so we try to minimize the number of times we do it. +status_t SharedUsageLimitProxyMemoryAllocator :: ChangeDaemonCounterAux(int32 byteDelta) +{ + status_t ret = B_ERROR; + if ((_memberID >= 0)&&(_shared.LockAreaReadWrite() == B_NO_ERROR)) + { + int32 numSlots = _shared.GetAreaSize() / sizeof(size_t); + size_t * sa = (size_t *) _shared(); + if ((sa)&&(_memberID < numSlots)) + { + if (byteDelta <= 0) + { + size_t reduceBy = -byteDelta; + if (reduceBy > _localAllocated) + { + // This should never happen, but just in case it does... + printf("Error, Attempted to reduce slot " INT32_FORMAT_SPEC "'s counter (currently " UINT64_FORMAT_SPEC ") by " UINT64_FORMAT_SPEC "! Setting counter at zero instead.\n", (int32)_memberID, (uint64)_localAllocated, (uint64)reduceBy); + _localAllocated = 0; + } + else _localAllocated -= reduceBy; + + ret = B_NO_ERROR; + } + else if ((CalculateTotalAllocationSum()+byteDelta) <= _maxBytes) + { + _localAllocated += byteDelta; + ret = B_NO_ERROR; + } + + sa[_memberID] = _localAllocated; // write our current allocation out to the shared memory region + } + _shared.UnlockArea(); + } + return ret; +} + +size_t SharedUsageLimitProxyMemoryAllocator :: CalculateTotalAllocationSum() const +{ + size_t total = 0; + const size_t * sa = (const size_t *) _shared(); + if (sa) + { + uint32 numSlots = GetNumSlots(); + for (uint32 i=0; itotalUsed)?(_maxBytes-totalUsed):0, ProxyMemoryAllocator::GetNumAvailableBytes(allocated)); +} + +status_t SharedUsageLimitProxyMemoryAllocator :: GetCurrentMemoryUsage(size_t * retCounts, size_t * optRetTotal) const +{ + if (_shared.LockAreaReadOnly() == B_NO_ERROR) + { + const size_t * sa = (const size_t *) _shared(); + if ((sa)&&(retCounts)) for (int32 i=GetNumSlots()-1; i>=0; i--) retCounts[i] = sa[i]; + if (optRetTotal) *optRetTotal = CalculateTotalAllocationSum(); + _shared.UnlockArea(); + return B_NO_ERROR; + } + else return B_ERROR; +} + +}; // end namespace muscle diff --git a/util/SharedUsageLimitProxyMemoryAllocator.h b/util/SharedUsageLimitProxyMemoryAllocator.h new file mode 100644 index 00000000..9d6ff0f4 --- /dev/null +++ b/util/SharedUsageLimitProxyMemoryAllocator.h @@ -0,0 +1,92 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef SharedUsageLimitProxyMemoryAllocator_h +#define SharedUsageLimitProxyMemoryAllocator_h + +#include "util/MemoryAllocator.h" +#include "system/SharedMemory.h" + +namespace muscle { + +/** This MemoryAllocator decorates its slave MemoryAllocator to + * enforce a user-defined per-process-group limit on how much + * memory may be allocated at any given time. Note that this + * class depends on the SharedMemory class to operate, so this + * class will only work on operating systems for which SharedMemory + * is implemented (read: Unix, currently). + */ +class SharedUsageLimitProxyMemoryAllocator : public ProxyMemoryAllocator +{ +public: + /** Constructor. + * @param groupKey A string that is used as a key by all processes in the group. + * Any process that uses the same key will use the same shared memory limit. + * @param memberID Our member ID in the group. This number should be between zero and (groupSize-1) inclusive, + * and should be different for each process in the process group. It is used to keep track + * of how much memory each process in the group is using. If the memberID is less than zero, + * this allocator will not track memory used by this process, and is then useful only to + * watch memory used by other processes. + * @param groupSize The maximum number of process that may be in the group. + * This value should be the same for all process in a given group. + * @param slaveRef Reference to a sub-MemoryAllocator whose methods we will call through to. + * @param maxBytes The maximum number of bytes that may be allocated, in aggregate, by all the process + * in the group. This value should be the same for all process in a given group. + */ + SharedUsageLimitProxyMemoryAllocator(const char * groupKey, int32 memberID, uint32 groupSize, const MemoryAllocatorRef & slaveRef, size_t maxBytes); + + /** Destructor. */ + virtual ~SharedUsageLimitProxyMemoryAllocator(); + + /** Overridden to return B_ERROR if memory usage would exceed the aggregate maximum due to this allocation. */ + virtual status_t AboutToAllocate(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + + /** Overridden to record this amount of memory being freed */ + virtual void AboutToFree(size_t currentlyAllocatedBytes, size_t allocRequestBytes); + + /** Overridden to return our total memory size as passed in to our ctor. */ + virtual size_t GetMaxNumBytes() const {return _maxBytes;} + + /** Overridden to return the number of bytes actually available to us. */ + virtual size_t GetNumAvailableBytes(size_t currentlyAllocated) const; + + /** Returns our own process's member ID value, as passed in to the constructor */ + int32 GetMemberID() const {return _memberID;} + + /** Returns the number of processes in our process group, as specified in the constructor. */ + uint32 GetGroupSize() const {return _groupSize;} + + /** Returns the current memory used, in bytes, for each member of our group. + * @param retCounts on success, the bytes used by each member of our group will + * be written into this array. This array must have enough space + * for the number of items specified by (groupSize) in our constructor. + * @param optRetTotal If non-NULL, then on success the shared memory area's current total-bytes-allocated value + * will be placed here. This value should always be the sum of the values returned in (retCounts). + * (although it is possible it might not be if the shared memory area becomes inconsistent). + * Defaults to NULL. + * @returns B_NO_ERROR on success, or B_ERROR if the information could not be accessed. + */ + status_t GetCurrentMemoryUsage(size_t * retCounts, size_t * optRetTotal = NULL) const; + + /** Returns true iff our shared memory area setup worked and we are ready for use. + * Returns false if there was a problem setting up and we aren't usable. + */ + bool IsValid() const {return (_shared.GetAreaSize() > 0);} + +private: + void ResetDaemonCounter(); // Note: this assumes the SharedMemory is already locked for read/write! + status_t ChangeDaemonCounter(int32 byteDelta); // Note: this assumes the SharedMemory is not locked yet + status_t ChangeDaemonCounterAux(int32 byteDelta); // Note: this assumes the SharedMemory is not locked yet + size_t CalculateTotalAllocationSum() const; + uint32 GetNumSlots() const {return _shared.GetAreaSize()/sizeof(size_t);} + + size_t _localAllocated; + size_t _maxBytes; + mutable SharedMemory _shared; + int32 _memberID; + uint32 _groupSize; + int32 _localCachedBytes; +}; + +}; // end namespace muscle + +#endif diff --git a/util/Socket.h b/util/Socket.h new file mode 100644 index 00000000..1b676b5a --- /dev/null +++ b/util/Socket.h @@ -0,0 +1,132 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleSocket_h +#define MuscleSocket_h + +#include "util/RefCount.h" +#include "util/CountedObject.h" + +namespace muscle { + +/** A simple socket-holder class to make sure that opened socket file + * descriptors don't get accidentally leaked. A Socket object is typically + * handed to a ConstSocketRef, whose constructor and destructor will implement + * the reference-counting necessary to automatically delete/recycle the Socket + * object (and thereby automatically close its held file descriptor) when the file + * descriptor is no longer needed for anything. + */ +class Socket : public RefCountable, public CountedObject +{ +public: + /** Default constructor. */ + Socket() : _fd(-1), _okayToClose(false) {/* empty */} + + /** Constructor. + * @param fd File descriptor of a socket. (fd) becomes property of this Socket object. + * @param okayToClose If true (fd) will be closed by the destructor. + * If false, we will not close (fd). Defaults to true. + */ + explicit Socket(int fd, bool okayToClose = true) : _fd(fd), _okayToClose(okayToClose) {/* empty */} + + /** Destructor. Closes our held file descriptor, if we have one. */ + virtual ~Socket(); + + /** Returns and releases our held file descriptor. + * When this method returns, ownership of the socket is transferred to the calling code. + */ + int ReleaseFileDescriptor() {int ret = _fd; _fd = -1; return ret;} + + /** Returns the held socket fd, but does not release ownership of it. */ + int GetFileDescriptor() const {return _fd;} + + /** Sets our file descriptor. Will close any old one if appropriate. + * @param fd The new file descriptor to hold, or -1 if we shouldn't hold a file descriptor any more. + * @param okayToCloseFD true iff we should close (fd) when we're done with it, false if we shouldn't. + * Note that this argument affects only what we'll do with (fd), and NOT what + * we'll do with any file descriptor we may already be holding!. This argument + * is not meaningful when (fd) is passed in as -1. + */ + void SetFileDescriptor(int fd, bool okayToCloseFD = true); + + /** Resets this Socket object to its just-constructed state, freeing any held socket descriptor if appropriate. + * This call is equivalent to calling SetFileDescriptor(-1, false). + */ + void Clear() {SetFileDescriptor(-1, false);} + +private: + friend class ObjectPool; + + /** Copy constructor, private and unimplemented on purpose */ + Socket(const Socket &); + + /** Assignment operator, used only for ObjectPool recycling, private on purpose */ + Socket & operator = (const Socket & /*rhs*/) {Clear(); return *this;} + + int _fd; + bool _okayToClose; +}; + +/** ConstSocketRef is subclassed rather than typedef'd so that I can override the == and != operators + * to check for equality based on the file descriptor value rather than on the address of the + * referenced Socket object. Doing it this way gives more intuitive hashing behavior (i.e. + * multiple ConstSocketRefs referencing the same file descriptor will hash to the same entry) + */ +class ConstSocketRef : public ConstRef +{ +public: + /** Default constructor */ + ConstSocketRef() : ConstRef() {/* empty */} + + /** This constructor takes ownership of the given Socket object. + * @param item The Socket object to take ownership of. This will be recycled/deleted when this ConstSocketRef is destroyed, unless (doRefCount) is specified as false. + * @param doRefCount If set false, we will not attempt to reference-count (item), and instead will only act like a dumb pointer. Defaults to true. + */ + ConstSocketRef(const Socket * item, bool doRefCount = true) : ConstRef(item, doRefCount) {/* empty */} + + /** Copy constructor + * @param copyMe The ConstSocketRef to become a copy of. Note that this doesn't copy (copyMe)'s underlying Socket object, but instead + * only adds one to its reference count. + */ + ConstSocketRef(const ConstSocketRef & copyMe) : ConstRef(copyMe) {/* empty */} + + /** Downcast constructor + * @param ref A RefCountableRef object that hopefully holds a Socket object + * @param junk This parameter doesn't mean anything; it is only here to differentiate this ctor from the other ctors. + */ + ConstSocketRef(const RefCountableRef & ref, bool junk) : ConstRef(ref, junk) {/* empty */} + + /** Comparison operator. Returns true iff (this) and (rhs) both contain the same file descriptor. */ + inline bool operator ==(const ConstSocketRef &rhs) const {return GetFileDescriptor() == rhs.GetFileDescriptor();} + + /** Comparison operator. Returns false iff (this) and (rhs) both contain the same file descriptor. */ + inline bool operator !=(const ConstSocketRef &rhs) const {return GetFileDescriptor() != rhs.GetFileDescriptor();} + + /** Convenience method. Returns the file descriptor we are holding, or -1 if we are a NULL reference. */ + int GetFileDescriptor() const {const Socket * s = GetItemPointer(); return s?s->GetFileDescriptor():-1;} + + /** When we're being used as a key in a Hashtable, key on the file descriptor we hold */ + uint32 HashCode() const {return CalculateHashCode(GetFileDescriptor());} +}; + +/** Returns a ConstSocketRef from our ConstSocketRef pool that references the passed in file descriptor. + * @param fd The file descriptor that the returned ConstSocketRef should be tracking. + * @param okayToClose if true, (fd) will be closed when the last ConstSocketRef + * that references it is destroyed. If false, it won't be. + * @param retNULLIfInvalidSocket If left true and (fd) is negative, then a NULL ConstSocketRef + * will be returned. If set false, then we will return a + * non-NULL ConstSocketRef object, with (fd)'s negative value in it. + * @returns a ConstSocketRef pointing to the specified socket on success, or a NULL ConstSocketRef on + * failure (out of memory). Note that in the failure case, (fd) will be closed unless + * (okayToClose) was false; that way you don't have to worry about closing it yourself. + */ +ConstSocketRef GetConstSocketRefFromPool(int fd, bool okayToClose = true, bool retNULLIfInvalidSocket = true); + +/** Convenience method: Returns a NULL socket reference. */ +inline const ConstSocketRef & GetNullSocket() {return GetDefaultObjectForType();} + +/** Convenience method: Returns a reference to an invalid Socket (i.e. a Socket object with a negative file descriptor). Note the difference between what this function returns and what GetNullSocket() returns! If you're not sure which of these two functions to use, then GetNullSocket() is probably the one you want. */ +const ConstSocketRef & GetInvalidSocket(); + +}; // end namespace muscle + +#endif diff --git a/util/SocketMultiplexer.cpp b/util/SocketMultiplexer.cpp new file mode 100644 index 00000000..338df1ae --- /dev/null +++ b/util/SocketMultiplexer.cpp @@ -0,0 +1,329 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#if defined(MUSCLE_USE_POLL) || defined(MUSCLE_USE_EPOLL) +# include // for INT_MAX +#endif + +#include "util/SocketMultiplexer.h" + +namespace muscle { + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) +static Mutex _multiplexersListMutex; +static SocketMultiplexer * _headMultiplexer = NULL; +void NotifySocketMultiplexersThatSocketIsClosed(int fd) +{ + if (_multiplexersListMutex.Lock() == B_NO_ERROR) + { + SocketMultiplexer * sm = _headMultiplexer; + while(sm) + { + sm->NotifySocketClosed(fd); + sm = sm->_nextMultiplexer; + } + _multiplexersListMutex.Unlock(); + } +} +#endif + +SocketMultiplexer :: SocketMultiplexer() +#if !defined(MUSCLE_USE_KQUEUE) && !defined(MUSCLE_USE_EPOLL) + : _curFDState(0) +#endif +{ +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + // Prepend this object to the global linked list of SocketMultiplexers + MutexGuard mg(_multiplexersListMutex); + _prevMultiplexer = NULL; + _nextMultiplexer = _headMultiplexer; + if (_headMultiplexer) _headMultiplexer->_prevMultiplexer = this; + _headMultiplexer = this; +#endif +} + +SocketMultiplexer :: ~SocketMultiplexer() +{ +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + // Unregister this object from the global linked list of SocketMultiplexers + MutexGuard mg(_multiplexersListMutex); + if (_headMultiplexer == this) _headMultiplexer = _nextMultiplexer; + if (_prevMultiplexer) _prevMultiplexer->_nextMultiplexer = _nextMultiplexer; + if (_nextMultiplexer) _nextMultiplexer->_prevMultiplexer = _prevMultiplexer; +#endif +} + +int SocketMultiplexer :: WaitForEvents(uint64 optTimeoutAtTime) +{ + int ret = GetCurrentFDState().WaitForEvents(optTimeoutAtTime); +#if !defined(MUSCLE_USE_KQUEUE) && !defined(MUSCLE_USE_EPOLL) + _curFDState = _curFDState?0:1; +#endif + GetCurrentFDState().Reset(); + return ret; +} + +int SocketMultiplexer :: FDState :: WaitForEvents(uint64 optTimeoutAtTime) +{ + // Calculate how long we should wait before timing out + uint64 waitTimeMicros; + if (optTimeoutAtTime == MUSCLE_TIME_NEVER) waitTimeMicros = MUSCLE_TIME_NEVER; + else + { + uint64 now = GetRunTime64(); + waitTimeMicros = (optTimeoutAtTime>now) ? (optTimeoutAtTime-now) : 0; + } + +#if defined(MUSCLE_USE_SELECT) + int maxFD = -1; + fd_set * sets[NUM_FDSTATE_SETS]; + for (uint32 i=0; i= 0) + { + maxFD = muscleMax(maxFD, _maxFD[i]); + sets[i] = &_fdSets[i]; + } + else sets[i] = NULL; + } +#endif + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + if (1) // for kqueue/epoll we need to always do the full check, or our state-bits might not get updated properly +#elif defined(MUSCLE_USE_POLL) + if (_pollFDArray.HasItems()) +#else + if (maxFD >= 0) +#endif + { +#if defined(MUSCLE_USE_KQUEUE) + if (ComputeStateBitsChangeRequests() != B_NO_ERROR) return B_ERROR; + + struct timespec waitTime; + struct timespec * pWaitTime; + if (waitTimeMicros == MUSCLE_TIME_NEVER) pWaitTime = NULL; + else + { + waitTime.tv_sec = MicrosToSeconds(waitTimeMicros); + waitTime.tv_nsec = MicrosToNanos(waitTimeMicros%MICROS_PER_SECOND); + pWaitTime = &waitTime; + } + + int ret = kevent(_kernelFD, _scratchChanges.HeadPointer(), _scratchChanges.GetNumItems(), _scratchEvents.HeadPointer(), _scratchEvents.GetNumItems(), pWaitTime); + if (ret >= 0) + { + // Record that kevent() accepted our _scratchChanges into its kernel-state, so we'll know not to send the changes again next time + for (HashtableIterator iter(_bits); iter.HasData(); iter++) + { + uint16 & b = iter.GetValue(); + uint16 userBits = (b&0x0F); + if (userBits) b = (userBits<<4); // post-kevent() state is: bits registered in-kernel but not in-userland or in-results + else (void) _bits.Remove(iter.GetKey()); // this FD isn't being monitored anymore, so we can forget about it + } + + // Now go through our _scratchEvents list and set bits for any flagged events, for quick lookup by the user + for (int i=0; i= 0) + { + // Now go through our _scratchEvents list and set bits for any flagged events, for quick lookup by the user + for (int i=0; ifd = fd; + p->events = GetPollBitsForFDSet(whichSet, true); + p->revents = 0; + if (_pollFDToArrayIndex.Put(fd, _pollFDArray.GetNumItems()-1) == B_NO_ERROR) return B_NO_ERROR; + else _pollFDArray.RemoveTail(); // roll back! + } + return B_ERROR; +} +#endif + +SocketMultiplexer :: FDState :: FDState() +{ +#if defined(MUSCLE_USE_KQUEUE) + _kernelFD = kqueue(); + if (_kernelFD < 0) printf("FDState: Error, couldn't allocate a kqueue!\n"); +#elif defined(MUSCLE_USE_EPOLL) + _kernelFD = epoll_create(1024); // note that this argument is ignored in modern unix, it just has to be greater than zero + if (_kernelFD < 0) printf("FDState: Error, epoll_create() failed!\n"); +#endif + + Reset(); +} + +SocketMultiplexer :: FDState :: ~FDState() +{ +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + if (_kernelFD >= 0) close(_kernelFD); +#endif +} + +#ifdef MUSCLE_USE_KQUEUE +status_t SocketMultiplexer :: FDState :: AddKQueueChangeRequest(int fd, uint32 whichSet, bool add) +{ + int16 filter; + switch(whichSet) + { + case FDSTATE_SET_READ: filter = EVFILT_READ; break; + case FDSTATE_SET_WRITE: filter = EVFILT_WRITE; break; + case FDSTATE_SET_EXCEPT: return B_NO_ERROR; // not sure how kqueue handles exception-sets: nerf them for now, since MUSCLE only uses them under Windows anyway + default: return B_ERROR; // bad set type!? + } + + struct kevent * kevt = _scratchChanges.AddTailAndGet(); + if (kevt == NULL) return B_ERROR; // wtf? + + EV_SET(kevt, fd, filter, add?EV_ADD:EV_DELETE, 0, 0, NULL); + return B_NO_ERROR; +} +#endif + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) +status_t SocketMultiplexer :: FDState :: ComputeStateBitsChangeRequests() +{ +#if defined(MUSCLE_USE_KQUEUE) + _scratchChanges.FastClear(); + (void) _scratchChanges.EnsureSize(GetMaxNumEvents()); // try to avoid multiple reallocs if possible +#endif + + // If any of our sockets were closed since the last call, we need to make sure to note that the kernel is no longer + // tracking them. Otherwise we can run into this problem: + // http://stackoverflow.com/questions/8608931/is-there-any-way-to-tell-that-a-file-descriptor-value-has-been-reused + { + // This scope is present so that _closedSocketMutex won't be locked while we iterate + { + MutexGuard mg(_closedSocketsMutex); + if (_closedSockets.HasItems()) _scratchClosedSockets.SwapContents(_closedSockets); + } + if (_scratchClosedSockets.HasItems()) + { + for (HashtableIterator iter(_scratchClosedSockets); iter.HasData(); iter++) + { + uint16 * bits = _bits.Get(iter.GetKey()); + if (bits) + { + uint16 & b = *bits; + b &= 0x0F; // Remove all bits except for the userland-registration-bits, to force a kernel re-registration below + if (b == 0) _bits.Remove(iter.GetKey()); // No userland-registration-bits either? Then we can discard the record + } + } + _scratchClosedSockets.Clear(); + } + } + + // Generate change requests to the kernel, based on how the userBits differ from the kernelBits + for (HashtableIterator iter(_bits); iter.HasData(); iter++) + { + uint16 & bits = iter.GetValue(); + bits &= ~(0xF00); // get rid of any leftover results-bits from the previous iteration + uint8 userBits = ((bits>>0)&0x0F); + uint8 kernBits = ((bits>>4)&0x0F); + if (userBits != kernBits) + { +#if defined(MUSCLE_USE_KQUEUE) + for (uint32 i=0; i +# include "system/Mutex.h" +#elif defined(MUSCLE_USE_EPOLL) +# include +# include "system/Mutex.h" +#elif defined(MUSCLE_USE_POLL) +# ifdef WIN32 +# if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600) +# error "Compiling with MUSCLE_USE_POLL under Windows requires that _WIN32_WINNT be defined as 0x600 (aka Vista) or higher!" +# endif +# else +# include +# endif +#else +# ifndef MUSCLE_USE_SELECT +# define MUSCLE_USE_SELECT 1 // we use select() by default if none of the above MUSCLE_USE_* compiler flags were defined +# endif +#endif + +#ifndef MUSCLE_USE_SELECT +# include "util/Hashtable.h" +# include "util/Queue.h" +#endif + +namespace muscle { + +/** This class allows a thread to wait until one or more of a set of + * file descriptors is ready for use, or until a specified time is + * reached, whichever comes first. + * + * This class has underlying implementations available for various OS + * mechanisms. The default implementation uses select(), since that + * mechanism is the most widely portable. However, you can force this class + * to use poll(), epoll(), or kqueue() instead, by specifying the compiler + * flag -DMUSCLE_USE_POLL, -DMUSCLE_USE_EPOLL, or -DMUSCLE_USE_KQUEUE + * (respectively) on the compile line. + */ +class SocketMultiplexer +{ +public: + /** Constructor. */ + SocketMultiplexer(); + + /** Destructor. */ + ~SocketMultiplexer(); + + /** Call this to indicate that you want the next call to Wait() to return if the specified + * socket has data ready to read. + * @note this registration is cleared after WaitForEvents() returns, so you will generally want to re-register + * your socket on each iteration of your event loop. + * @param fd The file descriptor to watch for data-ready-to-read. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory or bad fd value?) + */ + inline status_t RegisterSocketForReadReady(int fd) {return GetCurrentFDState().RegisterSocket(fd, FDSTATE_SET_READ);} + + /** Call this to indicate that you want the next call to WaitForEvents() to return if the specified + * socket has buffer space available to write to. + * @note this registration is cleared after WaitForEvents() returns, so you will generally want to re-register + * your socket on each iteration of your event loop. + * @param fd The file descriptor to watch for space-available-to-write. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory or bad fd value?) + */ + inline status_t RegisterSocketForWriteReady(int fd) {return GetCurrentFDState().RegisterSocket(fd, FDSTATE_SET_WRITE);} + + /** Call this to indicate that you want the next call to WaitForEvents() to return if the specified + * socket has buffer space available to write to. + * @note this registration is cleared after WaitForEvents() returns, so you will generally want to re-register + * your socket on each iteration of your event loop. + * @param fd The file descriptor to watch for space-available-to-write. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory or bad fd value?) + */ + inline status_t RegisterSocketForExceptionRaised(int fd) {return GetCurrentFDState().RegisterSocket(fd, FDSTATE_SET_EXCEPT);} + + /** This method is equivalent to any of the three other Register methods, except that in this method + * you can specify the set via a FDSTATE_SET_* value. + * @note this registration is cleared after WaitForEvents() returns, so you will generally want to re-register + * your socket on each iteration of your event loop. + * @param fd The file descriptor to watch for the event type specified by (whichSet) + * @param whichSet A FDSTATE_SET_* value indicating the type of event to watch the socket for. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory or bad fd value?) + */ + inline status_t RegisterSocketForEventsByTypeIndex(int fd, uint32 whichSet) {return GetCurrentFDState().RegisterSocket(fd, whichSet);} + + /** Blocks until at least one of the events specified in previous RegisterSocketFor*() + * calls becomes valid, or for (optMaxWaitTimeMicros) microseconds, whichever comes first. + * @note All socket-registrations will be cleared after this method call returns. You will typically + * want to re-register them (by calling RegisterSocketFor*() again) before calling WaitForEvents() again. + * @param timeoutAtTime The time to return 0 at if no events occur before then, or MUSCLE_TIME_NEVER + * if not timeout is desired. Uses the sameDefaults to MUSCLE_TIME_NEVER. + * Specifying 0 (or any other value not greater than the current value returned + * by GetRunTime64()) will effect a poll, guaranteed to return immediately. + * @returns The number of socket-registrations that indicated that they are currently ready, + * or 0 if the timeout period elapsed without any events happening, or -1 if an error occurred. + */ + int WaitForEvents(uint64 timeoutAtTime = MUSCLE_TIME_NEVER); + + /** Call this after WaitForEvents() returns, to find out if the specified file descriptor has + * data ready to read or not. + * @param fd The file descriptor to inquire about. Note that (fd) must have been previously + * registered via RegisterSocketForReadyReady() for this method to work correctly. + * @returns true if (fd) has data ready for reading, or false if it does not. + */ + inline bool IsSocketReadyForRead(int fd) const {return GetAlternateFDState().IsSocketReady(fd, FDSTATE_SET_READ);} + + /** Call this after WaitForEvents() returns, to find out if the specified file descriptor has + * buffer space available to write to, or not. + * @param fd The file descriptor to inquire about. Note that (fd) must have been previously + * registered via RegisterSocketForWriteReady() for this method to work correctly. + * @returns true if (fd) has buffer space available for writing to, or false if it does not. + */ + inline bool IsSocketReadyForWrite(int fd) const {return GetAlternateFDState().IsSocketReady(fd, FDSTATE_SET_WRITE);} + + /** Call this after WaitForEvents() returns, to find out if the specified file descriptor has + * an exception state raised, or not. + * @param fd The file descriptor to inquire about. Note that (fd) must have been previously + * registered via RegisterSocketForExceptionRaised() for this method to work correctly. + * @returns true if (fd) has an exception state raised, or false if it does not. + */ + inline bool IsSocketExceptionRaised(int fd) const {return GetAlternateFDState().IsSocketReady(fd, FDSTATE_SET_EXCEPT);} + + /** Call this after WaitForEvents() returns, to find out if the specified file descriptor has + * an event of the specified type flagged, or not. Note that this method can be used as an + * equivalent to any of the previous three methods. + * @param fd The file descriptor to inquire about. Note that (fd) must have been previously + * registered via the appropriate RegisterSocketFor*() call for this method to work correctly. + * @param whichSet A FDSTATE_SET_* value indicating the type of event to query the socket for. + * @returns true if (fd) has the specified event-type flagged, or false if it does not. + */ + inline bool IsSocketEventOfTypeFlagged(int fd, uint32 whichSet) const {return GetAlternateFDState().IsSocketReady(fd, whichSet);} + + enum { + FDSTATE_SET_READ = 0, + FDSTATE_SET_WRITE, + FDSTATE_SET_EXCEPT, + NUM_FDSTATE_SETS + }; + +private: + class FDState + { + public: + FDState(); + ~FDState(); + + void Reset(); + + inline status_t RegisterSocket(int fd, int whichSet) + { + if (fd < 0) return B_ERROR; + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + uint16 * b = _bits.GetOrPut(fd); + if (b == NULL) return B_ERROR; + *b |= (1<= FD_SETSIZE) return B_ERROR; +# endif + FD_SET(fd, &_fdSets[whichSet]); + _maxFD[whichSet] = muscleMax(_maxFD[whichSet], fd); +#endif + return B_NO_ERROR; + } + + inline bool IsSocketReady(int fd, int whichSet) const + { +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + return ((_bits.GetWithDefault(fd) & (1<<(whichSet+8))) != 0); +#elif defined(MUSCLE_USE_POLL) + uint32 idx; + return ((_pollFDToArrayIndex.Get(fd, idx) == B_NO_ERROR)&&((_pollFDArray[idx].revents & GetPollBitsForFDSet(whichSet, false)) != 0)); +#else + return (FD_ISSET(fd, const_cast(&_fdSets[whichSet])) != 0); +#endif + } + int WaitForEvents(uint64 timeoutAtTime); + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + void NotifySocketClosed(int fd) + { + MutexGuard mg(_closedSocketsMutex); + (void) _closedSockets.PutWithDefault(fd); + } +#endif + + private: +#if defined(MUSCLE_USE_KQUEUE) + status_t AddKQueueChangeRequest(int fd, uint32 whichSet, bool add); +#endif +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + status_t ComputeStateBitsChangeRequests(); + uint32 GetMaxNumEvents() const {return _bits.GetNumItems()*2;} // times two since each FD could have both read and write events + + Mutex _closedSocketsMutex; // necessary since NotifySocketClosed() might get called from any thread + Hashtable _closedSockets; // written to by NotifySocketClosed(), read-and-cleared by WaitForEvents(), protected by _closedSocketsMutex + Hashtable _scratchClosedSockets; // Used for double-buffering purposes + + int _kernelFD; + Hashtable _bits; // fd -> (nybble #0 for userland registrations, nybble #1 for kernel-state, nybble #2 for results) +# if defined(MUSCLE_USE_KQUEUE) + Queue _scratchChanges; + Queue _scratchEvents; +# else + Queue _scratchEvents; +# endif +#elif defined(MUSCLE_USE_POLL) + short GetPollBitsForFDSet(uint32 whichSet, bool isRegister) const + { + switch(whichSet) + { + case FDSTATE_SET_READ: return POLLIN |(isRegister?0:POLLHUP); + case FDSTATE_SET_WRITE: return POLLOUT|(isRegister?0:POLLHUP); +#ifdef WIN32 + case FDSTATE_SET_EXCEPT: return (isRegister?0:POLLERR); // WSAPoll() won't accept POLLERR as an events flag... see discussion at http://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ +#else + case FDSTATE_SET_EXCEPT: return POLLERR; +#endif + default: return 0; + } + } + status_t PollRegisterNewSocket(int fd, uint32 whichSet); + Hashtable _pollFDToArrayIndex; + Queue _pollFDArray; +#else + int _maxFD[NUM_FDSTATE_SETS]; + fd_set _fdSets[NUM_FDSTATE_SETS]; +#endif + }; + +#if defined(MUSCLE_USE_KQUEUE) || defined(MUSCLE_USE_EPOLL) + friend void NotifySocketMultiplexersThatSocketIsClosed(int); + void NotifySocketClosed(int fd) {GetCurrentFDState().NotifySocketClosed(fd);} + inline FDState & GetCurrentFDState() {return _fdState;} + inline const FDState & GetCurrentFDState() const {return _fdState;} + inline FDState & GetAlternateFDState() {return _fdState;} + inline const FDState & GetAlternateFDState() const {return _fdState;} + FDState _fdState; // For kqueue and epoll we only need to keep one FDState in memory at at time + + SocketMultiplexer * _prevMultiplexer; + SocketMultiplexer * _nextMultiplexer; +#else + inline FDState & GetCurrentFDState() {return _fdStates[_curFDState];} + inline const FDState & GetCurrentFDState() const {return _fdStates[_curFDState];} + inline FDState & GetAlternateFDState() {return _fdStates[_curFDState?0:1];} + inline const FDState & GetAlternateFDState() const {return _fdStates[_curFDState?0:1];} + + FDState _fdStates[2]; + int _curFDState; +#endif +}; + +}; // end namespace muscle + +#endif diff --git a/util/String.cpp b/util/String.cpp new file mode 100644 index 00000000..7e147309 --- /dev/null +++ b/util/String.cpp @@ -0,0 +1,938 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "util/String.h" +#include "support/Point.h" +#include "support/Rect.h" +#include + +namespace muscle { + +void String::ClearAndFlush() +{ + if (IsArrayDynamicallyAllocated()) muscleFree(_strData._bigBuffer); + ClearSmallBuffer(); + _bufferLen = sizeof(_strData._smallBuffer); + _length = 0; +} + +status_t String::SetFromString(const String & s, uint32 firstChar, uint32 afterLastChar) +{ + afterLastChar = muscleMin(afterLastChar, s.Length()); + uint32 len = (afterLastChar > firstChar) ? (afterLastChar-firstChar) : 0; + if (len > 0) + { + if (EnsureBufferSize(len+1, false) != B_NO_ERROR) return B_ERROR; // guaranteed not to realloc in the (&s==this) case + + char * b = GetBuffer(); + memmove(b, s()+firstChar, len); // memmove() is used in case (&s==this) + b[len] = '\0'; + _length = len; + } + else Clear(); + + return B_NO_ERROR; +} + +status_t String::SetCstr(const char * str, uint32 maxLen) +{ + // If (str)'s got a NUL byte before maxLen, make (maxLen) smaller. + // We can't call strlen(str) because we don't have any guarantee that the NUL + // byte even exists! Without a NUL byte, strlen() could run off into the weeds... + uint32 sLen = 0; + if (str) {while((sLen 0) + { + if (str[maxLen-1] != '\0') maxLen++; // make room to add the NUL byte if necessary + if (EnsureBufferSize(maxLen, false) != B_NO_ERROR) return B_ERROR; // guaranteed not to realloc in the IsCharInLocalArray(str) case + + char * b = GetBuffer(); + memmove(b, str, maxLen-1); // memmove() is used in case (str) points into our array + b[maxLen-1] = '\0'; + _length = maxLen-1; + } + else Clear(); + + return B_NO_ERROR; +} + +String & +String::operator+=(const String &other) +{ + uint32 otherLen = other.Length(); // save this value first, in case (&other==this) + if ((otherLen > 0)&&(EnsureBufferSize(Length()+otherLen+1, true) == B_NO_ERROR)) + { + memmove(GetBuffer()+_length, other(), otherLen+1); // memmove() is used in case (&other==this) + _length += otherLen; + } + return *this; +} + +String & +String::operator+=(const char * other) +{ + if (other == NULL) other = ""; + uint32 otherLen = (uint32) strlen(other); + if (otherLen > 0) + { + if (IsCharInLocalArray(other)) return operator+=(String(other,otherLen)); // avoid potential free-ing of (other) inside EnsureBufferSize() + if (EnsureBufferSize(Length()+otherLen+1, true) == B_NO_ERROR) + { + memcpy(GetBuffer()+_length, other, otherLen+1); + _length += otherLen; + } + } + return *this; +} + +String & String::operator-=(const char aChar) +{ + int idx = LastIndexOf(aChar); + if (idx >= 0) + { + char * b = GetBuffer(); + memmove(b+idx, b+idx+1, _length-idx); + --_length; + } + return *this; +} + +String & String::operator-=(const String &other) +{ + if (*this == other) Clear(); + else if (other.HasChars()) + { + int idx = LastIndexOf(other); + if (idx >= 0) + { + uint32 newEndIdx = idx+other.Length(); + char * b = GetBuffer(); + memmove(b+idx, b+newEndIdx, 1+_length-newEndIdx); + _length -= other.Length(); + } + } + return *this; +} + +String & String::operator-=(const char * other) +{ + int otherLen = ((other)&&(*other)) ? (int)strlen(other) : 0; + if (otherLen > 0) + { + int idx = LastIndexOf(other); + if (idx >= 0) + { + uint32 newEndIdx = idx+otherLen; + char * b = GetBuffer(); + memmove(b+idx, b+newEndIdx, 1+_length-newEndIdx); + _length -= otherLen; + } + } + return *this; +} + +String & String::operator<<(int rhs) +{ + char buff[64]; + sprintf(buff, "%d", rhs); + return *this << buff; +} + +String & String::operator<<(float rhs) +{ + char buff[64]; + sprintf(buff, "%.2f", rhs); + return *this << buff; +} + +String & String::operator<<(bool rhs) +{ + const char * val = rhs ? "true" : "false"; + return *this << val; +} + +void String::Reverse() +{ + if (HasChars()) + { + uint32 from = 0; + uint32 to = Length()-1; + char * b = GetBuffer(); + while(from 0)) + { + if (*c == findChar) + { + *c = replaceChar; + maxReplaceCount--; + ret++; + } + c++; + } + } + return ret; +} + +String String :: WithReplacements(char replaceMe, char withMe, uint32 maxReplaceCount) const +{ + String ret = *this; + ret.Replace(replaceMe, withMe, maxReplaceCount); + return ret; +} + +int32 String::Replace(const String & replaceMe, const String & withMe, uint32 maxReplaceCount) +{ + TCHECKPOINT; + + if (maxReplaceCount == 0) return 0; + if (replaceMe.IsEmpty()) return 0; // can't replace an empty string, that's silly! + if (replaceMe == withMe) return muscleMin(maxReplaceCount, GetNumInstancesOf(replaceMe)); // no changes necessary! + if (&replaceMe == this) return (SetFromString(withMe)==B_NO_ERROR)?1:-1; // avoid self-entanglement + if (&withMe == this) return Replace(replaceMe, String(withMe), maxReplaceCount); // avoid self-entanglement + + String temp; + int32 perInstanceDelta = ((int32)withMe.Length())-((int32)replaceMe.Length()); + if (perInstanceDelta > 0) + { + // If we are replacing a shorter string with a longer string, we'll have to do a copy-and-swap + uint32 numInstances = muscleMin(GetNumInstancesOf(replaceMe), maxReplaceCount); + if (numInstances == 0) return 0; // no changes necessary! + if (temp.Prealloc(Length()+(perInstanceDelta*numInstances)) != B_NO_ERROR) return -1; + } + + // This code works for both the in-place and the copy-over modes! + int32 ret = 0; + const char * readPtr = Cstr(); + char * writePtr = (perInstanceDelta > 0) ? temp.GetBuffer() : NULL; + while(1) + { + char * nextReplaceMe = (maxReplaceCount>0) ? strstr((char *) readPtr, (char *) replaceMe()) : NULL; + if (nextReplaceMe) + { + ret++; + if (writePtr) + { + uint32 numBytes = (uint32) (nextReplaceMe-readPtr); + if (perInstanceDelta != 0) memmove(writePtr, readPtr, numBytes); + writePtr += numBytes; + } + else writePtr = nextReplaceMe; + + memcpy(writePtr, withMe(), withMe.Length()); + readPtr = nextReplaceMe + replaceMe.Length(); + writePtr += withMe.Length(); + maxReplaceCount--; + } + else + { + if (writePtr) + { + // Finish up + uint32 numBytes = (uint32) (Cstr()+Length()-readPtr); + if (perInstanceDelta != 0) memmove(writePtr, readPtr, numBytes); + writePtr += numBytes; + *writePtr = '\0'; + if (perInstanceDelta > 0) + { + temp._length = (uint32) (writePtr-temp()); + SwapContents(temp); + } + else _length = (uint32) (writePtr-Cstr()); + } + return ret; + } + } + return ret; // just to shut the compiler up; we never actually get here +} + +String String :: WithReplacements(const String & replaceMe, const String & withMe, uint32 maxReplaceCount) const +{ + String ret = *this; + ret.Replace(replaceMe, withMe, maxReplaceCount); + return ret; +} + +int String::LastIndexOf(const String &s2, uint32 fromIndex) const +{ + TCHECKPOINT; + + if (s2.IsEmpty()) return Length()-1; + if (fromIndex >= Length()) return -1; + for (int i=fromIndex; i>=0; i--) if (strncmp(Cstr()+i, s2.Cstr(), s2.Length()) == 0) return i; + return -1; +} + +int String::LastIndexOf(const char * s2, uint32 fromIndex) const +{ + TCHECKPOINT; + + if (s2 == NULL) s2 = ""; + uint32 s2Len = (uint32) strlen(s2); + if (s2Len == 0) return Length()-1; + if (fromIndex >= Length()) return -1; + for (int i=fromIndex; i>=0; i--) if (strncmp(Cstr()+i, s2, s2Len) == 0) return i; + return -1; +} + +String String::ToLowerCase() const +{ + String ret(*this); + char * b = ret.GetBuffer(); + for (uint32 i=0; istartIdx; endIdx--) if (!IsSpaceChar(s[endIdx])) break; + return String(*this, (uint32)startIdx, (uint32)(endIdx+1)); +} + +uint32 String :: GetNumInstancesOf(char ch) const +{ + uint32 ret = 0; + for (const char * s = Cstr(); (*s != '\0'); s++) if (*s == ch) ret++; + return ret; +} + +uint32 String :: GetNumInstancesOf(const String & substring) const +{ + TCHECKPOINT; + + uint32 ret = 0; + if (substring.HasChars()) + { + uint32 lastIdx = 0; + int32 idx; + while((idx = IndexOf(substring, lastIdx)) >= 0) + { + ret++; + lastIdx = idx + substring.Length(); + } + } + return ret; +} + +uint32 String :: GetNumInstancesOf(const char * substring) const +{ + TCHECKPOINT; + + if (substring == NULL) substring = ""; + uint32 ret = 0; + uint32 substringLength = (uint32) strlen(substring); + if (substringLength > 0) + { + uint32 lastIdx = 0; + int32 idx; + while((idx = IndexOf(substring, lastIdx)) >= 0) + { + ret++; + lastIdx = idx + substringLength; + } + } + return ret; +} + +String String :: Prepend(const String & str, uint32 count) const +{ + TCHECKPOINT; + + if (&str == this) return Prepend(String(str), count); // avoid self-entanglement + + String ret; + uint32 newLen = (count*str.Length())+Length(); + if (ret.Prealloc(newLen) == B_NO_ERROR) + { + char * b = ret.GetBuffer(); + + if (str.HasChars()) + { + for (uint32 i=0; i 0) + { + for (uint32 i=0; i 0) + { + for (uint32 i=0; i= 0) + { + char token[64]; + sprintf(token, "%%" INT32_FORMAT_SPEC, lowestArg); + String ret(*this); + (void) ret.Replace(token, buf); + return ret; + } + else return *this; +} + +/* strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. (JAF-note: I have altered + this software slightly -- it is not the same as the original code!) + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* These are defined as macros to make it easier to adapt this code to + * different characters types or comparison functions. */ +static inline int nat_isdigit(char a) {return isdigit((unsigned char) a);} +static inline int nat_isspace(char a) {return isspace((unsigned char) a);} +static inline char nat_toupper(char a) {return toupper((unsigned char) a);} + +static int nat_compare_right(char const *a, char const *b) +{ + int bias = 0; + + /* The longest run of digits wins. That aside, the greatest + value wins, but we can't know that it will until we've scanned + both numbers to know that they have the same magnitude, so we + remember it in BIAS. */ + for (;; a++, b++) + { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) return bias; + else if (!nat_isdigit(*a)) return -1; + else if (!nat_isdigit(*b)) return +1; + else if (*a < *b) + { + if (!bias) bias = -1; + } + else if (*a > *b) + { + if (!bias) bias = +1; + } + else if ((!*a)&&(!*b)) return bias; + } + return 0; +} + +static int nat_compare_left(char const *a, char const *b) +{ + /* Compare two left-aligned numbers: the first to have a different value wins. */ + while(1) + { + if ((!nat_isdigit(*a))&&(!nat_isdigit(*b))) return 0; + else if (!nat_isdigit(*a)) return -1; + else if (!nat_isdigit(*b)) return +1; + else if (*a < *b) return -1; + else if (*a > *b) return +1; + ++a; ++b; + } + return 0; +} + +static int strnatcmp0(char const *a, char const *b, int fold_case) +{ + int ai = 0; + int bi = 0; + while(1) + { + char ca = a[ai]; + char cb = b[bi]; + + /* skip over leading spaces or zeros */ + while(nat_isspace(ca)) ca = a[++ai]; + while(nat_isspace(cb)) cb = b[++bi]; + + /* process run of digits */ + if ((nat_isdigit(ca))&&(nat_isdigit(cb))) + { + int fractional = (ca == '0' || cb == '0'); + if (fractional) + { + int result = nat_compare_left(a+ai, b+bi); + if (result != 0) return result; + } + else + { + int result = nat_compare_right(a+ai, b+bi); + if (result != 0) return result; + } + } + + /* The strings compare the same. Call strcmp() to break the tie. */ + if (!ca && !cb) return strcmp(a,b); + + if (fold_case) + { + ca = nat_toupper(ca); + cb = nat_toupper(cb); + } + + if (ca < cb) return -1; + else if (ca > cb) return +1; + + ++ai; ++bi; + } +} + +int strnatcmp(char const *a, char const *b) {return strnatcmp0(a, b, 0);} +int strnatcasecmp(char const *a, char const *b) {return strnatcmp0(a, b, 1);} + +int NumericAwareStrcmp(const char * s1, const char * s2) {return strnatcmp( s1, s2);} +int NumericAwareStrcasecmp(const char * s1, const char * s2) {return strnatcasecmp(s1, s2);} + +}; // end namespace muscle diff --git a/util/String.h b/util/String.h new file mode 100644 index 00000000..1dd739f9 --- /dev/null +++ b/util/String.h @@ -0,0 +1,1073 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +/* NOTE TO MACOS/X X-CODE USERS: If you are trying to #include + * and X-Code is "helpfully" pulling in this file instead (because the + * OS/X filesystem is case-insensitive), you can get around that problem + * by adding "USE_HEADERMAP = NO" to your X-Code target settings. + * ref: http://lists.apple.com/archives/xcode-users/2004/Aug/msg00934.html + * --Jeremy + */ + +#ifndef MuscleString_h +#define MuscleString_h + +#include +#include "support/Flattenable.h" +#include "syslog/SysLog.h" +#include "system/GlobalMemoryAllocator.h" // for muscleFree() + + +namespace muscle { + +#ifdef MUSCLE_COUNT_STRING_COPY_OPERATIONS +enum { + STRING_OP_DEFAULT_CTOR = 0, + STRING_OP_CSTR_CTOR, + STRING_OP_COPY_CTOR, + STRING_OP_PARTIAL_COPY_CTOR, + STRING_OP_SET_FROM_CSTR, + STRING_OP_SET_FROM_STRING, + STRING_OP_MOVE_CTOR, + STRING_OP_MOVE_FROM_STRING, + STRING_OP_DTOR, + NUM_STRING_OPS +}; +extern uint32 _stringOpCounts[NUM_STRING_OPS]; +extern void PrintAndClearStringCopyCounts(const char * optDesc = NULL); +# define MUSCLE_INCREMENT_STRING_OP_COUNT(which) _stringOpCounts[which]++ +#else +# define MUSCLE_INCREMENT_STRING_OP_COUNT(which) +static inline void PrintAndClearStringCopyCounts(const char * optDesc = NULL) {(void) optDesc;} +#endif + +class Point; +class Rect; + +// strings containing up to SMALL_MUSCLE_STRING_LENGTH characters can be stored inline, without requiring a dynamic memory allocation +#ifndef SMALL_MUSCLE_STRING_LENGTH +# define SMALL_MUSCLE_STRING_LENGTH 7 // Note: values greater than 7 will cause sizeof(String) to grow to greater than 16 bytes! +#endif + +/** Same as strcmp(), except that it will sort numbers within the string numerically rather than lexically. */ +int NumericAwareStrcmp(const char * s1, const char * s2); + +/** Same as NumericAwareStrcmp(), but case-insensitive. */ +int NumericAwareStrcasecmp(const char * s1, const char * s2); + +/** Wrapper for strcasecmp(), which isn't always named the same on all OS's */ +inline int Strcasecmp(const char * s1, const char * s2) +{ +#ifdef WIN32 + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif +} + +/** Wrapper for strncasecmp(), which isn't always named the same on all OS's */ +inline int Strncasecmp(const char * s1, const char * s2, size_t n) +{ +#ifdef WIN32 + return _strnicmp(s1, s2, n); +#else + return strncasecmp(s1, s2, n); +#endif +} + +/** An arbitrary-length character-string class. Represents a dynamically resizable, NUL-terminated ASCII string. + * This class can be used to hold UTF8-encoded strings as well, but because the code in this class is not + * UTF8-aware, certain operations (such as Reverse() and ToLowerCase()) will not do the right thing when used in + * conjunction with non-ASCII UTF8 data. + */ +class String : public PseudoFlattenable +{ +public: + /** Constructor. + * @param str If non-NULL, the initial value for this String. + * @param maxLen The maximum number of characters to place into + * this String (not including the NUL terminator byte). + * Default is unlimited (i.e. scan the entire string no matter how long it is) + */ + String(const char * str = NULL, uint32 maxLen = MUSCLE_NO_LIMIT) : _bufferLen(sizeof(_strData._smallBuffer)), _length(0) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(str?STRING_OP_CSTR_CTOR:STRING_OP_DEFAULT_CTOR); + ClearSmallBuffer(); + if (str) (void) SetCstr(str, maxLen); + } + + /** Copy Constructor. + * @param str String to become a copy of. + */ + String(const String & str) : _bufferLen(sizeof(_strData._smallBuffer)), _length(0) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_COPY_CTOR); + ClearSmallBuffer(); (void) SetFromString(str); + } + + /** This constructor sets this String to be a substring of the specified String. + * @param str String to become a copy of. + * @param beginIndex Index of the first character in (str) to include. + * @param endIndex Index after the last character in (str) to include. + * Defaults to a very large number, so that by default the entire remainder of the string is included. + */ + String(const String & str, uint32 beginIndex, uint32 endIndex=MUSCLE_NO_LIMIT) : _bufferLen(sizeof(_strData._smallBuffer)), _length(0) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_PARTIAL_COPY_CTOR); + ClearSmallBuffer(); (void) SetFromString(str, beginIndex, endIndex); + } + + /** Destructor. */ + ~String() + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_DTOR); + if (IsArrayDynamicallyAllocated()) muscleFree(_strData._bigBuffer); + } + + /** Assignment Operator. + * @param val Pointer to the C-style string to copy from. If NULL, this string will become "". + */ + String & operator = (const char * val) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_SET_FROM_CSTR); + (void) SetCstr(val); return *this; + } + + /** Assignment Operator. + * @param rhs String to become a copy of. + */ + String & operator = (const String & rhs) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_SET_FROM_STRING); + (void) SetFromString(rhs); return *this; + } + + /** Append Operator. + * @param rhs A string to append to this string. + */ + String & operator += (const String & rhs); + + /** Append Operator. + * @param rhs A string to append to this string. If NULL, this operation is a no-op. + */ + String & operator += (const char * rhs); + + /** Append Operator. + * @param ch A character to append to this string. + */ + String & operator += (char ch) + { + if (EnsureBufferSize(Length()+2, true) == B_NO_ERROR) + { + GetBuffer()[_length++] = ch; + WriteNULTerminatorByte(); + } + return *this; + } + + /** Remove Operator. + * @param rhs A substring to remove from this string; the + * last instance of the substring will be cut out. + * If (rhs) is not found, there is no effect. + */ + String & operator -= (const String & rhs); + + /** Remove Operator. + * @param rhs A substring to remove from this string; the + * last instance of the substring will be cut out. + * If (rhs) is not found, there is no effect. + */ + String & operator -= (const char * rhs); + + /** Remove Operator. + * @param ch A character to remove from this string; the last + * instance of this char will be cut out. If (ch) is + * not found, there is no effect. + */ + String & operator -= (const char ch); + + /** Append 'Stream' Operator. + * @param rhs A String to append to this string. + * @return a non const String refrence to 'this' so you can chain appends. + */ + String & operator << (const String & rhs) {return (*this += rhs);} + + /** Append 'Stream' Operator. + * @param rhs A const char* to append to this string. + * @return a non const String refrence to 'this' so you can chain appends. + */ + String & operator << (const char * rhs) {return (*this += rhs);} + + /** Append 'Stream' Operator. + * @param rhs An int to append to this string. + * @return a non const String refrence to 'this' so you can chain appends. + */ + String & operator << (int rhs); + + /** Append 'Stream' Operator. + * @param rhs A float to append to this string. Formatting is set at 2 decimals of precision. + * @return a non const String refrence to 'this' so you can chain appends. + */ + String & operator << (float rhs); + + /** Append 'Stream' Operator. + * @param rhs A bool to append to this string. Converts to 'true' and 'false' strings appropriately. + * @return a non const String refrence to 'this' so you can chain appends. + */ + String & operator << (bool rhs); + + /** Comparison Operator. Returns true if the two strings are equal (as determined by strcmp()) + * @param rhs A string to compare ourself with + */ + bool operator == (const String & rhs) const {return ((this == &rhs)||((Length() == rhs.Length())&&(strcmp(Cstr(), rhs.Cstr()) == 0)));} + + /** Comparison Operator. Returns true if the two strings are equal (as determined by strcmp()) + * @param rhs Pointer to a C string to compare with. NULL pointers are considered a synonym for "". + */ + bool operator == (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") == 0);} + + /** Comparison Operator. Returns true if the two strings are not equal (as determined by strcmp()) + * @param rhs A string to compare ourself with + */ + bool operator != (const String & rhs) const {return !(*this == rhs);} + + /** Comparison Operator. Returns true if the two strings are not equal (as determined by strcmp()) + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool operator != (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") != 0);} + + /** Comparison Operator. Returns true if this string comes before (rhs) lexically. + * @param rhs A string to compare ourself with + */ + bool operator < (const String & rhs) const {return (this == &rhs) ? false : (strcmp(Cstr(), rhs.Cstr()) < 0);} + + /** Comparison Operator. Returns true if this string comes before (rhs) lexically. + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool operator < (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") < 0);} + + /** Comparison Operator. Returns true if this string comes after (rhs) lexically. + * @param rhs A string to compare ourself with + */ + bool operator > (const String & rhs) const {return (this == &rhs) ? false : (strcmp(Cstr(), rhs.Cstr()) > 0);} + + /** Comparison Operator. Returns true if this string comes after (rhs) lexically. + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool operator > (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") > 0);} + + /** Comparison Operator. Returns true if the two strings are equal, or this string comes before (rhs) lexically. + * @param rhs A string to compare ourself with + */ + bool operator <= (const String & rhs) const {return (this == &rhs) ? true : (strcmp(Cstr(), rhs.Cstr()) <= 0);} + + /** Comparison Operator. Returns true if the two strings are equal, or this string comes before (rhs) lexically. + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool operator <= (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") <= 0);} + + /** Comparison Operator. Returns true if the two strings are equal, or this string comes after (rhs) lexically. + * @param rhs A string to compare ourself with + */ + bool operator >= (const String & rhs) const {return (this == &rhs) ? true : (strcmp(Cstr(), rhs.Cstr()) >= 0);} + + /** Comparison Operator. Returns true if the two strings are equal, or this string comes after (rhs) lexically. + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool operator >= (const char * rhs) const {return (strcmp(Cstr(), rhs?rhs:"") >= 0);} + + /** Array Operator. Used to get easy access to the characters that make up this string. + * @param index Index of the character to return. Be sure to only use valid indices! + */ + char operator [] (uint32 index) const {VerifyIndex(index); return Cstr()[index];} + + /** Array Operator. Used to get easy access to the characters that make up this string. + * @param index Index of the character to set. Be sure to only use valid indices! + */ + char & operator [] (uint32 index) {VerifyIndex(index); return GetBuffer()[index];} + + /** Adds a space to the end of this string. */ + void operator ++ (int) {(*this) += ' ';} + + /** Remove the last character from the end of this string. It's a no-op if the string is already empty. */ + void operator -- (int) {TruncateChars(1);} + + /** Returns the character at the (index)'th position in the string. + * @param index A value between 0 and (Length()-1), inclusive. + * @return A character value. + */ + char CharAt(uint32 index) const {return operator[](index);} + + /** Compares this string to another string using strcmp() + * @param rhs A string to compare ourself with + */ + int CompareTo(const String & rhs) const {return strcmp(Cstr(), rhs.Cstr());} + + /** Compares this string to a C string using strcmp() + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + int CompareTo(const char * rhs) const {return strcmp(Cstr(), rhs?rhs:"");} + + /** Compares this string to another string using NumericAwareStrcmp() + * @param rhs A string to compare ourself with + */ + int NumericAwareCompareTo(const String & rhs) const {return NumericAwareStrcmp(Cstr(), rhs.Cstr());} + + /** Compares this string to a C string using NumericAwareStrcmp() + * @param rhs Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + int NumericAwareCompareTo(const char * rhs) const {return NumericAwareStrcmp(Cstr(), rhs?rhs:"");} + + /** Returns a read-only C-style pointer to our held character string. */ + const char * Cstr() const {return IsArrayDynamicallyAllocated() ? _strData._bigBuffer : _strData._smallBuffer;} + + /** Convenience synonym for Cstr(). */ + const char * operator()() const {return Cstr();} + + /** Clears this string so that it contains no characters. Equivalent to setting this string to "". */ + void Clear() {_length = 0; WriteNULTerminatorByte();} + + /** Similar to Clear(), except this version also frees up any dynamically allocated character arary we may have cached. */ + void ClearAndFlush(); + + /** Sets our state from the given C-style string. + * @param str The new string to copy from. If maxLen is negative, this may be NULL. + * @param maxLen If set, the maximum number of characters to copy (not including the NUL + * terminator byte). By default, all characters up to the first encountered + * NUL terminator byte will be copied out of (str). + */ + status_t SetCstr(const char * str, uint32 maxLen = MUSCLE_NO_LIMIT); + + /** Sets our state from the given String. This is similar to the copy constructor, except + * that it allows you to optionally specify a maximum length, and it allows you to detect + * out-of-memory errors. + * @param str The new string to copy from. + * @param beginIndex Index of the first character in (str) to include. + * Defaults to zero, so that by default the entire string is included. + * @param endIndex Index after the last character in (str) to include. + * Defaults to a very large number, so that by default the entire remainder of the string is included. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory?) + */ + status_t SetFromString(const String & str, uint32 beginIndex = 0, uint32 endIndex = MUSCLE_NO_LIMIT); + + /** Returns true iff this string is a zero-length string. */ + bool IsEmpty() const {return (_length == 0);} + + /** Returns true iff this string starts with a number. + * @param allowNegativeValues if true, negative values will be considered as numbers also. Defaults to true. + */ + bool StartsWithNumber(bool allowNegativeValues = true) const {const char * s = Cstr(); return ((isdigit(*s))||((allowNegativeValues)&&(s[0]=='-')&&(isdigit(s[1]))));} + + /** Returns true iff this string is not a zero-length string. */ + bool HasChars() const {return (_length > 0);} + + /** Returns true iff this string starts with (prefix) + * @param c a character to check for at the end of this String. + */ + bool EndsWith(char c) const {return (_length > 0)&&(Cstr()[_length-1] == c);} + + /** Returns true iff this string ends with (suffix) + * @param suffix a String to check for at the end of this String. + */ + bool EndsWith(const String & suffix) const {return (Length() < suffix.Length()) ? false : (strcmp(Cstr()+(Length()-suffix.Length()), suffix.Cstr()) == 0);} + + /** Returns true iff this string ends with (suffix) + * @param suffix a String to check for at the end of this String. NULL pointers are treated as a synonym for "". + */ + bool EndsWith(const char * suffix) const + { + if (suffix == NULL) suffix = ""; + uint32 suffixLen = (uint32) strlen(suffix); + return (Length() < suffixLen) ? false : (strcmp(Cstr()+(Length()-suffixLen), suffix) == 0); + } + + /** Returns true iff this string is equal to (string), as determined by strcmp(). + * @param str a String to compare this String with. + */ + bool Equals(const String & str) const {return (*this == str);} + + /** Returns true iff this string is equal to (str), as determined by strcmp(). + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool Equals(const char * str) const {return (*this == str);} + + /** Returns true iff this string contains a single character (c). + * @param c a character to compare this String with. + */ + bool Equals(char c) const {return (_length == 1)&&(Cstr()[0] == c);} + + /** Returns the first index of (ch) in this string starting at or after (fromIndex), or -1 if not found. + * @param ch A character to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int IndexOf(char ch, uint32 fromIndex = 0) const + { + const char * temp = (fromIndex < Length()) ? strchr(Cstr()+fromIndex, ch) : NULL; + return temp ? (int)(temp - Cstr()) : -1; + } + + /** Returns true iff (ch) is contained in this string. + * @param ch A character to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + bool Contains(char ch, uint32 fromIndex = 0) const {return (IndexOf(ch, fromIndex) >= 0);} + + /** Returns true iff substring (str) is in this string starting at or after (fromIndex). + * @param str A String to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + bool Contains(const String & str, uint32 fromIndex = 0) const {return (IndexOf(str, fromIndex) >=0);} + + /** Returns true iff the substring (str) is in this string starting at or after (fromIndex). + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + bool Contains(const char * str, uint32 fromIndex = 0) const {return (IndexOf(str, fromIndex) >= 0);} + + /** Returns the first index of substring (str) in this string starting at or after (fromIndex), or -1 if not found. + * @param str A String to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int IndexOf(const String & str, uint32 fromIndex = 0) const + { + const char * temp = (fromIndex < Length()) ? strstr(Cstr()+fromIndex, str()) : NULL; + return temp ? (int)(temp - Cstr()) : -1; + } + + /** Returns the first index of substring (str) in this string starting at or after (fromIndex), or -1 if not found. + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int IndexOf(const char * str, uint32 fromIndex = 0) const + { + const char * temp = (fromIndex < Length()) ? strstr(Cstr()+fromIndex, str?str:"") : NULL; + return temp ? (int)(temp - Cstr()) : -1; + } + + /** Returns the last index of (ch) in this string starting at or after (fromIndex), or -1 if not found. + * @param ch A character to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int LastIndexOf(char ch, uint32 fromIndex = 0) const + { + if (fromIndex < Length()) + { + const char * s = Cstr()+fromIndex; + const char * p = Cstr()+Length(); + while(--p >= s) if (*p == ch) return (int)(p-Cstr()); + } + return -1; + } + + /** Returns the last index of substring (str) in this string + * @param str A String to look for in this string. + */ + int LastIndexOf(const String & str) const {return (str.Length() <= Length()) ? LastIndexOf(str, Length()-str.Length()) : -1;} + + /** Returns the last index of substring (str) in this string + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + int LastIndexOf(const char * str) const + { + if (str == NULL) str = ""; + uint32 strLen = (uint32) strlen(str); + return (strLen <= Length()) ? LastIndexOf(str, Length()-strLen) : -1; + } + + /** Returns the last index of substring (str) in this string starting at or after (fromIndex), or -1 if not found. + * @param str A String to look for in this string. + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int LastIndexOf(const String & str, uint32 fromIndex) const; + + /** Returns the last index of substring (str) in this string starting at or after (fromIndex), or -1 if not found. + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param fromIndex Index of the first character to start searching at in this String. Defaults to zero (i.e. start from the first character) + */ + int LastIndexOf(const char * str, uint32 fromIndex) const; + + /** Returns the number of characters in the string (not including the terminating NUL byte) */ + uint32 Length() const {return _length;} + + /** Returns the number of bytes of storage we have allocated. Note that this value will often + * be greater than the value returned by Length(), since we allocate extra bytes to minimize + * the number of reallocations that must be done as data is being added to a String. + */ + uint32 GetNumAllocatedBytes() const {return _bufferLen;} + + /** Returns the number of instances of (c) in this string. + * @param ch The character to count the number of instances of in this String. + */ + uint32 GetNumInstancesOf(char ch) const; + + /** Returns the number of instances of (substring) in this string. + * @param substring String to count the number of instances of in this String. + */ + uint32 GetNumInstancesOf(const String & substring) const; + + /** Returns the number of instances of (substring) in this string. + * @param substring C string to count the number of instances of in this String. + */ + uint32 GetNumInstancesOf(const char * substring) const; + + /** Returns true iff this string starts with (c) + * @param c The character to see if this string starts with or not + */ + bool StartsWith(char c) const {return (*Cstr() == c);} + + /** Returns true iff this string starts with (prefix) + * @param prefix The prefix to see whether this string starts with or not + */ + bool StartsWith(const String & prefix) const {return ((Length() >= prefix.Length())&&(strncmp(Cstr(), prefix(), prefix.Length()) == 0));} + + /** Returns true iff this string starts with (prefix) + * @param prefix Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + */ + bool StartsWith(const char * prefix) const + { + if (prefix == NULL) prefix = ""; + uint32 prefixLen = (uint32) strlen(prefix); + return (Length() < prefixLen) ? false : (strncmp(Cstr(), prefix, prefixLen) == 0); + } + + /** Returns true iff this string starts with the first (offset) characters of (prefix) */ + bool StartsWith(const String & prefix, uint32 offset) const {return ((offset+prefix.Length()<=Length())&&(strncmp(Cstr()+offset, prefix.Cstr(), prefix.Length()) == 0));} + + /** Returns true iff this string starts with the first (offset) characters of (prefix) + * @param prefix Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param offset An index into this string to begivn the comparison at. + */ + bool StartsWith(const char * prefix, uint32 offset) const + { + if (prefix == NULL) prefix = ""; + uint32 prefixLen = (uint32) strlen(prefix); + return ((offset+prefixLen<=Length())&&(strncmp(Cstr()+offset, prefix, prefixLen) == 0)); + } + + /** Returns a string that consists of (count) copies of (str), followed by this string. + * @param str The string to prepend + * @param count How many instances of (str) to prepend. Defaults to 1. + */ + String Prepend(const String & str, uint32 count = 1) const; + + /** Returns a string that consists of (count) copies of (str), followed by this string. + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param count How many instances of (str) should be prepended to this string. Defaults to 1. + */ + String Prepend(const char * str, uint32 count = 1) const; + + /** Returns a string that consists of (count) copies of (c), followed by this string. + * @param c The character to prepend + * @param count How many instances of (c) to prepend. Defaults to 1. + */ + String Prepend(char c, uint32 count = 1) const {const char cc[2] = {c, '\0'}; return Prepend(cc, count);} + + /** Similar to Prepend(), but this version will insert separator string between our current content and the prepended string, if necessary. + * @param str A string to prepended to the end of this string. + * @param sep Pointer to the string used to separate words. Defaults to " " + * @returns a reference to this object, which will have had the specified string prepended, with an inserted (sep) infix if necessary. + */ + String PrependWord(const String & str, const char * sep = " ") const {return str.AppendWord(*this, sep);} + + /** Returns a string that consists of this string followed by (count) copies of (str). + * @param str A string to append to the end of this string. + * @param count How many copies of (str) to append. Defaults to 1. + */ + String Append(const String & str, uint32 count = 1) const; + + /** Returns a string that consists of this string followed by (count) copies of (str). + * @param str Pointer to a C string to compare to. NULL pointers are considered a synonym for "". + * @param count How many instances of (str) should be appended to this string. Defaults to 1. + */ + String Append(const char * str, uint32 count = 1) const; + + /** Returns a string that consists of this string followed by (count) copies of (c). + * @param c The character to append + * @param count How many instances of (c) to append. Defaults to 1. + */ + String Append(char c, uint32 count = 1) const {const char cc[2] = {c, '\0'}; return Append(cc, count);} + + /** Similar to the += operator, but this version will insert a separator between our current content and the appended string, if necessary. + * @param str Pointer to a C string to return appended to this string. NULL pointers are considered a synonym for "". + * @param sep Pointer to the string used to separate words. Defaults to " " + * @returns a reference to this object, which will have had the specified string appended, with an inserted (sep) infix if necessary. + */ + String AppendWord(const char * str, const char * sep = " ") const; + + /** Similar to the += operator, but this version will insert a separator between our current content and the appended string, if necessary. + * @param str A string to append to the end of this string. + * @param sep Pointer to the string used to separate words. Defaults to " " + * @returns a reference to this object, which will have had the specified string appended, with an inserted (sep) infix if necessary. + */ + String AppendWord(const String & str, const char * sep = " ") const; + + /** Returns a string that is like this string, but padded out to the specified minimum length with (padChar). + * @param minLength Minimum length that the returned string should be. + * @param padOnRight If true, (padChar)s will be added to the right; if false (the default), they will be added on the left. + * @param padChar The character to pad out the string with. Defaults to ' '. + * @returns the new, padded String. + */ + String Pad(uint32 minLength, bool padOnRight = false, char padChar = ' ') const; + + /** Returns a string that is the same as this one, except that the beginning of each line in the string has (numIndentChars) + * instances of (indentChar) prepended to it. + * @param numIndentChars How many indent characters to prepend to each line + * @param indentChar The character to use to make the indentations. Defaults to ' '. + * @returns the indented string. + */ + String Indent(uint32 numIndentChars, char indentChar = ' ') const; + + /** Returns a string that consists of only the last part of this string, starting with index (beginIndex). Does not modify the string it is called on. */ + String Substring(uint32 beginIndex) const {return String(*this, beginIndex);} + + /** Returns a string that consists of only the characters in this string from range (beginIndex) to (endIndex-1). Does not modify the string it is called on. */ + String Substring(uint32 beginIndex, uint32 endIndex) const {return String(*this, beginIndex, endIndex);} + + /** Returns a string that consists of only the last part of this string, starting with the first character after the last instance of (markerString). + * If (markerString) is not found in the string, then this entire String is returned. + * For example, String("this is a test").Substring("is a") returns " test". + * Does not modify the string it is called on. + */ + String Substring(const String & markerString) const + { + int idx = LastIndexOf(markerString); + return (idx >= 0) ? String(*this, idx+markerString.Length()) : *this; + } + + /** See the Substring(const String &) documentation for details. */ + String Substring(const char * markerString) const + { + int idx = LastIndexOf(markerString); + return (idx >= 0) ? String(*this, idx+(int)strlen(markerString)) : *this; // if (idx >= 0), then we know markerString is non-NULL + } + + /** Returns a string that consists of only the characters in the string from range (beginIndex) until the character just before + * the first character in (markerString). If (markerString) is not found, then the entire substring starting at (beginIndex) is returned. + * For example, String("this is a test").Substring(1, "is a") returns "his ". + * Does not modify the string it is called on. + */ + String Substring(uint32 beginIndex, const String & markerString) const {return String(*this, beginIndex, (uint32) IndexOf(markerString, beginIndex));} + + /** See the Substring(uint32, const String &) documentation for details. */ + String Substring(uint32 beginIndex, const char * markerString) const {return String(*this, beginIndex, (uint32) IndexOf(markerString, beginIndex));} + + /** Returns an all lower-case version of this string. Does not modify the string it is called on. */ + String ToLowerCase() const; + + /** Returns an all upper-case version of this string. Does not modify the string it is called on. */ + String ToUpperCase() const; + + /** Returns a version of this string where All Words Are In Lower Case Except For The First Letter. */ + String ToMixedCase() const; + + /** Returns an version of this string that has all leading and trailing whitespace removed. Does not modify the string it is called on. */ + String Trim() const; + + /** Swaps the state of this string with (swapWithMe). Very efficient since little or no data copying is required. */ + inline void SwapContents(String & swapWithMe) + { + muscleSwap(_strData, swapWithMe._strData); + muscleSwap(_bufferLen, swapWithMe._bufferLen); + muscleSwap(_length, swapWithMe._length); // always do this + } + +#ifdef MUSCLE_USE_CPLUSPLUS11 + /** C++11 Move Constructor */ + String(String && rhs) : _bufferLen(0), _length(0) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_MOVE_CTOR); + ClearSmallBuffer(); + SwapContents(rhs); + } + + /** C++11 Move Assignment Operator */ + String & operator =(String && rhs) + { + MUSCLE_INCREMENT_STRING_OP_COUNT(STRING_OP_MOVE_FROM_STRING); + SwapContents(rhs); + return *this; + } +#endif + + /** Like CompareTo(), but case insensitive. */ + int CompareToIgnoreCase(const String & s) const {return Strcasecmp(Cstr(), s());} + + /** Like CompareTo(), but case insensitive. */ + int CompareToIgnoreCase(const char * s) const {return Strcasecmp(Cstr(), s?s:"");} + + /** Like NumericAwareCompareTo(), but case insensitive. */ + int NumericAwareCompareToIgnoreCase(const String & s) const {return NumericAwareStrcasecmp(Cstr(), s());} + + /** Like NumericAwareCompareTo(), but case insensitive. */ + int NumericAwareCompareToIgnoreCase(const char * s) const {return NumericAwareStrcasecmp(Cstr(), s?s:"");} + + /** Like EndsWith(), but case insensitive. */ + bool EndsWithIgnoreCase(char c) const {return (_length > 0)&&(tolower(Cstr()[_length-1]) == tolower(c));} + + /** Like EndsWith(), but case insensitive. */ + bool EndsWithIgnoreCase(const String & s) const {return ToLowerCase().EndsWith(s.ToLowerCase());} + + /** Like Equals(), but case insensitive. */ + bool EqualsIgnoreCase(const String & s) const {return (Strcasecmp(Cstr(), s()) == 0);} + + /** Like Equals(), but case insensitive. */ + bool EqualsIgnoreCase(char c) const {return (_length==1)&&(tolower(Cstr()[0])==tolower(c));} + + /** Like Contains(), but case insensitive. */ + bool ContainsIgnoreCase(const String & s) const {return ToLowerCase().Contains(s.ToLowerCase());} + + /** Like Contains(), but case insensitive. */ + bool ContainsIgnoreCase(const String & s, uint32 f) const {return ToLowerCase().Contains(s.ToLowerCase(),f);} + + /** Like Contains(), but case insensitive. */ + bool ContainsIgnoreCase(char ch) const {return ToLowerCase().Contains((char)tolower(ch));} + + /** Like Contains(), but case insensitive. */ + bool ContainsIgnoreCase(char ch, uint32 f) const {return ToLowerCase().Contains((char)tolower(ch),f);} + + /** Like IndexOf(), but case insensitive. */ + int IndexOfIgnoreCase(const String & s) const {return ToLowerCase().IndexOf(s.ToLowerCase());} + + /** Like IndexOf(), but case insensitive. */ + int IndexOfIgnoreCase(const String & s, uint32 f) const {return ToLowerCase().IndexOf(s.ToLowerCase(),f);} + + /** Like IndexOf(), but case insensitive. */ + int IndexOfIgnoreCase(char ch) const {return ToLowerCase().IndexOf((char)tolower(ch));} + + /** Like IndexOf(), but case insensitive. */ + int IndexOfIgnoreCase(char ch, uint32 f) const {return ToLowerCase().IndexOf((char)tolower(ch),f);} + + /** Like LastIndexOf(), but case insensitive. */ + int LastIndexOfIgnoreCase(const String & s) const {return ToLowerCase().LastIndexOf(s.ToLowerCase());} + + /** Like LastIndexOf(), but case insensitive. */ + int LastIndexOfIgnoreCase(const String & s, uint32 f) const {return ToLowerCase().LastIndexOf(s.ToLowerCase(),f);} + + /** Like LastIndexOf(), but case insensitive. */ + int LastIndexOfIgnoreCase(char ch) const {return ToLowerCase().LastIndexOf((char)tolower(ch));} + + /** Like LastIndexOf(), but case insensitive. */ + int LastIndexOfIgnoreCase(char ch, uint32 f) const {return ToLowerCase().LastIndexOf((char)tolower(ch),f);} + + /** Like EndsWith(), but case insensitive. */ + bool StartsWithIgnoreCase(char c) const {return (_length > 0)&&(tolower(Cstr()[0]) == tolower(c));} + + /** Like StartsWith(), but case insensitive. */ + bool StartsWithIgnoreCase(const String & s) const {return ToLowerCase().StartsWith(s.ToLowerCase());} + + /** Like StartsWith(), but case insensitive. */ + bool StartsWithIgnoreCase(const String & s, uint32 o) const {return ToLowerCase().StartsWith(s.ToLowerCase(),o);} + + /** Returns a hash code for this string */ + inline uint32 HashCode() const {return CalculateHashCode(Cstr(), Length());} + + /** Calculates a 64-bit hash code for this string. */ + inline uint64 HashCode64() const {return CalculateHashCode64(Cstr(), Length());} + + /** Replaces all instances of (oldChar) in this string with (newChar). + * @param replaceMe The character to search for. + * @param withMe The character to replace all occurrences of (replaceMe) with. + * @param maxReplaceCount The maximum number of characters that should be replaced. Defaults to MUSCLE_NO_LIMIT. + * @returns The number of characters that were successfully replaced. + */ + uint32 Replace(char replaceMe, char withMe, uint32 maxReplaceCount = MUSCLE_NO_LIMIT); + + /** Same as Replace(), but instead of modifying this object, it returns a modified copy, and the called object remains unchanged. + * @param replaceMe The character to search for. + * @param withMe The character to replace all occurrences of (replaceMe) with. + * @param maxReplaceCount The maximum number of characters that should be replaced. Defaults to MUSCLE_NO_LIMIT. + * @returns The modified string. + */ + String WithReplacements(char replaceMe, char withMe, uint32 maxReplaceCount = MUSCLE_NO_LIMIT) const; + + /** Replaces all instances of (replaceMe) in this string with (withMe). + * @param replaceMe The substring to search for. + * @param withMe The substring to replace all occurrences of (replaceMe) with. + * @param maxReplaceCount The maximum number of substrings that should be replaced. Defaults to MUSCLE_NO_LIMIT. + * @returns The number of substrings that were successfully replaced, or -1 if the operation failed (out of memory) + */ + int32 Replace(const String & replaceMe, const String & withMe, uint32 maxReplaceCount = MUSCLE_NO_LIMIT); + + /** Same as Replace(), but instead of modifying this object, it returns a modified copy, and the called object remains unchanged. + * @param replaceMe The substring to search for. + * @param withMe The substring to replace all occurrences of (replaceMe) with. + * @param maxReplaceCount The maximum number of substrings that should be replaced. Defaults to MUSCLE_NO_LIMIT. + * @returns The modified string. + */ + String WithReplacements(const String & replaceMe, const String & withMe, uint32 maxReplaceCount = MUSCLE_NO_LIMIT) const; + + /** Reverses the order of all characters in the string, so that e.g. "string" becomes "gnirts" */ + void Reverse(); + + /** Part of the Flattenable pseudo-interface. + * @return false + */ + bool IsFixedSize() const {return false;} + + /** Part of the Flattenable pseudo-interface. + * @return B_STRING_TYPE + */ + uint32 TypeCode() const {return B_STRING_TYPE;} + + /** Returns true iff (tc) equals B_STRING_TYPE. */ + bool AllowsTypeCode(uint32 tc) const {return (TypeCode()==tc);} + + /** Part of the Flattenable pseudo-interface. + * @return Length()+1 (the +1 is for the terminating NUL byte) + */ + uint32 FlattenedSize() const {return Length()+1;} + + /** Part of the Flattenable pseudo-interface. Flattens our string into (buffer). + * @param buffer A byte array to receive the flattened version of this string. + * There must be at least FlattenedSize() bytes available in this array. + * The clever secret here is that a flattened String is just a C-style + * null-terminated character array, and can be used interchangably as such. + */ + void Flatten(uint8 *buffer) const {memcpy((char *)buffer, Cstr(), Length()+1);} + + /** Unflattens a String from (buf). + * @param buf an array of (size) bytes. + * @param size the number of bytes in (buf). + * @return B_NO_ERROR (never fails!) + */ + status_t Unflatten(const uint8 *buf, uint32 size) {return SetCstr((const char *)buf, size);} + + /** Makes sure that we have pre-allocated enough space for a NUL-terminated string + * at least (numChars) bytes long (not including the NUL byte). + * If not, this method will try to allocate the space. + * @param numChars How much space to pre-allocate, in ASCII characters. + * @returns B_NO_ERROR on success, or B_ERROR on failure (out of memory). + */ + status_t Prealloc(uint32 numChars) {return EnsureBufferSize(numChars+1, true);} + + /** Removes (numCharsToTruncate) characters from the end of this string. + * @param numCharsToTruncate How many characters to truncate. If greater than the + * length of this String, this String will become empty. + */ + void TruncateChars(uint32 numCharsToTruncate) {_length -= muscleMin(_length, numCharsToTruncate); WriteNULTerminatorByte();} + + /** Makes sure this string is no longer than (maxLength) characters long by truncating + * any extra characters, if necessary + * @param maxLength Maximum length that this string should be allowed to be. + */ + void TruncateToLength(uint32 maxLength) {_length = muscleMin(_length, maxLength); WriteNULTerminatorByte();} + + /** Returns a string like this string, but with the appropriate %# tokens + * replaced with a textual representation of the values passed in as (value). + * For example, String("%1 is a %2").Arg(13).Arg("bakers dozen") would return + * the string "13 is a bakers dozen". + * @param value The value to replace in the string. + * @param fmt The format parameter to pass to sprintf(). Note that if you supply your + * own format string, you'll need to be careful since a badly chosen format + * string could lead to a crash or out-of-bounds memory overwrite. In particular, + * the format string should match the type specified, and should not lead to + * more than 256 bytes being written. + * @returns a new String with the appropriate tokens replaced. + */ + String Arg(bool value, const char * fmt = "%s") const; + + /** As above, but for char values. */ + String Arg(char value, const char * fmt = "%i") const; + + /** As above, but for unsigned char values. */ + String Arg(unsigned char value, const char * fmt = "%u") const; + + /** As above, but for short values. */ + String Arg(short value, const char * fmt = "%i") const; + + /** As above, but for unsigned shot values. */ + String Arg(unsigned short value, const char * fmt = "%u") const; + + /** As above, but for int values. */ + String Arg(int value, const char * fmt = "%i") const; + + /** As above, but for unsigned int values. */ + String Arg(unsigned int value, const char * fmt = "%u") const; + + /** As above, but for long values. */ + String Arg(long value, const char * fmt = "%li") const; + + /** As above, but for unsigned long values. */ + String Arg(unsigned long value, const char * fmt = "%lu") const; + + /** As above, but for long long values. */ + String Arg(long long value, const char * fmt = INT64_FORMAT_SPEC) const; + + /** As above, but for unsigned long long values. */ + String Arg(unsigned long long value, const char * fmt = UINT64_FORMAT_SPEC) const; + + /** As above, but for double values. */ + String Arg(double value, const char * fmt = "%f") const; + + /** As above, but for string values. */ + String Arg(const String & value) const; + + /** As above, but for Point values. */ + String Arg(const Point & value, const char * fmt = "%f,%f") const; + + /** As above, but for Rect values. */ + String Arg(const Rect & value, const char * fmt = "%f,%f,%f,%f") const; + + /** As above, but for C string values. */ + String Arg(const char * value) const; + + /** As above, but for printing pointer values. */ + String Arg(const void * value) const; + + /** If this string already ends with the specified character, returns this string verbatim. + * Otherwise, returns a String equivalent to this one but with the specified character appended. + * @param c The char we want to be sure is at the end of the returned String. + */ + String WithSuffix(char c) const {return EndsWith(c) ? *this : Append(c);} + + /** If this string already ends with the specified string, returns this string verbatim. + * Otherwise, returns a String equivalent to this one but with the specified string appended. + * @param str The string we want to be sure is at the end of the returned String. + */ + String WithSuffix(const String & str) const {return EndsWith(str) ? *this : Append(str);} + + /** If this string already begins with the specified character, returns this string verbatim. + * Otherwise, returns a String equivalent to this one but with the specified character prepended. + * @param c The character we want to be sure is at the beginning of the returned String. + */ + String WithPrefix(char c) const {return StartsWith(c) ? *this : Prepend(c);} + + /** If this string already begins with the specified string, returns this string verbatim. + * Otherwise, returns a String equivalent to this one but with the specified string prepended. + * @param str The string we want to be sure is at the beginning of the returned String. + */ + String WithPrefix(const String & str) const {return StartsWith(str) ? *this : Prepend(str);} + + /** Returns a String like this one, but with any characters (c) removed from the end. + * @param c The char we want to be sure is not at the end of the returned String. + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all trailing (c) chars. + */ + String WithoutSuffix(char c, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any instances of (str) removed from the end. + * @param str The substring we want to be sure is not at the end of the returned String. + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all trailing (str) substrings. + */ + String WithoutSuffix(const String & str, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any characters (c) removed from the beginning. + * @param c The char we want to be sure is not at the beginning of the returned String. + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all starting (c) chars. + */ + String WithoutPrefix(char c, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any instances of (str) removed from the beginning. + * @param str The substring we want to be sure is not at the beginning of the returned String. + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all starting (str) substrings. + */ + String WithoutPrefix(const String & str, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any characters (c) removed from the end. + * @param c The char we want to be sure is not at the end of the returned String (case-insensitive). + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all trailing (c) chars. + */ + String WithoutSuffixIgnoreCase(char c, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any instances of (str) removed from the end. + * @param str The substring we want to be sure is not at the end of the returned String (case-insensitive). + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all trailing (str) substrings. + */ + String WithoutSuffixIgnoreCase(const String & str, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any characters (c) removed from the beginning. + * @param c The char we want to be sure is not at the beginning of the returned String (case-insensitive). + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all starting (c) chars. + */ + String WithoutPrefixIgnoreCase(char c, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a String like this one, but with any instances of (str) removed from the beginning. + * @param str The substring we want to be sure is not at the beginning of the returned String (case-insensitive). + * @param maxToRemove Maximum number of instances of (c) to remove from the returned String. + * Defaults to MUSCLE_NO_LIMIT, i.e. remove all starting (str) substrings. + */ + String WithoutPrefixIgnoreCase(const String & str, uint32 maxToRemove = MUSCLE_NO_LIMIT) const; + + /** Returns a 32-bit checksum corresponding to this String's contents. + * Note that this method method is O(N). + */ + uint32 CalculateChecksum() const {return muscle::CalculateChecksum((const uint8 *) Cstr(), Length());} + + /** Returns true iff the given pointer points into our held character array. + * @param s A character pointer. It will not be dereferenced by this call. + */ + bool IsCharInLocalArray(const char * s) const {const char * b = Cstr(); return muscleInRange(s, b, b+_length);} + +private: + bool IsSpaceChar(char c) const {return ((c==' ')||(c=='\t')||(c=='\r')||(c=='\n'));} + status_t EnsureBufferSize(uint32 newBufLen, bool retainValue); + String ArgAux(const char * buf) const; + bool IsArrayDynamicallyAllocated() const {return (_bufferLen>sizeof(_strData._smallBuffer));} + char * GetBuffer() {return IsArrayDynamicallyAllocated() ? _strData._bigBuffer : _strData._smallBuffer;} + void ClearSmallBuffer() {memset(_strData._smallBuffer, 0, sizeof(_strData._smallBuffer));} + void WriteNULTerminatorByte() {GetBuffer()[_length] = '\0';} + + union StringUnion { // the StringUnion name is apparently necessary for muscleSwap() to work on the union + char * _bigBuffer; // Pointer to allocated array. Valid iff (_bufferLen > sizeof(_smallBuffer)) + char _smallBuffer[SMALL_MUSCLE_STRING_LENGTH+1]; // inline character array. Valid iff (_bufferLen <= sizeof(_smallBuffer)) + } _strData; + + uint32 _bufferLen; // Number of bytes pointed to by (GetBuffer()) + uint32 _length; // cached strlen(GetBuffer()) + + void VerifyIndex(uint32 index) const + { +#ifdef MUSCLE_AVOID_ASSERTIONS + (void) index; // avoid compiler warnings +#else + MASSERT(index < _length, "Index Out Of Bounds Exception"); +#endif + } +}; + +/** A custom compare functor for case-insensitive comparisons of Strings. */ +class CaseInsensitiveStringCompareFunctor +{ +public: + /** Compares the two String's strcmp() style, returning zero if they are equal, a negative value if (item1) comes first, or a positive value if (item2) comes first. */ + int Compare(const String & s1, const String & s2, void *) const {return s1.CompareToIgnoreCase(s2);} +}; + +/** A custom compare functor for numeric-aware comparisons of Strings. */ +class NumericAwareStringCompareFunctor +{ +public: + /** Compares the two String's strcmp() style, returning zero if they are equal, a negative value if (item1) comes first, or a positive value if (item2) comes first. */ + int Compare(const String & s1, const String & s2, void *) const {return s1.NumericAwareCompareTo(s2);} +}; + +/** A custom compare functor for case-insensitive numeric-aware comparisons of Strings. */ +class CaseInsensitiveNumericAwareStringCompareFunctor +{ +public: + /** Compares the two String's strcmp() style, returning zero if they are equal, a negative value if (item1) comes first, or a positive value if (item2) comes first. */ + int Compare(const String & s1, const String & s2, void *) const {return s1.NumericAwareCompareToIgnoreCase(s2);} +}; + +/** Convenience method: returns a string with no characters in it (a.k.a. "") */ +inline const String & GetEmptyString() {return GetDefaultObjectForType();} + +inline String operator+(const String & lhs, const String & rhs) {String ret; (void) ret.Prealloc(lhs.Length()+rhs.Length()); ret = lhs; ret += rhs; return ret;} +inline String operator+(const String & lhs, const char *rhs) {String ret; (void) ret.Prealloc(lhs.Length()+(rhs?(uint32)strlen(rhs):0)); ret = lhs; ret += rhs; return ret;} +inline String operator+(const char * lhs, const String & rhs) {String ret; (void) ret.Prealloc((lhs?(uint32)strlen(lhs):0)+rhs.Length()); ret = lhs; ret += rhs; return ret;} +inline String operator+(const String & lhs, char rhs) {String ret; (void) ret.Prealloc(lhs.Length()+1); ret = lhs; ret += rhs; return ret;} +inline String operator+(char lhs, const String & rhs) {String ret; (void) ret.Prealloc(1+rhs.Length()); ret.SetCstr(&lhs, 1); ret += rhs; return ret;} +inline String operator-(const String & lhs, const String & rhs) {String ret = lhs; ret -= rhs; return ret;} +inline String operator-(const String & lhs, const char *rhs) {String ret = lhs; ret -= rhs; return ret;} +inline String operator-(const char *lhs, const String & rhs) {String ret = lhs; ret -= rhs; return ret;} +inline String operator-(const String & lhs, char rhs) {String ret = lhs; ret -= rhs; return ret;} +inline String operator-(char lhs, const String & rhs) {String ret; ret.SetCstr(&lhs, 1); ret -= rhs; return ret;} + +}; // end namespace muscle + +#endif diff --git a/util/StringTokenizer.h b/util/StringTokenizer.h new file mode 100644 index 00000000..5e2d0fe4 --- /dev/null +++ b/util/StringTokenizer.h @@ -0,0 +1,123 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleStringTokenizer_h +#define MuscleStringTokenizer_h + +#include "support/MuscleSupport.h" + +namespace muscle { + +/** String tokenizer class, similar to Java's java.util.StringTokenizer. This class is used + * to interpret a specified character string as a series of sub-strings, with each sub-string + * differentiated from its neighbors by the presence of one or more of the specified separator-tokens + * between the two sub-strings. + */ +class StringTokenizer +{ +public: + /** Initializes the StringTokenizer to parse (tokenizeMe), which + * should be a string of tokens (e.g. words) separated by any + * of the characters specified in (separators) + * @param tokenizeMe the string to break up into 'words'. + * @param separators ASCII string representing a list of characters to interpret a substring-separators. + * Defaults to ", \t\r\n". + */ + StringTokenizer(const char * tokenizeMe, const char * separators = ", \t\r\n") + { + int tlen = (int) strlen(tokenizeMe); + int slen = (int) strlen(separators); + + char * temp = newnothrow_array(char, slen+1+tlen+1); + if (temp) + { + strcpy(temp, separators); + _seps = temp; + _next = temp + slen + 1; + strcpy(_next, tokenizeMe); + } + else + { + _seps = _next = NULL; + WARN_OUT_OF_MEMORY; + } + + _alloced = true; + } + + /** This Constructor is the same as above, only with this one you allow + * the StringTokenizer to modify (tokenizeMe) directly, rather + * than making a copy of the string first and modifying that. (it's a bit more efficient) + * @param junk Ignored; it's only here to disambiguate the two constructors. + * @param tokenizeMe The string to tokenize. This string will get munged! + * @param separators ASCII string representing a list of characters to interpret as word-separator characters. + * Defaults to ", \t" (where "\t" is of course the tab character) + */ + StringTokenizer(bool junk, char * tokenizeMe, const char * separators = ", \t\r\n") + { + (void) junk; + _next = tokenizeMe; + _seps = separators; + _alloced = false; + } + + /** Destructor */ + ~StringTokenizer() + { + // must cast to (char *) or VC++ complains :^P + if (_alloced) delete [] ((char *)_seps); + } + + /** Returns the next token in the parsed string, or NULL if there are no more tokens left */ + char * GetNextToken() + { + if (_seps) + { + // Move until first non-sep char + while((*_next)&&(strchr(_seps, *_next) != NULL)) _next++; + if (*_next) + { + char * ret = _next; + + // Move until next sep-char + while((*_next)&&(strchr(_seps, *_next) == NULL)) _next++; + if (*_next) + { + *_next = '\0'; + _next++; + } + return ret; + } + } + return NULL; + } + + /** Convenience synonym for GetNextToken() */ + char * operator()() {return GetNextToken();} + + /** Returns the remainder of the string, starting with the next token, + * or NULL if there are no more tokens in the string. + * Doesn't affect the next return value of GetNextToken(), though. + */ + char * GetRemainderOfString() + { + if (_seps) + { + // Move until first non-sep char + while((*_next)&&(strchr(_seps, *_next) != NULL)) _next++; + return (*_next) ? _next : NULL; // and return from there + } + return NULL; + } + +private: + StringTokenizer(const StringTokenizer &); // unimplemented on purpose + StringTokenizer & operator = (const StringTokenizer &); // unimplemented on purpose + + bool _alloced; + const char * _seps; + char * _next; +}; + +}; // end namespace muscle + +#endif diff --git a/util/TimeUtilityFunctions.h b/util/TimeUtilityFunctions.h new file mode 100644 index 00000000..094795a4 --- /dev/null +++ b/util/TimeUtilityFunctions.h @@ -0,0 +1,294 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleTimeUtilityFunctions_h +#define MuscleTimeUtilityFunctions_h + +#include + +#include "support/MuscleSupport.h" +#include "syslog/SysLog.h" // for MUSCLE_TIMEZONE_* + +#ifndef WIN32 +# include +#endif + +#if defined(__BEOS__) || defined(__HAIKU__) +# include +#endif + +#if defined(TARGET_PLATFORM_XENOMAI) +# include "native/timer.h" +#endif + +namespace muscle { + +/** @defgroup timeutilityfunctions The TimeUtilityFunctions function API + * These functions are all defined in TimeUtilityFunctions.h, and are stand-alone + * inline functions that do various time-related calculations + * @{ + */ + +/** A value that GetPulseTime() can return to indicate that Pulse() should never be called. */ +#define MUSCLE_TIME_NEVER ((uint64)-1) // (9223372036854775807LL) + +/** How many milliseconds are in one second (1000) */ +#define MILLIS_PER_SECOND ((int64)1000) + +/** How many microseconds are in one second (1000000) */ +#define MICROS_PER_SECOND ((int64)1000000) + +/** How many nanoseconds are in one second (1000000000) */ +#define NANOS_PER_SECOND ((int64)1000000000) + +/** Given a value in seconds, returns the equivalent number of nanoseconds. + * @param s A time value, in seconds. + */ +inline int64 SecondsToNanos(int64 s) {return s*NANOS_PER_SECOND;} + +/** Given a value in seconds, returns the equivalent number of microseconds. + * @param s A time value, in seconds. + */ +inline int64 SecondsToMicros(int64 s) {return s*MICROS_PER_SECOND;} + +/** Given a value in seconds, returns the equivalent number of milliseconds. + * @param s A time value, in seconds. + */ +inline int64 SecondsToMillis(int64 s) {return s*MILLIS_PER_SECOND;} + +/** Given a value in milliseconds, returns the equivalent number of nanoseconds. + * @param ms A time value, in milliseconds. + */ +inline int64 MillisToNanos(int64 ms) {return ms*(1000*1000);} + +/** Given a value in milliseconds, returns the equivalent number of nanoseconds. + * @param ms A time value, in milliseconds. + */ +inline int64 MillisToMicros(int64 ms) {return ms*1000;} + +/** Given a value in milliseconds, returns the equivalent number of nanoseconds. + * @param ms A time value, in milliseconds. + */ +inline int64 MillisToSeconds(int64 ms) {return ms/MILLIS_PER_SECOND;} + +/** Given a value in microseconds, returns the equivalent number of nanoseconds. + * @param us A time value, in microseconds. + */ +inline int64 MicrosToNanos(int64 us) {return us*1000;} + +/** Given a value in microseconds, returns the equivalent number of milliseconds. + * @param us A time value, in microseconds. + */ +inline int64 MicrosToMillis(int64 us) {return us/1000;} + +/** Given a value in microseconds, returns the equivalent number of seconds. + * @param us A time value, in microseconds. + */ +inline int64 MicrosToSeconds(int64 us) {return us/MICROS_PER_SECOND;} + +/** Given a value in nanoseconds, returns the equivalent number of microseconds. + * @param ns A time value, in nanoseconds. + */ +inline int64 NanosToMicros(int64 ns) {return ns/1000;} + +/** Given a value in nanoseconds, returns the equivalent number of milliseconds. + * @param ns A time value, in nanoseconds. + */ +inline int64 NanosToMillis(int64 ns) {return ns/(1000*1000);} + +/** Given a value in nanoseconds, returns the equivalent number of seconds. + * @param ns A time value, in nanoseconds. + */ +inline int64 NanosToSeconds(int64 ns) {return ns/NANOS_PER_SECOND;} + +/** Given a value in minutes, returns the equivalent number of microseconds. + * @param m A time value, in minutes. + */ +inline int64 MinutesToMicros(int64 m) {return SecondsToMicros(m*60);} + +/** Given a value in hours, returns the equivalent number of microseconds. + * @param h A time value, in hours. + */ +inline int64 HoursToMicros(int64 h) {return MinutesToMicros(h*60);} + +/** Given a value in days, returns the equivalent number of microseconds. + * @param d A time value, in days. + */ +inline int64 DaysToMicros(int64 d) {return HoursToMicros(d*24);} + +/** Given a value in weeks, returns the equivalent number of microseconds. + * @param w A time value, in weeks. + */ +inline int64 WeeksToMicros(int64 w) {return DaysToMicros(w*7);} + +/** Given a timeval struct, returns the equivalent uint64 value (in microseconds). + * @param tv a timeval to convert + * @return A uint64 representing the same time. + */ +inline uint64 ConvertTimeValTo64(const struct timeval & tv) {return SecondsToMicros(tv.tv_sec) + ((uint64)tv.tv_usec);} + +/** Given a uint64, writes the equivalent timeval struct into the second argument. + * @param val A uint64 time value in microseconds + * @param retStruct On return, contains the equivalent timeval struct to (val) + */ +inline void Convert64ToTimeVal(uint64 val, struct timeval & retStruct) +{ + retStruct.tv_sec = (int32)(val / MICROS_PER_SECOND); + retStruct.tv_usec = (int32)(val % MICROS_PER_SECOND); +} + +/** Convenience function: Returns true true iff (t1 < t2) + * @param t1 a time value + * @param t2 another time value + * @result true iff t1 < t2 + */ +inline bool IsLessThan(const struct timeval & t1, const struct timeval & t2) +{ + return (t1.tv_sec == t2.tv_sec) ? (t1.tv_usec < t2.tv_usec) : (t1.tv_sec < t2.tv_sec); +} + +/** Convenience function: Adds (addThis) to (addToThis) + * @param addToThis A time value. On return, this time value will have been modified. + * @param addThis Another time value, that will be added to (addToThis). + */ +inline void AddTimeVal(struct timeval & addToThis, const struct timeval & addThis) +{ + addToThis.tv_sec += addThis.tv_sec; + addToThis.tv_usec += addThis.tv_usec; + if (addToThis.tv_usec > MICROS_PER_SECOND) + { + addToThis.tv_sec += addToThis.tv_usec / MICROS_PER_SECOND; + addToThis.tv_usec = addToThis.tv_usec % MICROS_PER_SECOND; + } +} + +/** Convenience function: Subtracts (subtractThis) from (subtractFromThis) + * @param subtractFromThis A time value. On return, this time value will have been modified. + * @param subtractThis Another time value, that will be subtracted from (subtractFromThis). + */ +inline void SubtractTimeVal(struct timeval & subtractFromThis, const struct timeval & subtractThis) +{ + subtractFromThis.tv_sec -= subtractThis.tv_sec; + subtractFromThis.tv_usec -= subtractThis.tv_usec; + if (subtractFromThis.tv_usec < 0L) + { + subtractFromThis.tv_sec += (subtractFromThis.tv_usec / MICROS_PER_SECOND)-1; + while(subtractFromThis.tv_usec < 0L) subtractFromThis.tv_usec += MICROS_PER_SECOND; + } +} + +/** Returns the current real-time clock time as a uint64. The returned value is expressed + * as microseconds since the beginning of the year 1970. + * @param timeType if left as MUSCLE_TIMEZONE_UTC (the default), the value returned will be the current UTC time. + * If set to MUSCLE_TIMEZONE_LOCAL, on the other hand, the returned value will include the proper + * offset to make the time be the current time as expressed in this machine's local time zone. + * For any other value, the behaviour of this function is undefined. + * @note that the values returned by this function are NOT guaranteed to be monotonically increasing! + * Events like leap seconds, the user changing the system time, or even the OS tweaking the system + * time automatically to eliminate clock drift may cause this value to decrease occasionally! + * If you need a time value that is guaranteed to never decrease, you may want to call GetRunTime64() instead. + */ +uint64 GetCurrentTime64(uint32 timeType=MUSCLE_TIMEZONE_UTC); + +/** Returns a current time value, in microseconds, that is guaranteed never to decrease. The absolute + * values returned by this call are undefined, so you should only use it for measuring relative time + * (i.e. how much time has passed between two events). For a "wall clock" type of result with + * a well-defined time-base, you can call GetCurrentTime64() instead. + */ +#if defined(__BEOS__) || defined(__HAIKU__) +inline uint64 GetRunTime64() {return system_time();} +#elif defined(TARGET_PLATFORM_XENOMAI) +inline uint64 GetRunTime64() {return rt_timer_tsc2ns(rt_timer_tsc())/1000;} +#else +uint64 GetRunTime64(); +#endif + +/** Given a run-time value, returns the equivalent current-time value. + * @param runTime64 A run-time value, e.g. as returned by GetRunTime64(). + * @param timeType if left as MUSCLE_TIMEZONE_UTC (the default), the value returned will be the equivalent UTC time. + * If set to MUSCLE_TIMEZONE_LOCAL, on the other hand, the returned value will include the proper + * offset to make the time be the equivalent time as expressed in this machine's local time zone. + * For any other value, the behaviour of this function is undefined. + * @returns a current-time value, as might have been returned by GetCurrentTime64(timeType) at the specified run-time. + * @note The values returned by this function are only approximate, and may differ slightly from one call to the next. + * Of course they will differ significantly if the system's real-time clock value is changed by the user. + */ +inline uint64 GetCurrentTime64ForRunTime64(uint64 runTime64, uint32 timeType=MUSCLE_TIMEZONE_UTC) {return GetCurrentTime64(timeType)+(runTime64-GetRunTime64());} + +/** Given a current-time value, returns the equivalent run-time value. + * @param currentTime64 A current-time value, e.g. as returned by GetCurrentTime64(timeType). + * @param timeType if left as MUSCLE_TIMEZONE_UTC (the default), the (currentTime64) argument will be interpreted as a UTC time. + * If set to MUSCLE_TIMEZONE_LOCAL, on the other hand, the (currentTime64) argument will be interpreted as + * a current-time in this machine's local time zone. + * For any other value, the behaviour of this function is undefined. + * @returns a run-time value, as might have been returned by GetRunTime64() at the specified current-time. + * @note The values returned by this function are only approximate, and may differ slightly from one call to the next. + * Of course they will differ significantly if the system's real-time clock value is changed by the user. + */ +inline uint64 GetRunTime64ForCurrentTime64(uint64 currentTime64, uint32 timeType=MUSCLE_TIMEZONE_UTC) {return GetRunTime64()+(currentTime64-GetCurrentTime64(timeType));} + +/** Convenience function: Won't return for a given number of microsends. + * @param micros The number of microseconds to wait for. + * @return B_NO_ERROR on success, or B_ERROR on failure. + */ +#if defined(__BEOS__) || defined(__HAIKU__) +inline status_t Snooze64(uint64 microseconds) {return snooze(microseconds);} +#else +status_t Snooze64(uint64 micros); +#endif + +/** Convenience function: Returns true no more often than once every (interval). + * Useful if you are in a tight loop, but don't want e.g. more than one debug output line per second, or something like that. + * @param interval The minimum time that must elapse between two calls to this function returning true. + * @param lastTime A state variable. Pass in the same reference every time you call this function. + * @return true iff it has been at least (interval) since the last time this function returned true, else false. + */ +inline bool OnceEvery(const struct timeval & interval, struct timeval & lastTime) +{ + uint64 now64 = GetRunTime64(); + struct timeval now; + Convert64ToTimeVal(now64, now); + if (!IsLessThan(now, lastTime)) + { + lastTime = now; + AddTimeVal(lastTime, interval); + return true; + } + return false; +} + +/** Convenience function: Returns true no more than once every (interval). + * Useful if you are in a tight loop, but don't want e.g. more than one debug output line per second, or something like that. + * @param interval The minimum time that must elapse between two calls to this function returning true (in microseconds). + * @param lastTime A state variable. Pass in the same reference every time you call this function. (set to zero the first time) + * @return true iff it has been at least (interval) since the last time this function returned true, else false. + */ +inline bool OnceEvery(uint64 interval, uint64 & lastTime) +{ + uint64 now = GetRunTime64(); + if (now >= lastTime+interval) + { + lastTime = now; + return true; + } + return false; +} + +/** This handy macro will print out, twice a second, + * the average number of times per second it is being called. + */ +#define PRINT_CALLS_PER_SECOND(x) \ +{ \ + static uint32 count = 0; \ + static uint64 startTime = 0; \ + uint64 lastTime = 0; \ + uint64 now = GetCurrentTime64(); \ + if (startTime == 0) startTime = now; \ + count++; \ + if ((OnceEvery(500000, lastTime))&&(now>startTime)) printf("%s: " UINT64_FORMAT_SPEC "/s\n", x, (MICROS_PER_SECOND*((uint64)count))/(now-startTime)); \ +} + +/** @} */ // end of timeutilityfunctions doxygen group + +}; // end namespace muscle + +#endif diff --git a/vc++/README.txt b/vc++/README.txt new file mode 100644 index 00000000..127c097e --- /dev/null +++ b/vc++/README.txt @@ -0,0 +1,19 @@ +In this directory you will find Visual C++ project files created by Vitaliy + +Note that MUSCLE 5.xx and higher won't compile under VisualC++6, so you will need +Visual Studio 2008 or better (Visual Studio 2008 Express will work too). + +To compile muscle, do the following: + +1. Open the "muscle.vcproj" project file in VC++ + +2. Click Build->Build Solution + +3. Sit back and relax. The muscle build should complete in a minute or two. + +4. Open the new "Build" folder that is created inside vc++. You should see + three files there: admin.exe, muscled.exe, and muscle.lib. You can run + your server with muscled.exe, and maintain it with admin.exe. You can + build your own apps and link them against muscle.lib. + +-Vitaliy Mikitchenko (aka "VitViper") diff --git a/vc++/admin.vcproj b/vc++/admin.vcproj new file mode 100755 index 00000000..1781ea53 --- /dev/null +++ b/vc++/admin.vcproj @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vc++/muscle.sln b/vc++/muscle.sln new file mode 100755 index 00000000..a281b59a --- /dev/null +++ b/vc++/muscle.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "admin", "admin.vcproj", "{5723BD9A-B23F-4F4F-8D12-C246D8B3D788}" + ProjectSection(ProjectDependencies) = postProject + {001C4ECC-76A2-4008-BBCC-BBF252EC93A5} = {001C4ECC-76A2-4008-BBCC-BBF252EC93A5} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "muscle", "muscle.vcproj", "{001C4ECC-76A2-4008-BBCC-BBF252EC93A5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "muscled", "muscled.vcproj", "{6A82FF6A-1634-45EB-AAB4-6360EA6F19B0}" + ProjectSection(ProjectDependencies) = postProject + {001C4ECC-76A2-4008-BBCC-BBF252EC93A5} = {001C4ECC-76A2-4008-BBCC-BBF252EC93A5} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5723BD9A-B23F-4F4F-8D12-C246D8B3D788}.Release|Win32.ActiveCfg = Release|Win32 + {5723BD9A-B23F-4F4F-8D12-C246D8B3D788}.Release|Win32.Build.0 = Release|Win32 + {001C4ECC-76A2-4008-BBCC-BBF252EC93A5}.Release|Win32.ActiveCfg = Release|Win32 + {001C4ECC-76A2-4008-BBCC-BBF252EC93A5}.Release|Win32.Build.0 = Release|Win32 + {6A82FF6A-1634-45EB-AAB4-6360EA6F19B0}.Release|Win32.ActiveCfg = Release|Win32 + {6A82FF6A-1634-45EB-AAB4-6360EA6F19B0}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/vc++/muscle.vcproj b/vc++/muscle.vcproj new file mode 100755 index 00000000..ed00985a --- /dev/null +++ b/vc++/muscle.vcproj @@ -0,0 +1,1046 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vc++/muscled.vcproj b/vc++/muscled.vcproj new file mode 100755 index 00000000..e397f370 --- /dev/null +++ b/vc++/muscled.vcproj @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winsupport/Win32FileHandleDataIO.cpp b/winsupport/Win32FileHandleDataIO.cpp new file mode 100644 index 00000000..f400f928 --- /dev/null +++ b/winsupport/Win32FileHandleDataIO.cpp @@ -0,0 +1,91 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#if defined(WIN32) +#include "winsupport/Win32FileHandleDataIO.h" + +namespace muscle { + +Win32FileHandleDataIO :: +Win32FileHandleDataIO(::HANDLE handle) : _handle(handle) +{ + // empty +} + +Win32FileHandleDataIO :: +~Win32FileHandleDataIO() +{ + if (_handle != INVALID_HANDLE_VALUE) CloseHandle(_handle); +} + +int32 Win32FileHandleDataIO :: Read(void * buffer, uint32 size) +{ + if (_handle != INVALID_HANDLE_VALUE) + { + DWORD readCount=0; + return ReadFile(_handle, buffer, size, &readCount, 0) ? readCount : -1; + } + else return -1; +} + +int32 Win32FileHandleDataIO :: Write(const void * buffer, uint32 size) +{ + if (_handle != INVALID_HANDLE_VALUE) + { + DWORD writeCount; + return WriteFile(_handle, buffer, size, &writeCount, 0) ? writeCount : -1; + } + else return -1; +} + +void Win32FileHandleDataIO :: FlushOutput() +{ + // empty +} + +status_t Win32FileHandleDataIO :: SetBlockingIOEnabled(bool blocking) +{ + return ((_handle != INVALID_HANDLE_VALUE)&&(blocking==false)) ? B_NO_ERROR : B_ERROR; +} + +void Win32FileHandleDataIO :: Shutdown() +{ + if (_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(_handle); + _handle = INVALID_HANDLE_VALUE; + } +} + +status_t Win32FileHandleDataIO :: Seek(int64 offset, int whence) +{ + if (_handle != INVALID_HANDLE_VALUE) + { + switch(whence) + { + case IO_SEEK_SET: whence = FILE_BEGIN; break; + case IO_SEEK_CUR: whence = FILE_CURRENT; break; + case IO_SEEK_END: whence = FILE_END; break; + default: return B_ERROR; + } + + LARGE_INTEGER newPosition; newPosition.QuadPart = 0; + LARGE_INTEGER distanceToMove; distanceToMove.QuadPart = offset; + return SetFilePointerEx(_handle, distanceToMove, &newPosition, whence) ? B_NO_ERROR : B_ERROR; + } + return B_ERROR; +} + +int64 Win32FileHandleDataIO :: GetPosition() const +{ + if (_handle != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER newPosition; newPosition.QuadPart = 0; + LARGE_INTEGER distanceToMove; distanceToMove.QuadPart = 0; + return SetFilePointerEx(_handle, distanceToMove, &newPosition, FILE_CURRENT) ? newPosition.QuadPart : -1; + } + return -1; +} + +}; // end namespace muscle + +#endif diff --git a/winsupport/Win32FileHandleDataIO.h b/winsupport/Win32FileHandleDataIO.h new file mode 100755 index 00000000..025d4976 --- /dev/null +++ b/winsupport/Win32FileHandleDataIO.h @@ -0,0 +1,97 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleWin32FileHandleDataIO_h +#define MuscleWin32FileHandleDataIO_h + +#include "dataio/DataIO.h" + +namespace muscle { + +/** + * Data I/O to and from a Win32 style file descriptor + */ +class Win32FileHandleDataIO : public DataIO, private CountedObject +{ +public: + /** + * Constructor. + * @param handle The file descriptor to use. Becomes property of this FileHandleDataIO object. + */ + Win32FileHandleDataIO(::HANDLE handle); + + /** Destructor. + * close()'s the held file descriptor. + */ + virtual ~Win32FileHandleDataIO(); + + /** Reads bytes from the file descriptor and places them into (buffer). + * @param buffer Buffer to write the bytes into + * @param size Number of bytes in the buffer. + * @return Number of bytes read, or -1 on error. + * @see DataIO::Read() + */ + virtual int32 Read(void * buffer, uint32 size); + + /** + * Reads bytes from (buffer) and sends them out to the file descriptor. + * @param buffer Buffer to read the bytes from. + * @param size Number of bytes in the buffer. + * @return Number of bytes written, or -1 on error. + * @see DataIO::Write() + */ + virtual int32 Write(const void * buffer, uint32 size); + + /** + * Implemented as a no-op (I don't believe file descriptors need flushing?) + */ + virtual void FlushOutput(); + + /** + * Enables or diables blocking I/O on this file descriptor. + * If this object is to be used by an AbstractMessageIOGateway, + * then non-blocking I/O is usually better to use. + * NOTE: Win32 File handles currently do not use this flag. + * @param blocking If true, file descriptor is set to blocking I/O mode. Otherwise, non-blocking I/O. + * @return B_NO_ERROR on success, B_ERROR on error. + */ + status_t SetBlockingIOEnabled(bool blocking); + + virtual void Shutdown(); + + /** Seeks to the specified point in the file stream. + * @param offset Where to seek to. + * @param whence IO_SEEK_SET, IO_SEEK_CUR, or IO_SEEK_END. + * @return B_NO_ERROR on success, B_ERROR on failure. + */ + virtual status_t Seek(int64 offset, int whence); + + /** Returns our current position in the file */ + virtual int64 GetPosition() const; + + /** Win32 HANDLES are not compatible with unix-style select, so this method always returns a NULL socket ref. */ + virtual const ConstSocketRef & GetReadSelectSocket() const {return GetNullSocket();} + + /** Win32 HANDLES are not compatible with unix-style select, so this method always returns a NULL socket ref. */ + virtual const ConstSocketRef & GetWriteSelectSocket() const {return GetNullSocket();} + + /** + * Releases control of the contained file descriptor to the calling code. + * After this method returns, this object no longer owns or can + * use or close the file descriptor descriptor it once held. + */ + void ReleaseFileHandle() {_handle = INVALID_HANDLE_VALUE;} + + /** + * Returns the file descriptor held by this object, or + * INVALID_HANDLE_VALUE if there is none. + */ + ::HANDLE GetFileHandle() const {return _handle;} + +private: + ::HANDLE _handle; +}; + +}; // end namespace muscle + +#endif + diff --git a/winsupport/Win32MessageTransceiverThread.h b/winsupport/Win32MessageTransceiverThread.h new file mode 100644 index 00000000..6651bba0 --- /dev/null +++ b/winsupport/Win32MessageTransceiverThread.h @@ -0,0 +1,190 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef MuscleWin32MessageTransceiverThread_h +#define MuscleWin32MessageTransceiverThread_h + +#include "system/MessageTransceiverThread.h" + +namespace muscle { + +static const UINT WIN32MTT_SIGNAL_EVENT = WM_USER; // default signal value + +/** + * This is a Win32-API-specific subclass of MessageTransceiverThread. + * It can be useful when you are using MUSCLE in a Win32-only application. + */ +class Win32MessageTransceiverThread : public MessageTransceiverThread, private CountedObject +{ +public: + /** This constructor creates an object that will signal your thread by calling + * PostThreadMessage() with the arguments you specify here. + * @param replyThreadID The ID of the thread you wish notification signals + * to be sent to (typically GetCurrentThreadId()) + * @param signalValue Signal value to deliver to the reply thread when notifying it of an event. + * Defaults to WIN32MTT_SIGNAL_EVENT. + */ + Win32MessageTransceiverThread(DWORD replyThreadID, UINT signalValue = WIN32MTT_SIGNAL_EVENT) : _replyThreadID(replyThreadID), _signalValue(signalValue), _signalHandle(INVALID_HANDLE_VALUE), _closeHandleWhenDone(false) {/* empty */} + + /** This constructor creates an object that will signal your thread by calling + * SetEvent() on the handle that you specify here. + * @param signalHandle Handle that we will call SetEvent() on whenever we want to notify + * the owning thread of a pending event. + * @param closeHandleWhenDone If true, we will call CloseHandle() on (signalHandle) in our destructor. + * Otherwise, our destructor will leave (signalHandle) open. + */ + Win32MessageTransceiverThread(::HANDLE signalHandle, bool closeHandleWhenDone) : _replyThreadID(0), _signalValue(0), _signalHandle(signalHandle), _closeHandleWhenDone(closeHandleWhenDone) {/* empty */} + + /** + * Destructor. You will generally want to call ShutdownInternalThread() + * before destroying this object. + */ + virtual ~Win32MessageTransceiverThread() {if ((_signalHandle != INVALID_HANDLE_VALUE)&&(_closeHandleWhenDone)) CloseHandle(_signalHandle);} + + /** Returns the signal HANDLE that was passed in to our constructor, or INVALID_HANDLE_VALUE if there wasn't one. */ + ::HANDLE GetSignalHandle() const {return _signalHandle;} + + /** Used to set the signal HANDLE when the constructor call isn't appropriate. + * If set to INVALID_HANDLE_VALUE, we'll use PostThreadMessage() to signal the user thread; + * otherwise we'll call SetEvent() on this handle to do so. + * Any previously held handle will not be closed by this call; if you want it closed you'll + * need to close it manually yourself, first. + * @param signalHandle handle to call SetEvent() on from now on, or INVALID_HANDLE_VALUE if you + * wish to switch to calling SetReplyThreadID() instead. + * @param closeHandleWhenDone If true, this object's destructor will call CloseHandle() on (signalHandle). + * Otherwise, the handle will not be closed by this object, ever. + */ + void SetSignalHandle(::HANDLE signalHandle, bool closeHandleWhenDone) {_signalHandle = signalHandle; _closeHandleWhenDone = closeHandleWhenDone;} + + /** Returns true iff we plan to call CloseHandle() on our held signal handle when we are destroyed. */ + bool GetCloseHandleWhenDone() const {return _closeHandleWhenDone;} + + /** Returns the reply thread ID that was passed in to our constructor, or 0 if there wasn't one. */ + DWORD GetReplyThreadID() const {return _replyThreadID;} + + /** Used to set the reply thread ID when the constructor call isn't appropriate. + * @param replyThreadID The new thread ID to call PostThreadMessage() on to signal the user thread. + * This value is only used if the SignalHandle value is set to INVALID_HANDLE_VALUE. + */ + void SetReplyThreadID(DWORD replyThreadID) {_replyThreadID = replyThreadID;} + + /** Returns the signal value that was passed in to our constructor, or 0 if there wasn't one. */ + UINT GetSignalValue() const {return _signalValue;} + + /** Used to set the signal value when the constructor call isn't appropriate. This value is only used + * if the signal handle is set to a valid value (i.e. not INVALID_HANDLE_VALUE) + */ + void SetSignalValue(UINT signalValue) {_signalValue = signalValue;} + +protected: + /** Overridden to send a signal the specified windows thread */ + virtual void SignalOwner() + { + if (_signalHandle != INVALID_HANDLE_VALUE) SetEvent(_signalHandle); + else PostThreadMessage(_replyThreadID, _signalValue, 0, 0); + } + +private: + // method 1 -- via PostThreadMessage() + DWORD _replyThreadID; + UINT _signalValue; + + // method 2 -- via SetEvent() + ::HANDLE _signalHandle; + bool _closeHandleWhenDone; +}; + +/** Here is some example code showing how to process events from a Win32MessageTransceiverThread object + * From inside a Win32 thread, using the PostThreadMessage()/PeekMessage() method: + * + * Win32MessageTransceiverThread * mtt = new Win32MessageTransceiverThread(GetCurrentThreadId()); + * if ((mtt->AddNewConnectSession("beshare.tycomsystems.com", 2960) == B_NO_ERROR)&&(mtt->StartInternalThread() == B_NO_ERROR)) + * { + * while(1) + * { + * MSG msg; + * if (PeekMessage(&msg, 0, WIN32MTT_SIGNAL_EVENT, WIN32MTT_SIGNAL_EVENT, PM_REMOVE) != 0) + * { + * if (msg.message == WIN32MTT_SIGNAL_EVENT) + * { + * uint32 code; + * MessageRef nextMsgRef; + * + * // Check for any new messages from our internal thread + * while (myMessageTransceiverThread->GetNextEventFromInternalThread(code, &nextMsgRef) >= 0) + * { + * switch (code) + * { + * case MTT_EVENT_INCOMING_MESSAGE: + * printf("Received Message from network!\n"); + * if (nextMsgRef()) nextMsgRef()->PrintToStream(); + * break; + * + * case MTT_EVENT_SESSION_CONNECTED: + * printf("Connected to remote peer complete!\n"); + * break; + + * case MTT_EVENT_SESSION_DISCONNECTED: + * printf("Disconnected from remote peer, or connection failed!\n"); + * break; + * + * // other MTT_EVENT_* handling code could be put here + * } + * } + * } + * } + * } + * } + * mtt->ShutdownInternalThread(); + * delete mtt; + * +***/ + +/** And here is some example code showing how to process events from a Win32MessageTransceiverThread object + * From inside a Win32 thread, using the SetEvent()/WaitForMultipleObjects() method: + * + * Win32MessageTransceiverThread * mtt = new Win32MessageTransceiverThread(CreateEvent(0, false, false, 0), true); + * if ((mtt->AddNewConnectSession("beshare.tycomsystems.com", 2960) == B_NO_ERROR)&&(mtt->StartInternalThread() == B_NO_ERROR)) + * { + * while(1) + * { + * ::HANDLE events[] = {mtt->GetSignalHandle()}; // other things to wait on can be added here too if you want + * switch(WaitForMultipleObjects(ARRAYITEMS(events), events, false, INFINITE)-WAIT_OBJECT_0) + * { + * case 0: + * { + * // wakeupSignal signalled!! Check for any new messages from our internal thread + * uint32 code; + * MessageRef nextMsgRef; + * while (myMessageTransceiverThread->GetNextEventFromInternalThread(code, &nextMsgRef) >= 0) + * { + * switch (code) + * { + * case MTT_EVENT_INCOMING_MESSAGE: + * printf("Received Message from network!\n"); + * if (nextMsgRef()) nextMsgRef()->PrintToStream(); + * break; + * + * case MTT_EVENT_SESSION_CONNECTED: + * printf("Connected to remote peer complete!\n"); + * break; + + * case MTT_EVENT_SESSION_DISCONNECTED: + * printf("Disconnected from remote peer, or connection failed!\n"); + * break; + * + * // other MTT_EVENT_* handling code could be put here + * } + * } + * break; + * } + * } + * } + * } + * mtt->ShutdownInternalThread(); + * delete mtt; + * +***/ + +}; // end namespace muscle + +#endif diff --git a/zlib/TarFileWriter.cpp b/zlib/TarFileWriter.cpp new file mode 100644 index 00000000..b5cc423f --- /dev/null +++ b/zlib/TarFileWriter.cpp @@ -0,0 +1,147 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#include "dataio/FileDataIO.h" +#include "util/MiscUtilityFunctions.h" +#include "zlib/TarFileWriter.h" + +namespace muscle { + +TarFileWriter :: TarFileWriter() : _currentHeaderOffset(-1) +{ + // empty +} + +TarFileWriter :: TarFileWriter(const char * outputFileName, bool append) : _currentHeaderOffset(-1) +{ + (void) SetFile(outputFileName, append); +} + +TarFileWriter :: TarFileWriter(const DataIORef & dio) : _writerIO(dio), _currentHeaderOffset(-1) +{ + // empty +} + +TarFileWriter :: ~TarFileWriter() +{ + (void) Close(); +} + +status_t TarFileWriter :: Close() +{ + status_t ret = _writerIO() ? FinishCurrentFileDataBlock() : B_NO_ERROR; + _writerIO.Reset(); // do this no matter what + return ret; +} + +void TarFileWriter :: SetFile(const DataIORef & dio) +{ + (void) Close(); + _writerIO = dio; +} + +status_t TarFileWriter :: SetFile(const char * outputFileName, bool append) +{ + (void) Close(); + _writerIO.Reset(); + + if (outputFileName) + { + if (append == false) (void) DeleteFile(outputFileName); + + FILE * fpOut = fopen(outputFileName, append?"ab":"wb"); + if (fpOut) + { + _writerIO.SetRef(newnothrow FileDataIO(fpOut)); + if (_writerIO()) return B_NO_ERROR; + else {fclose(fpOut); WARN_OUT_OF_MEMORY;} + } + return B_ERROR; + } + else return B_NO_ERROR; +} + +static void WriteOctalASCII(uint8 * b, uint64 val, uint8 fieldSize) +{ + // gotta pad out the file data to the nearest block boundary! + char formatStr[16]; strcpy(formatStr, UINT64_FORMAT_SPEC" "); + + char * pi = strchr(formatStr, 'u'); + if (pi) *pi = 'o'; // gotta use octal here! + + char tmp[256]; + sprintf(tmp, formatStr, val); + int numChars = muscleMin((int)fieldSize, ((int)(strlen(tmp)+1))); // include the NUL byte if possible + uint8 * dStart = (b+fieldSize)-numChars; + memcpy(dStart, tmp, numChars); + memset(b, '0', dStart-b); // initial zeros +} + +status_t TarFileWriter :: FinishCurrentFileDataBlock() +{ + if (_writerIO() == NULL) return B_ERROR; + + if (_currentHeaderOffset >= 0) + { + int64 currentPos = GetCurrentSeekPosition(); + uint64 currentFileLength = currentPos-(_currentHeaderOffset+TAR_BLOCK_SIZE); + int64 extraBytes = (currentPos%TAR_BLOCK_SIZE); + if (extraBytes != 0) + { + int64 numPadBytes = (TAR_BLOCK_SIZE-extraBytes); + uint8 zeros[TAR_BLOCK_SIZE]; memset(zeros, 0, numPadBytes); + if (_writerIO()->WriteFully(zeros, numPadBytes) != numPadBytes) return B_ERROR; + } + + WriteOctalASCII(&_currentHeaderBytes[124], currentFileLength, 12); + + uint32 checksum = 0; + for (uint32 i=0; iSeek(_currentHeaderOffset, DataIO::IO_SEEK_SET) != B_NO_ERROR)||(_writerIO()->WriteFully(_currentHeaderBytes, sizeof(_currentHeaderBytes)) != sizeof(_currentHeaderBytes))||(_writerIO()->Seek(0, DataIO::IO_SEEK_END) != B_NO_ERROR)) return B_ERROR; + + _currentHeaderOffset = -1; + } + return B_NO_ERROR; +} + +int64 TarFileWriter :: GetCurrentSeekPosition() const +{ + return _writerIO() ? _writerIO()->GetPosition() : -1; +} + +status_t TarFileWriter :: WriteFileHeader(const char * fileName, uint32 fileMode, uint32 ownerID, uint32 groupID, uint64 modificationTime, int linkIndicator, const char * linkedFileName) +{ + if ((strlen(fileName) > 100)||((linkedFileName)&&(strlen(linkedFileName)>100))) return B_ERROR; // string fields are only 100 chars long! + if (FinishCurrentFileDataBlock() != B_NO_ERROR) return B_ERROR; // should pad out position out to a multiple of 512, if necessary + + int64 curSeekPos = GetCurrentSeekPosition(); + if ((curSeekPos < 0)||((curSeekPos%TAR_BLOCK_SIZE) != 0)) return B_ERROR; + + _currentHeaderOffset = curSeekPos; + memset(_currentHeaderBytes, 0, sizeof(_currentHeaderBytes)); + strcpy((char *)(&_currentHeaderBytes[0]), fileName); + + WriteOctalASCII(&_currentHeaderBytes[100], fileMode, 8); + WriteOctalASCII(&_currentHeaderBytes[108], ownerID, 8); + WriteOctalASCII(&_currentHeaderBytes[116], groupID, 8); + + uint64 secondsSince1970 = MicrosToSeconds(modificationTime); + if (secondsSince1970 != 0) WriteOctalASCII(&_currentHeaderBytes[136], secondsSince1970, 12); + + memset(&_currentHeaderBytes[148], ' ', 8); // these spaces are used later on, when calculating the header checksum + _currentHeaderBytes[156] = linkIndicator+'0'; + if (linkedFileName) strcpy((char *)(&_currentHeaderBytes[157]), linkedFileName); + + // We write out the header as it is now, in order to keep the file offsets correct... but we'll rewrite it again later + // when we know the actual file size. + return (_writerIO()->WriteFully(_currentHeaderBytes, sizeof(_currentHeaderBytes)) == sizeof(_currentHeaderBytes)) ? B_NO_ERROR : B_ERROR; +} + +status_t TarFileWriter :: WriteFileData(const uint8 * fileData, uint32 numBytes) +{ + if ((_writerIO() == NULL)||(_currentHeaderOffset < 0)) return B_ERROR; + return (_writerIO()->WriteFully(fileData, numBytes) == numBytes) ? B_NO_ERROR : B_ERROR; +} + +}; // end namespace muscle diff --git a/zlib/TarFileWriter.h b/zlib/TarFileWriter.h new file mode 100644 index 00000000..5df30d4b --- /dev/null +++ b/zlib/TarFileWriter.h @@ -0,0 +1,118 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef TarFileUtilityFunctions_h +#define TarFileUtilityFunctions_h + +#include "dataio/DataIO.h" + +namespace muscle { + +/** @defgroup tarfileutilityfunctions The TarFileUtilityFunctions function API + * These functions deal with .tar files in a cross-platform-compatible manner. + * (I use these instead of libtar, because libtar doesn't work under Windows, + * and it doesn't support streaming data into a tar file easily). Currently + * only the writing of a .tar file is supported; at some point I may add + * support for reading .tar files as well. + * @{ + */ + +class TarFileWriter +{ +public: + /** Default constructor. */ + TarFileWriter(); + + /** Constructor. + * @param outputFileName Name/path of the .tar file to write to + * @param append If true, written data will be appended to this file; otherwise + * if the file already exists it will be deleted and replaced. + * This constructor is equivalent to the default constructor, plus calling SetFile(). + */ + TarFileWriter(const char * outputFileName, bool append); + + /** Constructor. + * @param dio Reference to a DataIO to use to write data out. + * This constructor is equivalent to the default constructor, plus calling SetFile(). + */ + TarFileWriter(const DataIORef & dio); + + /** Destructor. */ + ~TarFileWriter(); + + /** Closes the current file (if any is open) and returns to the just-default-constructed state. + * @returns B_NO_ERROR on success, or B_ERROR if there was an error writing out header data. + * Note that the file will always be closed, even if we return B_ERROR. + */ + status_t Close(); + + /** Closes the currently open file (if any) and attempts to open the new one. + * @param outputFileName Name/path of the .tar file to write to + * @param append If true, new written data will be appended to the existing file; otherwise + * if the file already exists it will be deleted and replaced. + */ + status_t SetFile(const char * outputFileName, bool append); + + /** Closes the currently open file (if any) and uses the specified DataIO instead. + * @param dioRef Reference to The DataIORef to use to output .tar data to, or a NULL Ref to close only. + */ + void SetFile(const DataIORef & dioRef); + + /** Writes a .tar header fle-block with the given information. + * @param fileName The name of the member file as it should be recorded inside the .tar file. + * @param fileMode The file-mode bits that should be stored with this file. + * @param ownerID The file's owner's numeric user ID + * @param groupID The file's group's numeric user ID + * @param modificationTime A timestamp indicating the file's last modification time (microseconds since 1970) + * @param linkIndicator one of the TAR_LINK_INDICATOR_* values + * @param linkedFileName Name of the linked file (if any); + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t WriteFileHeader(const char * fileName, uint32 fileMode, uint32 ownerID, uint32 groupID, uint64 modificationTime, int linkIndicator, const char * linkedFileName); + + /** Writes (numBytes) of data into the current file block. + * Three must be a file-header currently active for this call to succeed. + * @param fileData Pointer to some data bytes to write into the tar file. + * @param numBytes How many bytes (fileData) points to. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ + status_t WriteFileData(const uint8 * fileData, uint32 numBytes); + + /** Updates the current file-header-block and resets our state to receive the next one. + * Note that Close() and WriteFileHeader() will call FinishCurrentFileDataBlock() if necessary, so calling this method isn't strictly necessary. + * @returns B_NO_ERROR on success (or if no header-block was open), or B_ERROR if there was an error updating the header block. + */ + status_t FinishCurrentFileDataBlock(); + + enum { + TAR_LINK_INDICATOR_NORMAL_FILE = 0, + TAR_LINK_INDICATOR_HARD_FILE, + TAR_LINK_INDICATOR_SYMBOLIC_LINK, + TAR_LINK_INDICATOR_CHARACTER_SPECIAL, + TAR_LINK_INDICATOR_BLOCK_SPECIAL, + TAR_LINK_INDICATOR_DIRECTORY, + TAR_LINK_INDICATOR_FIFO, + TAR_LINK_INDICATOR_CONTIGUOUS_FILE, + NUM_TAR_LINK_INDICATORS + }; + + /** Returns true iff we successfully opened the .tar output file. */ + bool IsFileOpen() const {return (_writerIO() != NULL);} + + /** Returns true iff we successfully started a .tar record block and it is currently open. */ + bool IsFileDataBlockOpen() const {return (_currentHeaderOffset >= 0);} + +private: + enum {TAR_BLOCK_SIZE=512}; + + int64 GetCurrentSeekPosition() const; + + DataIORef _writerIO; + int64 _currentHeaderOffset; + uint8 _currentHeaderBytes[TAR_BLOCK_SIZE]; +}; + +/** @} */ // end of tarfileutilityfunctions doxygen group + +}; // end namespace muscle + +#endif diff --git a/zlib/ZLibCodec.cpp b/zlib/ZLibCodec.cpp new file mode 100644 index 00000000..56fc83fc --- /dev/null +++ b/zlib/ZLibCodec.cpp @@ -0,0 +1,164 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +#include "zlib/ZLibCodec.h" +#include "system/GlobalMemoryAllocator.h" + +namespace muscle { + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING +static void * muscleZLibAlloc(void *, uInt items, uInt size) {using namespace muscle; return muscleAlloc(items*size);} +static void muscleZLibFree(void *, void * address) {using namespace muscle; muscleFree(address);} +# define MUSCLE_ZLIB_ALLOC muscleZLibAlloc +# define MUSCLE_ZLIB_FREE muscleZLibFree +#else +# define MUSCLE_ZLIB_ALLOC Z_NULL +# define MUSCLE_ZLIB_FREE Z_NULL +#endif + +ZLibCodec :: ZLibCodec(int compressionLevel) : _compressionLevel(muscleClamp(compressionLevel, 0, 9)) +{ + TCHECKPOINT; + + InitStream(_inflater); + _inflateOkay = (inflateInit(&_inflater) == Z_OK); + + InitStream(_deflater); + _deflateOkay = (deflateInit(&_deflater, _compressionLevel) == Z_OK); +} + +ZLibCodec :: ~ZLibCodec() +{ + TCHECKPOINT; + + if (_inflateOkay) + { + inflateEnd(&_inflater); + _inflateOkay = false; + } + if (_deflateOkay) + { + deflateEnd(&_deflater); + _deflateOkay = false; + } +} + +void ZLibCodec :: InitStream(z_stream & stream) +{ + stream.next_in = Z_NULL; + stream.avail_in = 0; + stream.total_in = 0; + + stream.next_out = Z_NULL; + stream.avail_out = 0; + stream.total_out = 0; + + stream.zalloc = MUSCLE_ZLIB_ALLOC; + stream.zfree = MUSCLE_ZLIB_FREE; + stream.opaque = Z_NULL; +} + +static const uint32 ZLIB_CODEC_HEADER_DEPENDENT = 2053925218; // 'zlib' +static const uint32 ZLIB_CODEC_HEADER_INDEPENDENT = 2053925219; // 'zlic' +static const uint32 ZLIB_CODEC_HEADER_SIZE = sizeof(uint32)+sizeof(uint32); // 4 bytes of magic, 4 bytes of raw-size + +ByteBufferRef ZLibCodec :: Deflate(const uint8 * rawBytes, uint32 numRaw, bool independent, uint32 addHeaderBytes, uint32 addFooterBytes) +{ + TCHECKPOINT; + + ByteBufferRef ret; + if ((rawBytes)&&(_deflateOkay)) + { + if ((independent)&&(deflateReset(&_deflater) != Z_OK)) + { + _deflateOkay = false; + return ByteBufferRef(); + } + + uint32 compAvailSize = ZLIB_CODEC_HEADER_SIZE+deflateBound(&_deflater, numRaw)+13; + ret = GetByteBufferFromPool(addHeaderBytes+compAvailSize+addFooterBytes); + if (ret()) + { + _deflater.next_in = (Bytef *)rawBytes; + _deflater.total_in = 0; + _deflater.avail_in = numRaw; + + _deflater.next_out = ret()->GetBuffer()+ZLIB_CODEC_HEADER_SIZE+addHeaderBytes; + _deflater.total_out = 0; + _deflater.avail_out = compAvailSize; // doesn't include the users add-header or add-footer bytes! + + if ((deflate(&_deflater, Z_SYNC_FLUSH) == Z_OK)&&(ret()->SetNumBytes(addHeaderBytes+ZLIB_CODEC_HEADER_SIZE+_deflater.total_out+addFooterBytes, true) == B_NO_ERROR)) + { + (void) ret()->FreeExtraBytes(); // no sense keeping all that extra space around, is there? + + uint8 * compBytes = ret()->GetBuffer()+addHeaderBytes; // important -- it might have changed! + + const uint32 magic = B_HOST_TO_LENDIAN_INT32(independent ? ZLIB_CODEC_HEADER_INDEPENDENT : ZLIB_CODEC_HEADER_DEPENDENT); + muscleCopyOut(compBytes, magic); + + const uint32 rawLen = B_HOST_TO_LENDIAN_INT32(numRaw); + muscleCopyOut(&compBytes[sizeof(magic)], rawLen); +//printf("Deflated " UINT32_FORMAT_SPEC" bytes to " UINT32_FORMAT_SPEC" bytes\n", numRaw, ret()->GetNumBytes()); + } + else ret.Reset(); // oops, something went wrong! + } + } + return ret; +} + +int32 ZLibCodec :: GetInflatedSize(const uint8 * compBytes, uint32 numComp, bool * optRetIsIndependent) const +{ + TCHECKPOINT; + + if ((compBytes)&&(numComp >= ZLIB_CODEC_HEADER_SIZE)) + { + uint32 magic; muscleCopyIn(magic, compBytes); magic = B_LENDIAN_TO_HOST_INT32(magic); + if ((magic == ZLIB_CODEC_HEADER_INDEPENDENT)||(magic == ZLIB_CODEC_HEADER_DEPENDENT)) + { + if (optRetIsIndependent) *optRetIsIndependent = (magic == ZLIB_CODEC_HEADER_INDEPENDENT); + uint32 rawLen; muscleCopyIn(rawLen, compBytes+sizeof(magic)); + return B_LENDIAN_TO_HOST_INT32(rawLen); + } + } + return -1; +} + +ByteBufferRef ZLibCodec :: Inflate(const uint8 * compBytes, uint32 numComp) +{ + TCHECKPOINT; + + ByteBufferRef ret; + + bool independent; + int32 rawLen = GetInflatedSize(compBytes, numComp, &independent); + if ((rawLen >= 0)&&(_inflateOkay)) + { + if ((independent)&&(inflateReset(&_inflater) != Z_OK)) + { + _inflateOkay = false; + return ByteBufferRef(); + } + + ret = GetByteBufferFromPool(rawLen); + if (ret()) + { + _inflater.next_in = (Bytef *) (compBytes+ZLIB_CODEC_HEADER_SIZE); + _inflater.total_in = 0; + _inflater.avail_in = numComp-ZLIB_CODEC_HEADER_SIZE; + + _inflater.next_out = ret()->GetBuffer(); + _inflater.total_out = 0; + _inflater.avail_out = ret()->GetNumBytes(); + + int zRet = inflate(&_inflater, Z_SYNC_FLUSH); + if (((zRet != Z_OK)&&(zRet != Z_STREAM_END))||((int32)_inflater.total_out != rawLen)) ret.Reset(); // oopsie! +//printf("Inflated " UINT32_FORMAT_SPEC" bytes to " UINT32_FORMAT_SPEC" bytes\n", numComp, rawLen); + } + } + return ret; +} + +}; // end namespace muscle + +#endif diff --git a/zlib/ZLibCodec.h b/zlib/ZLibCodec.h new file mode 100644 index 00000000..3ab6c73e --- /dev/null +++ b/zlib/ZLibCodec.h @@ -0,0 +1,97 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef ZLibCodec_h +#define ZLibCodec_h + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +# include "util/ByteBuffer.h" +# include "zlib/zlib/zlib.h" + +namespace muscle { + +/** This class is a handy wrapper around the zlib C functions. + * It quickly and easily inflates and deflates data to/from independently compressed chunks. + */ +class ZLibCodec +{ +public: + /** Constructor. + * @param compressionLevel how much to compress outgoing data. 0 is no + * compression, 9 is maximum compression. Default is 6. + */ + ZLibCodec(int compressionLevel = 6); + + /** Destructor */ + ~ZLibCodec(); + + /** Given a buffer of raw data, returns a reference to a Buffer containing + * the matching compressed data. + * @param rawData The raw data to compress + * @param numBytes The number of bytes (rawData) points to + * @param independent If true, the generated buffer will be decompressible on its + * own, not depending on any previously decompressed data. + * If false, the generated buffer will only be uncompressable + * if the previously Deflate()'d buffers have been reinflated + * before it. Setting this value to true will reduce the + * compression efficiency, but allows for more flexibility. + * @param addHeaderBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the beginning of the byte array, + * before the first compressed-data byte. The values in these bytes + * are undefined; the caller can write header data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @param addFooterBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the end of the byte array, + * after the last compressed-data byte. The values in these bytes + * are undefined; the caller can write footer data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @returns Reference to a buffer of compressed data on success, or a NULL reference on failure. + */ + ByteBufferRef Deflate(const uint8 * rawData, uint32 numBytes, bool independent, uint32 addHeaderBytes=0, uint32 addFooterBytes=0); + + /** Given a buffer of compressed data, returns a reference to a Buffer containing + * the matching raw data, or NULL on failure. + * @param compressedData The compressed data to expand. This should be data that was previously produced by the Deflate() method. + * @param numBytes The number of bytes (compressedData) points to + * @returns Reference to a buffer of decompressed data on success, or a NULL reference on failure. + */ + ByteBufferRef Inflate(const uint8 * compressedData, uint32 numBytes); + + /** Given a ByteBuffer that was previously produced by Deflate(), returns the number of bytes + * of raw data that the buffer represents, or -1 if the buffer isn't recognized as valid. + * @param compressedData Pointer to data that was previously created by ZLibCodec::Deflate(). + * @param numBytes The number of bytes (compressedData) points to + * @param optRetIsIndependent If non-NULL, the bool that this argument points to will have + * the independent/non-independent state of this buffer written into it. + * See Deflate()'s documentation for details. + */ + int32 GetInflatedSize(const uint8 * compressedData, uint32 numBytes, bool * optRetIsIndependent = NULL) const; + + /** Returns this codec's compression level, as was specified in the constructor. + * Note that this value only affects what we compress to -- we can compress any compression + * level, although the compression level cannot change from one Inflate() call to another. + */ + int GetCompressionLevel() const {return _compressionLevel;} + + // Convenience methods -- they work the same as their counterparts above, but take a ByteBuffer argument rather than a pointer and byte count. + ByteBufferRef Deflate(const ByteBuffer & rawData, bool independent, uint32 addHeaderBytes=0, uint32 addFooterBytes=0) {return Deflate(rawData.GetBuffer(), rawData.GetNumBytes(), independent, addHeaderBytes, addFooterBytes);} + ByteBufferRef Inflate(const ByteBuffer & compressedData) {return Inflate(compressedData.GetBuffer(), compressedData.GetNumBytes());} + int32 GetInflatedSize(const ByteBuffer & compressedData, bool * optRetIsIndependent = NULL) const {return GetInflatedSize(compressedData.GetBuffer(), compressedData.GetNumBytes(), optRetIsIndependent);} + +private: + void InitStream(z_stream & stream); + + int _compressionLevel; + + bool _inflateOkay; + z_stream _inflater; + + bool _deflateOkay; + z_stream _deflater; +}; + +}; // end namespace muscle + +#endif + +#endif diff --git a/zlib/ZLibDataIO.cpp b/zlib/ZLibDataIO.cpp new file mode 100644 index 00000000..200454d9 --- /dev/null +++ b/zlib/ZLibDataIO.cpp @@ -0,0 +1,253 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +#include "zlib/ZLibDataIO.h" +#include "system/GlobalMemoryAllocator.h" + +namespace muscle { + +#ifdef MUSCLE_ENABLE_MEMORY_TRACKING +static void * muscleZLibAlloc(void *, uInt items, uInt size) {return muscleAlloc(items*size);} +static void muscleZLibFree(void *, void * address) {muscleFree(address);} +# define MUSCLE_ZLIB_ALLOC muscleZLibAlloc +# define MUSCLE_ZLIB_FREE muscleZLibFree +#else +# define MUSCLE_ZLIB_ALLOC Z_NULL +# define MUSCLE_ZLIB_FREE Z_NULL +#endif + +ZLibDataIO :: ZLibDataIO(int compressionLevel) : _compressionLevel(compressionLevel) +{ + Init(); + SetSlaveIO(DataIORef()); +} + +ZLibDataIO :: ZLibDataIO(const DataIORef & slaveIO, int compressionLevel) : _compressionLevel(compressionLevel) +{ + Init(); + SetSlaveIO(slaveIO); +} + +ZLibDataIO :: ~ZLibDataIO() +{ + CleanupZLib(); +} + +void ZLibDataIO :: CleanupZLib() +{ + if (_inflateAllocated) + { + inflateEnd(&_readInflater); + _inflateAllocated = _inflateOkay = false; + } + if (_deflateAllocated) + { + deflateEnd(&_writeDeflater); + _deflateAllocated = false; + } +} + +void ZLibDataIO :: InitStream(z_stream & stream, uint8 * toStreamBuf, uint8 * fromStreamBuf, uint32 bufSize) +{ + stream.next_in = toStreamBuf; + stream.avail_in = 0; + stream.total_in = 0; + + stream.next_out = fromStreamBuf; + stream.avail_out = bufSize; + stream.total_out = 0; + + stream.zalloc = MUSCLE_ZLIB_ALLOC; + stream.zfree = MUSCLE_ZLIB_FREE; + stream.opaque = Z_NULL; +} + +// Note: assumes nothing is allocated! +void ZLibDataIO :: Init() +{ + InitStream(_readInflater, _toInflateBuf, _inflatedBuf, sizeof(_inflatedBuf)); + _inflateAllocated = _inflateOkay = _inputStreamOkay = false; + _sendToUser = _inflatedBuf; + + InitStream(_writeDeflater, _toDeflateBuf, _deflatedBuf, sizeof(_deflatedBuf)); + _deflateAllocated = false; + _sendToSlave = _deflatedBuf; +} + +void ZLibDataIO :: SetSlaveIO(const DataIORef & dio) +{ + CleanupZLib(); + Init(); + + _slaveIO = dio; + _inputStreamOkay = (_slaveIO() != NULL); + _inflateAllocated = _inflateOkay = ((_inputStreamOkay)&&(inflateInit(&_readInflater) == Z_OK)); + _deflateAllocated = (deflateInit(&_writeDeflater, _compressionLevel) == Z_OK); +} + +#define ZLIB_READ_COPY_TO_USER \ + if (_readInflater.next_out > _sendToUser) \ + { \ + uint32 bytesToCopy = muscleMin(size, (uint32)(_readInflater.next_out-_sendToUser)); \ + memcpy(buffer, _sendToUser, bytesToCopy); \ + uint8 * buf8 = (uint8 *) buffer; \ + buffer = buf8+bytesToCopy; \ + bytesAdded += bytesToCopy; \ + size -= bytesToCopy; \ + _sendToUser += bytesToCopy; \ + if (_sendToUser == _readInflater.next_out) \ + { \ + _sendToUser = _readInflater.next_out = _inflatedBuf; \ + _readInflater.avail_out = sizeof(_inflatedBuf); \ + } \ + } + +#define ZLIB_READ_INFLATE \ + if (_inflateOkay) \ + { \ + int zRet = inflate(&_readInflater, Z_NO_FLUSH); \ + if ((zRet != Z_OK)&&(zRet != Z_BUF_ERROR)) _inflateOkay = false; \ + ZLIB_READ_COPY_TO_USER; \ + } + +int32 ZLibDataIO :: Read(void * buffer, uint32 size) +{ + int32 bytesAdded = 0; + if (_slaveIO()) + { + ZLIB_READ_COPY_TO_USER; // First, hand any pre-inflated bytes over to the user + ZLIB_READ_INFLATE; // Then try to inflate some more bytes + + // Lastly, try to read and inflate some more bytes from our stream + if (_inputStreamOkay) + { + if (_readInflater.avail_in == 0) _readInflater.next_in = _toInflateBuf; + int32 bytesRead = _slaveIO()->Read(_readInflater.next_in, (_toInflateBuf+sizeof(_toInflateBuf))-_readInflater.next_in); + if (bytesRead >= 0) + { + _readInflater.avail_in += bytesRead; + ZLIB_READ_INFLATE; + } + else _inputStreamOkay = false; + } + } + return (bytesAdded > 0) ? bytesAdded : (((_inputStreamOkay)&&(_inflateOkay)) ? 0 : -1); +} + +int32 ZLibDataIO :: Write(const void * buffer, uint32 size) +{ + return WriteAux(buffer, size, false); +} + +#define ZLIB_WRITE_SEND_TO_SLAVE \ + if (_writeDeflater.next_out > _sendToSlave) \ + { \ + int32 bytesWritten = _slaveIO()->Write(_sendToSlave, _writeDeflater.next_out-_sendToSlave); \ + if (bytesWritten >= 0) \ + { \ + _sendToSlave += bytesWritten; \ + if (_sendToSlave == _writeDeflater.next_out) \ + { \ + _sendToSlave = _writeDeflater.next_out = _deflatedBuf; \ + _writeDeflater.avail_out = sizeof(_deflatedBuf); \ + } \ + } \ + else return -1; \ + } + +int32 ZLibDataIO :: WriteAux(const void * buffer, uint32 size, bool flushAtEnd) +{ + if ((_slaveIO())&&(_deflateAllocated)) + { + int32 bytesCompressed = 0; + + ZLIB_WRITE_SEND_TO_SLAVE; + if (_sendToSlave == _deflatedBuf) + { + if (_writeDeflater.avail_in == 0) _writeDeflater.next_in = _toDeflateBuf; + if (buffer) + { + uint32 bytesToCopy = muscleMin((uint32)((_toDeflateBuf+sizeof(_toDeflateBuf))-_writeDeflater.next_in), size); + memcpy(_writeDeflater.next_in, buffer, bytesToCopy); + bytesCompressed += bytesToCopy; +#ifdef REMOVED_TO_SUPPRESS_CLANG_STATIC_ANALYZER_WARNING_BUT_REENABLE_THIS_IF_THERES_ANOTHER_STEP_ADDED_IN_THE_FUTURE + uint8 * buf8 = (uint8 *) buffer; + buffer = buf8+bytesToCopy; + size -= bytesToCopy; +#endif + _writeDeflater.avail_in += bytesToCopy; + } + + int zRet = deflate(&_writeDeflater, ((flushAtEnd)&&(_writeDeflater.avail_in == 0)) ? Z_SYNC_FLUSH : Z_NO_FLUSH); + if ((zRet != Z_OK)&&(zRet != Z_BUF_ERROR)) return -1; + ZLIB_WRITE_SEND_TO_SLAVE; + } + return bytesCompressed; + } + return -1; +} + +status_t ZLibDataIO :: Seek(int64 offset, int whence) +{ + return _slaveIO() ? _slaveIO()->Seek(offset, whence) : B_ERROR; +} + +int64 ZLibDataIO :: GetPosition() const +{ + return _slaveIO() ? _slaveIO()->GetPosition() : -1; +} + +uint64 ZLibDataIO :: GetOutputStallLimit() const +{ + return _slaveIO() ? _slaveIO()->GetOutputStallLimit() : MUSCLE_TIME_NEVER; +} + +void ZLibDataIO :: FlushOutput() +{ + if (_slaveIO()) + { + for (uint32 i=0; i<3; i++) WriteAux(NULL, 0, true); // try to flush any/all buffered data out first... + _slaveIO()->FlushOutput(); + } +} + +void ZLibDataIO :: Shutdown() +{ + if (_slaveIO()) + { + FlushOutput(); + _slaveIO()->Shutdown(); + CleanupZLib(); + _inputStreamOkay = false; + } +} + +const ConstSocketRef & ZLibDataIO :: GetReadSelectSocket() const +{ + return (_slaveIO()) ? _slaveIO()->GetReadSelectSocket() : GetNullSocket(); +} + +const ConstSocketRef & ZLibDataIO :: GetWriteSelectSocket() const +{ + return (_slaveIO()) ? _slaveIO()->GetWriteSelectSocket() : GetNullSocket(); +} + +status_t ZLibDataIO :: GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const +{ + return (_slaveIO()) ? _slaveIO()->GetReadByteTimeStamp(whichByte, retStamp) : B_ERROR; +} + +bool ZLibDataIO :: HasBufferedOutput() const +{ + return ((_sendToSlave < _writeDeflater.next_out)||(_writeDeflater.avail_in > 0)); +} + +void ZLibDataIO :: WriteBufferedOutput() +{ + (void) WriteAux(NULL, 0, false); +} + +}; // end namespace muscle + +#endif diff --git a/zlib/ZLibDataIO.h b/zlib/ZLibDataIO.h new file mode 100644 index 00000000..1a722d3b --- /dev/null +++ b/zlib/ZLibDataIO.h @@ -0,0 +1,86 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef ZLibDataIO_h +#define ZLibDataIO_h + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +# include "zlib/zlib/zlib.h" +# include "dataio/DataIO.h" + +namespace muscle { + +/** This class wraps around another DataIO and transparently compresses all + * data going to that DataIO, and decompresses all data coming from that + * dataIO. + */ +class ZLibDataIO : public DataIO, private CountedObject +{ +public: + /** Default Constructor -- Be sure to call SetDataIO() before use. + * @param compressionLevel how much to compress outgoing data. 0 is no + * compression 9 is maximum compression. Default is 6. + */ + ZLibDataIO(int compressionLevel = 6); + + /** Constructor + * @param slaveIO Reference to the DataIO object to pass compressed data to/from. + * @param compressionLevel how much to compress outgoing data. 0 is no + * compression 9 is maximum compression. Default is 6. + */ + ZLibDataIO(const DataIORef & slaveIO, int compressionLevel = 6); + + /** Destructor */ + virtual ~ZLibDataIO(); + + virtual int32 Read(void * buffer, uint32 size); + virtual int32 Write(const void * buffer, uint32 size); + virtual status_t Seek(int64 offset, int whence); + virtual int64 GetPosition() const; + virtual uint64 GetOutputStallLimit() const; + virtual void FlushOutput(); + virtual void Shutdown(); + virtual const ConstSocketRef & GetReadSelectSocket() const; + virtual const ConstSocketRef & GetWriteSelectSocket() const; + + virtual status_t GetReadByteTimeStamp(int32 whichByte, uint64 & retStamp) const; + virtual bool HasBufferedOutput() const; + virtual void WriteBufferedOutput(); + + /** Returns the reference to the slave DataIO that was previously set in our constructor + * or via SetSlaveIO(). + */ + DataIORef GetSlaveIO() const {return _slaveIO;} + + /** Sets our slave DataIO object, and resets our state. */ + void SetSlaveIO(const DataIORef & ref); + +private: + void Init(); + void InitStream(z_stream & stream, uint8 * toStreamBuf, uint8 * fromStreamBuf, uint32 outBufSize); + int32 WriteAux(const void * buffer, uint32 size, bool flushAtEnd); + void CleanupZLib(); + + DataIORef _slaveIO; + int _compressionLevel; + + uint8 _toInflateBuf[2048]; + uint8 _inflatedBuf[2048]; + const uint8 * _sendToUser; + bool _inflateAllocated; + bool _inflateOkay; + bool _inputStreamOkay; + z_stream _readInflater; + + uint8 _toDeflateBuf[2048]; + uint8 _deflatedBuf[2048]; + const uint8 * _sendToSlave; + bool _deflateAllocated; + z_stream _writeDeflater; +}; + +}; // end namespace muscle + +#endif + +#endif diff --git a/zlib/ZLibUtilityFunctions.cpp b/zlib/ZLibUtilityFunctions.cpp new file mode 100644 index 00000000..7f2da4ac --- /dev/null +++ b/zlib/ZLibUtilityFunctions.cpp @@ -0,0 +1,165 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +#include "message/Message.h" +#include "system/SetupSystem.h" +#include "zlib/ZLibCodec.h" +#include "zlib/ZLibUtilityFunctions.h" +#ifndef MUSCLE_AVOID_THREAD_LOCAL_STORAGE +# ifdef MUSCLE_SINGLE_THREAD_ONLY +# define MUSCLE_AVOID_THREAD_LOCAL_STORAGE +# else +# include "system/ThreadLocalStorage.h" +# endif +#endif + +namespace muscle { + +static const String MUSCLE_ZLIB_FIELD_NAME_STRING = MUSCLE_ZLIB_FIELD_NAME; + +#ifdef MUSCLE_AVOID_THREAD_LOCAL_STORAGE +static Mutex _zlibLock; // a separate lock because using the global lock was causing deadlockfinder hits +static ZLibCodec * _codecs[10] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; +static bool _cleanupCallbackInstalled = false; +#else +static ThreadLocalStorage _codecs[10]; // using ThreadLocalStorage == no locking == no headaches :) +#endif + +#ifdef MUSCLE_AVOID_THREAD_LOCAL_STORAGE +static void FreeZLibCodecs() +{ + if (_zlibLock.Lock() == B_NO_ERROR) // probably isn't necessary since this only gets called during shutdown... but just in case + { + for (uint32 i=0; iLock() == B_NO_ERROR)) + { + if (_cleanupCallbackInstalled == false) // in case we got scooped before could aquire the lock + { + CompleteSetupSystem * css = CompleteSetupSystem::GetCurrentCompleteSetupSystem(); + if (css) + { + static FunctionCallback _freeCodecsCallback(FreeZLibCodecs); + if (css->GetCleanupCallbacks().AddTail(GenericCallbackRef(&_freeCodecsCallback, false)) == B_NO_ERROR) _cleanupCallbackInstalled = true; + } + } + m->Unlock(); + } + } +} +#endif + +static ZLibCodec * GetZLibCodec(int level) +{ +#ifdef MUSCLE_AVOID_THREAD_LOCAL_STORAGE + level = muscleClamp(level, 0, 9); + if (_codecs[level] == NULL) + { + _codecs[level] = newnothrow ZLibCodec(level); // demand-allocate + if (_codecs[level] == NULL) WARN_OUT_OF_MEMORY; + } + return _codecs[level]; +#else + return muscleInRange(level, 0, 9) ? _codecs[level].GetOrCreateThreadLocalObject() : NULL; +#endif +} + +bool IsMessageDeflated(const MessageRef & msgRef) +{ + return ((msgRef())&&(msgRef()->HasName(MUSCLE_ZLIB_FIELD_NAME_STRING))); +} + +ByteBufferRef DeflateByteBuffer(const uint8 * buf, uint32 numBytes, int compressionLevel, uint32 addHeaderBytes, uint32 addFooterBytes) +{ + ByteBufferRef ret; +#ifdef MUSCLE_AVOID_THREAD_LOCAL_STORAGE + if (_zlibLock.Lock() == B_NO_ERROR) // serialize so that it's thread safe! + { + ZLibCodec * codec = GetZLibCodec(compressionLevel); + if (codec) ret = codec->Deflate(buf, numBytes, true, addHeaderBytes, addFooterBytes); + (void) _zlibLock.Unlock(); + if (codec) EnsureCleanupCallbackInstalled(); // do this outside of the ZLib lock! + } +#else + ZLibCodec * codec = GetZLibCodec(compressionLevel); + if (codec) ret = codec->Deflate(buf, numBytes, true, addHeaderBytes, addFooterBytes); +#endif + return ret; +} + +ByteBufferRef InflateByteBuffer(const uint8 * buf, uint32 numBytes) +{ + ByteBufferRef ret; +#ifdef MUSCLE_AVOID_THREAD_LOCAL_STORAGE + if (_zlibLock.Lock() == B_NO_ERROR) // serialize so that it's thread safe! + { + ZLibCodec * codec = GetZLibCodec(6); // doesn't matter which compression-level/codec we use, any of them can inflate anything + if (codec) ret = codec->Inflate(buf, numBytes); + _zlibLock.Unlock(); + if (codec) EnsureCleanupCallbackInstalled(); // do this outside of the ZLib lock! + } +#else + ZLibCodec * codec = GetZLibCodec(6); // doesn't matter which compression-level/codec we use, any of them can inflate anything + if (codec) ret = codec->Inflate(buf, numBytes); +#endif + return ret; +} + +MessageRef DeflateMessage(const MessageRef & msgRef, int compressionLevel, bool force) +{ + TCHECKPOINT; + + MessageRef ret = msgRef; + if ((msgRef())&&(msgRef()->HasName(MUSCLE_ZLIB_FIELD_NAME_STRING) == false)) + { + MessageRef defMsg = GetMessageFromPool(msgRef()->what); + ByteBufferRef buf = msgRef()->FlattenToByteBuffer(); + if ((defMsg())&&(buf())) + { + buf = DeflateByteBuffer(*buf(), compressionLevel); + if ((buf())&&(defMsg()->AddFlat(MUSCLE_ZLIB_FIELD_NAME_STRING, FlatCountableRef(buf.GetRefCountableRef(), false)) == B_NO_ERROR)&&((force)||(defMsg()->FlattenedSize() < msgRef()->FlattenedSize()))) ret = defMsg; + } + } + return ret; +} + +MessageRef InflateMessage(const MessageRef & msgRef) +{ + TCHECKPOINT; + + MessageRef ret; + ByteBufferRef bufRef; + if ((msgRef())&&(msgRef()->FindFlat(MUSCLE_ZLIB_FIELD_NAME_STRING, bufRef) == B_NO_ERROR)) + { + MessageRef infMsg = GetMessageFromPool(); + if (infMsg()) + { + bufRef = InflateByteBuffer(*bufRef()); + if ((bufRef())&&(infMsg()->UnflattenFromByteBuffer(bufRef) == B_NO_ERROR)) + { + infMsg()->what = msgRef()->what; // do this after UnflattenFromByteBuffer(), so that the outer 'what' is the one that gets used + ret = infMsg; + } + } + } + else ret = msgRef; + + return ret; +} + +}; // end namespace muscle + +#endif diff --git a/zlib/ZLibUtilityFunctions.h b/zlib/ZLibUtilityFunctions.h new file mode 100644 index 00000000..9c48dc61 --- /dev/null +++ b/zlib/ZLibUtilityFunctions.h @@ -0,0 +1,142 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef ZLibUtilityFunctions_h +#define ZLibUtilityFunctions_h + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +#include "message/Message.h" + +namespace muscle { + +/** @defgroup zlibutilityfunctions The ZLibUtilityFunctions function API + * These functions are all defined in ZLibUtilityFunctions(.cpp,.h), and are stand-alone + * functions that do give an easy-to-use, high-level way to compress and uncompress + * raw data and Message objects. + * @{ + */ + +/** Given some data, returns a ByteBuffer containing a compressed version of that date. + * @param bytes Pointer to the data to compress + * @param numBytes Number of bytes that (bytes) points to + * @param compressionLevel The level of ZLib compression to use when creating the + * compressed Message. Should be between 0 (no compression) + * and 9 (maximum compression). Default value is 6. + * @param addHeaderBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the beginning of the byte array, + * before the first compressed-data byte. The values in these bytes + * are undefined; the caller can write header data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @param addFooterBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the end of the byte array, + * after the last compressed-data byte. The values in these bytes + * are undefined; the caller can write footer data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @returns a reference to a compressed ByteBuffer on success, or a NULL reference on failure. + */ +ByteBufferRef DeflateByteBuffer(const uint8 * bytes, uint32 numBytes, int compressionLevel = 6, uint32 addHeaderBytes = 0, uint32 addFooterBytes = 0); + +/** Given a ByteBuffer, returns a compressed version of the data. + * @param buf a ByteBuffer to compress that you want to get a compressed version of. + * @param compressionLevel The level of ZLib compression to use when creating the + * compressed Message. Should be between 0 (no compression) + * and 9 (maximum compression). Default value is 6. + * @param addHeaderBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the beginning of the byte array, + * before the first compressed-data byte. The values in these bytes + * are undefined; the caller can write header data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @param addFooterBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the end of the byte array, + * after the last compressed-data byte. The values in these bytes + * are undefined; the caller can write footer data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @returns a reference to a compressed ByteBuffer (not (buf)!) on success, or a NULL reference on failure. + */ +static inline ByteBufferRef DeflateByteBuffer(const ByteBuffer & buf, int compressionLevel = 6, uint32 addHeaderBytes = 0, uint32 addFooterBytes = 0) {return DeflateByteBuffer(buf.GetBuffer(), buf.GetNumBytes(), compressionLevel, addHeaderBytes, addFooterBytes);} + +/** Given a ByteBufferRef, returns a compressed version of the data. + * @param buf reference to a ByteBuffer to compress that you want to get a compressed version of. + * @param compressionLevel The level of ZLib compression to use when creating the + * compressed Message. Should be between 0 (no compression) + * and 9 (maximum compression). Default value is 6. + * @param addHeaderBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the beginning of the byte array, + * before the first compressed-data byte. The values in these bytes + * are undefined; the caller can write header data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @param addFooterBytes If set to non-zero, the returned ByteBuffer will contain + * this many additional bytes at the end of the byte array, + * after the last compressed-data byte. The values in these bytes + * are undefined; the caller can write footer data to them if desired. + * Leave this set to zero if you're not sure of what you are doing. + * @returns a reference to a compressed ByteBuffer (not (buf())!) on success, or a NULL reference on failure. + */ +static inline ByteBufferRef DeflateByteBuffer(const ByteBufferRef & buf, int compressionLevel = 6, uint32 addHeaderBytes = 0, uint32 addFooterBytes = 0) {return buf() ? DeflateByteBuffer(*buf(), compressionLevel, addHeaderBytes, addFooterBytes) : ByteBufferRef();} + +/** Given come compressed data, returns a ByteBuffer containing the original/uncompressed data. + * @param bytes Pointer to the data to uncompress + * @param numBytes Number of bytes that (bytes) points to + * @returns a reference to an uncompressed ByteBuffer on success, or a NULL reference on failure. + */ +ByteBufferRef InflateByteBuffer(const uint8 * bytes, uint32 numBytes); + +/** Given a compressed ByteBuffer, returns the original/uncompressed version of the data. + * @param buf a compressed ByteBuffer you want to get the decompressed version of. + * @returns a reference to an uncompressed ByteBuffer (not (buf)!) on success, or a NULL reference on failure. + */ +static inline ByteBufferRef InflateByteBuffer(const ByteBuffer & buf) {return InflateByteBuffer(buf.GetBuffer(), buf.GetNumBytes());} + +/** Given a reference to a compressed ByteBuffer, returns the original/uncompressed version of the data. + * @param buf a compressed ByteBuffer you want to get the decompressed version of. + * @returns a reference to an uncompressed ByteBuffer (not (buf())!) on success, or a NULL reference on failure. + */ +static inline ByteBufferRef InflateByteBuffer(const ByteBufferRef & buf) {return buf() ? InflateByteBuffer(*buf()) : ByteBufferRef();} + +/** Returns true iff the given MessageRef points to a deflated Message. + * Returns false iff the MessageRef is NULL or not deflated. + * @param msgRef The Message to determine the inflated/deflated status of. + */ +bool IsMessageDeflated(const MessageRef & msgRef); + +/** Examines the contents of the given Message, and creates and returns a new + * Message that represents the same data as the given Message, but in compressed form. + * If the passed-in Message is already in compressed form (i.e. it was created by + * a previous call to DeflateMessage()), or if the deflation didn't decrease the size + * any, then a reference to the original passed-in Message is returned instead. + * The returned Message is guaranteed to have the same 'what' code as the passed-in Message. + * If there is an error (out of memory?), a NULL reference is returned. + * @param msgRef The Message to create a compact version of. + * @param msgRef Reference to the newly generated compressed Message, or to the passed + * in Message, or a NULL reference on failure. + * @param compressionLevel The level of ZLib compression to use when creating the + * compressed Message. Should be between 0 (no compression) + * and 9 (maximum compression). Default value is 6. + * @param force If true, we will return a compressed Message even if the compressed Message's + * size is bigger than that of the original(!). Otherwise, we'll return the + * original Message if the compression didn't actually make the Message's flattened + * size smaller. Defaults to true. + */ +MessageRef DeflateMessage(const MessageRef & msgRef, int compressionLevel = 6, bool force=true); + +/** Examines the given Message, and if it is a Message in compressed form (i.e. one + * that was previously created by DeflateMessage()), creates and returns the + * equivalent uncompressed Message. If the passed-in Message was not in compressed + * form, then this function just returns a reference to the original passed-in Message. + * The returned Message is guaranteed to have the same 'what' code as the passed-in Message. + * Returns a NULL reference on failure (out of memory?) + * @param msgRef Message to examine and make an uncompressed equivalent of. + * @return Reference to an uncompressed Message on success, or a NULL reference on failure. + */ +MessageRef InflateMessage(const MessageRef & msgRef); + +// This is the field name that we store deflated data into +#define MUSCLE_ZLIB_FIELD_NAME "_zlib" + +/** @} */ // end of zlibutilityfunctions doxygen group + +}; // end namespace muscle + +#endif + +#endif diff --git a/zlib/ZipFileUtilityFunctions.cpp b/zlib/ZipFileUtilityFunctions.cpp new file mode 100644 index 00000000..8d7064a3 --- /dev/null +++ b/zlib/ZipFileUtilityFunctions.cpp @@ -0,0 +1,254 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifdef MUSCLE_ENABLE_ZLIB_ENCODING + +#include "dataio/FileDataIO.h" +#include "util/ByteBuffer.h" +#include "util/MiscUtilityFunctions.h" +#include "util/StringTokenizer.h" +#include "zlib/ZipFileUtilityFunctions.h" +#include "zlib/zlib/contrib/minizip/zip.h" +#include "zlib/zlib/contrib/minizip/unzip.h" + +namespace muscle { + +static voidpf ZCALLBACK fopen_dataio_func (voidpf opaque, const char * /*filename*/, int /*mode*/) +{ + return opaque; +} + +static uLong ZCALLBACK fread_dataio_func (voidpf /*opaque*/, voidpf stream, void *buf, uLong size) +{ + DataIO * dio = (DataIO *)stream; + return (uLong) (dio ? dio->ReadFully(buf, size) : 0); +} + +static uLong ZCALLBACK fwrite_dataio_func (voidpf /*opaque*/, voidpf stream, const void * buf, uLong size) +{ + DataIO * dio = (DataIO *)stream; + return (uLong) (dio ? dio->WriteFully(buf, size) : 0); +} + +static long ZCALLBACK ftell_dataio_func (voidpf /*opaque*/, voidpf stream) +{ + DataIO * dio = (DataIO *)stream; + return (long) (dio ? dio->GetPosition() : -1); +} + +static long ZCALLBACK fseek_dataio_func (voidpf /*opaque*/, voidpf stream, uLong offset, int origin) +{ + int muscleSeekOrigin; + switch(origin) + { + case ZLIB_FILEFUNC_SEEK_CUR: muscleSeekOrigin = DataIO::IO_SEEK_CUR; break; + case ZLIB_FILEFUNC_SEEK_END: muscleSeekOrigin = DataIO::IO_SEEK_END; break; + case ZLIB_FILEFUNC_SEEK_SET: muscleSeekOrigin = DataIO::IO_SEEK_SET; break; + default: return -1; + } + DataIO * dio = (DataIO *)stream; + return (long) ((dio)&&(dio->Seek(offset, muscleSeekOrigin) == B_NO_ERROR)) ? 0 : -1; +} + +static int ZCALLBACK fclose_dataio_func (voidpf /*opaque*/, voidpf /*stream*/) +{ + // empty + return 0; +} + +static int ZCALLBACK ferror_dataio_func (voidpf /*opaque*/, voidpf /*stream*/) +{ + // empty + return -1; +} + +static status_t WriteZipFileAux(zipFile zf, const String & baseName, const Message & msg, int compressionLevel, zip_fileinfo * fileInfo) +{ + for (MessageFieldNameIterator iter = msg.GetFieldNameIterator(); iter.HasData(); iter++) + { + const String & fn = iter.GetFieldName(); + + uint32 fieldType; + if (msg.GetInfo(fn, &fieldType) != B_NO_ERROR) return B_ERROR; + switch(fieldType) + { + case B_MESSAGE_TYPE: + { + String newBaseName = baseName; + if ((newBaseName.HasChars())&&(newBaseName.EndsWith('/') == false)) newBaseName += '/'; + newBaseName += fn; + + // Message fields we treat as sub-directories + MessageRef subMsg; + for (int32 i=0; msg.FindMessage(fn, i, subMsg) == B_NO_ERROR; i++) if (WriteZipFileAux(zf, newBaseName, *subMsg(), compressionLevel, fileInfo) != B_NO_ERROR) return B_ERROR; + } + break; + + case B_RAW_TYPE: + { + String fileName = baseName; + if ((fileName.HasChars())&&(fileName.EndsWith('/') == false)) fileName += '/'; + fileName += fn; + + const void * data; + uint32 numBytes; + for (int32 i=0; msg.FindData(fn, B_RAW_TYPE, i, &data, &numBytes) == B_NO_ERROR; i++) + { + if (zipOpenNewFileInZip2(zf, + fileName(), // file name + fileInfo, // file info + NULL, // const void* extrafield_local, + 0, // uInt size_extrafield_local, + NULL, // const void* extrafield_global, + 0, // uInt size_extrafield_global, + NULL, // const char* comment, + (compressionLevel>0)?Z_DEFLATED:0, // int method, + compressionLevel, // int compressionLevel + 0) != ZIP_OK) return B_ERROR; + if (zipWriteInFileInZip(zf, data, numBytes) != ZIP_OK) return B_ERROR; + if (zipCloseFileInZip(zf) != ZIP_OK) return B_ERROR; + } + } + break; + } + } + return B_NO_ERROR; +} + +status_t WriteZipFile(const char * fileName, const Message & msg, int compressionLevel, uint64 fileCreationTime) +{ + FileDataIO fio(fopen(fileName, "wb")); + return WriteZipFile(fio, msg, compressionLevel, fileCreationTime); +} + +status_t WriteZipFile(DataIO & writeTo, const Message & msg, int compressionLevel, uint64 fileCreationTime) +{ + TCHECKPOINT; + + zlib_filefunc_def zdefs = { + fopen_dataio_func, + fread_dataio_func, + fwrite_dataio_func, + ftell_dataio_func, + fseek_dataio_func, + fclose_dataio_func, + ferror_dataio_func, + &writeTo + }; + const char * comment = ""; + zipFile zf = zipOpen2(NULL, false, &comment, &zdefs); + if (zf) + { + zip_fileinfo * fi = NULL; + zip_fileinfo fileInfo; + { + memset(&fileInfo, 0, sizeof(fileInfo)); + HumanReadableTimeValues v; + if (GetHumanReadableTimeValues((fileCreationTime==MUSCLE_TIME_NEVER)?GetCurrentTime64(MUSCLE_TIMEZONE_LOCAL):fileCreationTime, v, MUSCLE_TIMEZONE_LOCAL) == B_NO_ERROR) + { + fi = &fileInfo; + fileInfo.tmz_date.tm_sec = v.GetSecond(); + fileInfo.tmz_date.tm_min = v.GetMinute(); + fileInfo.tmz_date.tm_hour = v.GetHour(); + fileInfo.tmz_date.tm_mday = v.GetDayOfMonth()+1; + fileInfo.tmz_date.tm_mon = v.GetMonth(); + fileInfo.tmz_date.tm_year = v.GetYear(); + } + } + + status_t ret = WriteZipFileAux(zf, "", msg, compressionLevel, fi); + zipClose(zf, NULL); + return ret; + } + else return B_ERROR; +} + +static status_t ReadZipFileAux(zipFile zf, Message & msg, char * nameBuf, uint32 nameBufLen, bool loadData) +{ + while(unzOpenCurrentFile(zf) == UNZ_OK) + { + unz_file_info fileInfo; + if (unzGetCurrentFileInfo(zf, &fileInfo, nameBuf, nameBufLen, NULL, 0, NULL, 0) != UNZ_OK) return B_ERROR; + + // Add the new entry to the appropriate spot in the tree (demand-allocate sub-Messages as necessary) + { + const char * nulByte = strchr(nameBuf, '\0'); + bool isFolder = ((nulByte > nameBuf)&&(*(nulByte-1) == '/')); + Message * m = &msg; + StringTokenizer tok(true, nameBuf, "/"); + const char * nextTok; + while((nextTok = tok()) != NULL) + { + String fn(nextTok); + if ((isFolder)||(tok.GetRemainderOfString())) + { + // Demand-allocate a sub-message + MessageRef subMsg; + if (m->FindMessage(fn, subMsg) != B_NO_ERROR) + { + if ((m->AddMessage(fn, Message()) != B_NO_ERROR)||(m->FindMessage(fn, subMsg) != B_NO_ERROR)) return B_ERROR; + } + m = subMsg(); + } + else + { + if (loadData) + { + ByteBufferRef bufRef = GetByteBufferFromPool(fileInfo.uncompressed_size); + if ((bufRef() == NULL)||(unzReadCurrentFile(zf, bufRef()->GetBuffer(), bufRef()->GetNumBytes()) != (int32)bufRef()->GetNumBytes())||(m->AddFlat(fn, bufRef) != B_NO_ERROR)) return B_ERROR; + } + else if (m->AddInt64(fn, fileInfo.uncompressed_size) != B_NO_ERROR) return B_ERROR; + } + } + } + if (unzCloseCurrentFile(zf) != UNZ_OK) return B_ERROR; + if (unzGoToNextFile(zf) != UNZ_OK) break; + } + return B_NO_ERROR; +} + +MessageRef ReadZipFile(const char * fileName, bool loadData) +{ + FileDataIO fio(fopen(fileName, "rb")); + return ReadZipFile(fio, loadData); +} + +MessageRef ReadZipFile(DataIO & readFrom, bool loadData) +{ + TCHECKPOINT; + + static const int NAME_BUF_LEN = 8*1024; // names longer than 8KB are ridiculous anyway! + char * nameBuf = newnothrow_array(char, NAME_BUF_LEN); + if (nameBuf) + { + MessageRef ret = GetMessageFromPool(); + if (ret()) + { + zlib_filefunc_def zdefs = { + fopen_dataio_func, + fread_dataio_func, + fwrite_dataio_func, + ftell_dataio_func, + fseek_dataio_func, + fclose_dataio_func, + ferror_dataio_func, + &readFrom + }; + zipFile zf = unzOpen2(NULL, &zdefs); + if (zf != NULL) + { + if (ReadZipFileAux(zf, *ret(), nameBuf, NAME_BUF_LEN, loadData) != B_NO_ERROR) ret.Reset(); + unzClose(zf); + } + else ret.Reset(); // failure! + } + delete [] nameBuf; + return ret; + } + else WARN_OUT_OF_MEMORY; + + return MessageRef(); +} + +}; // end namespace muscle + +#endif diff --git a/zlib/ZipFileUtilityFunctions.h b/zlib/ZipFileUtilityFunctions.h new file mode 100644 index 00000000..2873987b --- /dev/null +++ b/zlib/ZipFileUtilityFunctions.h @@ -0,0 +1,76 @@ +/* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */ + +#ifndef ZipFileUtilityFunctions_h +#define ZipFileUtilityFunctions_h + +#include "dataio/DataIO.h" +#include "message/Message.h" + +namespace muscle { + +/** @defgroup zipfileutilityfunctions The ZipFileUtilityFunctions function API + * These functions are all defined in ZipFileUtilityFunctions(.cpp,.h), and are stand-alone + * functions that conveniently convert .zip files into Message objects, and vice versa. + * @{ + */ + +/** Given a Message object, outputs a .zip file containing the B_RAW_TYPE data in that Message. + * Each B_RAW_TYPE field in the Message object will be written to the .zip file as a contained file. + * Message fields will be written recursively to the .zip file as sub-directories. + * @param writeTo DataIO object representing the file (or whatever) that we are outputting data to. + * @param msg Reference to the Message to write to (writeTo). + * @param compressionLevel A number between 0 (no compression) and 9 (maximum compression). Default value is 9. + * @param fileCreationTime the file creation time (in microseconds since 1970) to assign to all of the + * file-records in the .zip file. If left as MUSCLE_TIME_NEVER (the default) then + * the current time will be used. + * @note This function is useful only when you need to be compatible with the .ZIP file format. + * In particular, it does NOT save all the information in the Message -- only the contents + * of the B_RAW_TYPE fields (as "files") and B_MESSAGE_TYPE fields as ("folders"). + * If you need to store Message objects to disk and read them back verbatim later, you should + * use Message::Flatten() and Message::Unflatten() to do so, not this function. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t WriteZipFile(DataIO & writeTo, const Message & msg, int compressionLevel = 9, uint64 fileCreationTime = MUSCLE_TIME_NEVER); + +/** Convenience method: as above, but takes a file name instead of a DataIO object. + * @param fileName File path of the place to write the .zip file to + * @param msg Reference to the Message to write to (fileName). + * @param compressionLevel A number between 0 (no compression) and 9 (maximum compression). Default value is 9. + * @param fileCreationTime the file creation time (in microseconds since 1970) to assign to all of the + * file-records in the .zip file. If left as MUSCLE_TIME_NEVER (the default) then + * the current time will be used. + * @returns B_NO_ERROR on success, or B_ERROR on failure. + */ +status_t WriteZipFile(const char * fileName, const Message & msg, int compressionLevel = 9, uint64 fileCreationTime = MUSCLE_TIME_NEVER); + +/** Given a DataIO object to read from (often a FileDataIO for a .zip file on disk), + * reads the file and creates and returns an equivalent Message object. Each contained + * file in the .zip file will appear in the Message object as a B_RAW_DATA field + * (or a B_INT64_TYPE field, if you specified (loadData) to be false), and each directory + * in the .zip file will appear in the Message object as a Message field. + * @param readFrom DataIO to read the .zip file data from. + * @param loadData If true, all the zip file's data will be decompressed and added + * to the return Message. If false, only the filenames and lengths + * will be read (each file will show up as a B_INT64_TYPE field + * with the int64 value being the file's length); the actual file + * data will not be decompressed or placed into memory. Defaults + * to true. (You might set this argument to false if you just wanted + * to check that the expected files were present in the .zip, but + * not actually load them) + * @returns a valid MessageRef on success, or a NULL MessageRef on failure. + */ +MessageRef ReadZipFile(DataIO & readFrom, bool loadData = true); + +/** Convenience method: as above, but takes a file name instead of a DataIO object. + * @param fileName File name of a .zip file to read + * @param loadData Whether or not to read data or just directory structure (see previous + * overload of ReadZipFile() for a full explanation) + * @returns a valid MessageRef on success, or a NULL MessageRef on failure. + */ +MessageRef ReadZipFile(const char * fileName, bool loadData = true); + +/** @} */ // end of zipfileutilityfunctions doxygen group + +}; // end namespace muscle + +#endif diff --git a/zlib/zlib/CMakeLists.txt b/zlib/zlib/CMakeLists.txt new file mode 100644 index 00000000..0c0247cc --- /dev/null +++ b/zlib/zlib/CMakeLists.txt @@ -0,0 +1,249 @@ +cmake_minimum_required(VERSION 2.4.4) +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) + +project(zlib C) + +set(VERSION "1.2.8") + +option(ASM686 "Enable building i686 assembly implementation") +option(AMD64 "Enable building amd64 assembly implementation") + +set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") +set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") +set(INSTALL_INC_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "Installation directory for headers") +set(INSTALL_MAN_DIR "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Installation directory for manual pages") +set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") + +include(CheckTypeSize) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckCSourceCompiles) +enable_testing() + +check_include_file(sys/types.h HAVE_SYS_TYPES_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(stddef.h HAVE_STDDEF_H) + +# +# Check to see if we have large file support +# +set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1) +# We add these other definitions here because CheckTypeSize.cmake +# in CMake 2.4.x does not automatically do so and we want +# compatibility with CMake 2.4.x. +if(HAVE_SYS_TYPES_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H) +endif() +if(HAVE_STDINT_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H) +endif() +if(HAVE_STDDEF_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H) +endif() +check_type_size(off64_t OFF64_T) +if(HAVE_OFF64_T) + add_definitions(-D_LARGEFILE64_SOURCE=1) +endif() +set(CMAKE_REQUIRED_DEFINITIONS) # clear variable + +# +# Check for fseeko +# +check_function_exists(fseeko HAVE_FSEEKO) +if(NOT HAVE_FSEEKO) + add_definitions(-DNO_FSEEKO) +endif() + +# +# Check for unistd.h +# +check_include_file(unistd.h Z_HAVE_UNISTD_H) + +if(MSVC) + set(CMAKE_DEBUG_POSTFIX "d") + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # If we're doing an out of source build and the user has a zconf.h + # in their source tree... + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h) + message(STATUS "Renaming") + message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h") + message(STATUS "to 'zconf.h.included' because this file is included with zlib") + message(STATUS "but CMake generates it automatically in the build directory.") + file(RENAME ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.included) + endif() +endif() + +set(ZLIB_PC ${CMAKE_CURRENT_BINARY_DIR}/zlib.pc) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zlib.pc.cmakein + ${ZLIB_PC} @ONLY) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}) + + +#============================================================================ +# zlib +#============================================================================ + +set(ZLIB_PUBLIC_HDRS + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h + zlib.h +) +set(ZLIB_PRIVATE_HDRS + crc32.h + deflate.h + gzguts.h + inffast.h + inffixed.h + inflate.h + inftrees.h + trees.h + zutil.h +) +set(ZLIB_SRCS + adler32.c + compress.c + crc32.c + deflate.c + gzclose.c + gzlib.c + gzread.c + gzwrite.c + inflate.c + infback.c + inftrees.c + inffast.c + trees.c + uncompr.c + zutil.c +) + +if(NOT MINGW) + set(ZLIB_DLL_SRCS + win32/zlib1.rc # If present will override custom build rule below. + ) +endif() + +if(CMAKE_COMPILER_IS_GNUCC) + if(ASM686) + set(ZLIB_ASMS contrib/asm686/match.S) + elseif (AMD64) + set(ZLIB_ASMS contrib/amd64/amd64-match.S) + endif () + + if(ZLIB_ASMS) + add_definitions(-DASMV) + set_source_files_properties(${ZLIB_ASMS} PROPERTIES LANGUAGE C COMPILE_FLAGS -DNO_UNDERLINE) + endif() +endif() + +if(MSVC) + if(ASM686) + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx86/inffas32.asm + contrib/masmx86/match686.asm + ) + elseif (AMD64) + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx64/gvmat64.asm + contrib/masmx64/inffasx64.asm + ) + endif() + + if(ZLIB_ASMS) + add_definitions(-DASMV -DASMINF) + endif() +endif() + +# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents) +string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([-0-9A-Za-z.]+)\".*" + "\\1" ZLIB_FULL_VERSION ${_zlib_h_contents}) + +if(MINGW) + # This gets us DLL resource information when compiling on MinGW. + if(NOT CMAKE_RC_COMPILER) + set(CMAKE_RC_COMPILER windres.exe) + endif() + + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + COMMAND ${CMAKE_RC_COMPILER} + -D GCC_WINDRES + -I ${CMAKE_CURRENT_SOURCE_DIR} + -I ${CMAKE_CURRENT_BINARY_DIR} + -o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc) + set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) +endif(MINGW) + +add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) +set_target_properties(zlib PROPERTIES SOVERSION 1) + +if(NOT CYGWIN) + # This property causes shared libraries on Linux to have the full version + # encoded into their final filename. We disable this on Cygwin because + # it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll + # seems to be the default. + # + # This has no effect with MSVC, on that platform the version info for + # the DLL comes from the resource file win32/zlib1.rc + set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION}) +endif() + +if(UNIX) + # On unix-like platforms the library is almost always called libz + set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z) + if(NOT APPLE) + set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"") + endif() +elseif(BUILD_SHARED_LIBS AND WIN32) + # Creates zlib1.dll when building shared library version + set_target_properties(zlib PROPERTIES SUFFIX "1.dll") +endif() + +if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) + install(TARGETS zlib zlibstatic + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" ) +endif() +if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION "${INSTALL_INC_DIR}") +endif() +if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES zlib.3 DESTINATION "${INSTALL_MAN_DIR}/man3") +endif() +if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PC} DESTINATION "${INSTALL_PKGCONFIG_DIR}") +endif() + +#============================================================================ +# Example binaries +#============================================================================ + +add_executable(example test/example.c) +target_link_libraries(example zlib) +add_test(example example) + +add_executable(minigzip test/minigzip.c) +target_link_libraries(minigzip zlib) + +if(HAVE_OFF64_T) + add_executable(example64 test/example.c) + target_link_libraries(example64 zlib) + set_target_properties(example64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") + add_test(example64 example64) + + add_executable(minigzip64 test/minigzip.c) + target_link_libraries(minigzip64 zlib) + set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") +endif() diff --git a/zlib/zlib/ChangeLog b/zlib/zlib/ChangeLog new file mode 100644 index 00000000..f22aabae --- /dev/null +++ b/zlib/zlib/ChangeLog @@ -0,0 +1,1472 @@ + + ChangeLog file for zlib + +Changes in 1.2.8 (28 Apr 2013) +- Update contrib/minizip/iowin32.c for Windows RT [Vollant] +- Do not force Z_CONST for C++ +- Clean up contrib/vstudio [Ro§] +- Correct spelling error in zlib.h +- Fix mixed line endings in contrib/vstudio + +Changes in 1.2.7.3 (13 Apr 2013) +- Fix version numbers and DLL names in contrib/vstudio/*/zlib.rc + +Changes in 1.2.7.2 (13 Apr 2013) +- Change check for a four-byte type back to hexadecimal +- Fix typo in win32/Makefile.msc +- Add casts in gzwrite.c for pointer differences + +Changes in 1.2.7.1 (24 Mar 2013) +- Replace use of unsafe string functions with snprintf if available +- Avoid including stddef.h on Windows for Z_SOLO compile [Niessink] +- Fix gzgetc undefine when Z_PREFIX set [Turk] +- Eliminate use of mktemp in Makefile (not always available) +- Fix bug in 'F' mode for gzopen() +- Add inflateGetDictionary() function +- Correct comment in deflate.h +- Use _snprintf for snprintf in Microsoft C +- On Darwin, only use /usr/bin/libtool if libtool is not Apple +- Delete "--version" file if created by "ar --version" [Richard G.] +- Fix configure check for veracity of compiler error return codes +- Fix CMake compilation of static lib for MSVC2010 x64 +- Remove unused variable in infback9.c +- Fix argument checks in gzlog_compress() and gzlog_write() +- Clean up the usage of z_const and respect const usage within zlib +- Clean up examples/gzlog.[ch] comparisons of different types +- Avoid shift equal to bits in type (caused endless loop) +- Fix unintialized value bug in gzputc() introduced by const patches +- Fix memory allocation error in examples/zran.c [Nor] +- Fix bug where gzopen(), gzclose() would write an empty file +- Fix bug in gzclose() when gzwrite() runs out of memory +- Check for input buffer malloc failure in examples/gzappend.c +- Add note to contrib/blast to use binary mode in stdio +- Fix comparisons of differently signed integers in contrib/blast +- Check for invalid code length codes in contrib/puff +- Fix serious but very rare decompression bug in inftrees.c +- Update inflateBack() comments, since inflate() can be faster +- Use underscored I/O function names for WINAPI_FAMILY +- Add _tr_flush_bits to the external symbols prefixed by --zprefix +- Add contrib/vstudio/vc10 pre-build step for static only +- Quote --version-script argument in CMakeLists.txt +- Don't specify --version-script on Apple platforms in CMakeLists.txt +- Fix casting error in contrib/testzlib/testzlib.c +- Fix types in contrib/minizip to match result of get_crc_table() +- Simplify contrib/vstudio/vc10 with 'd' suffix +- Add TOP support to win32/Makefile.msc +- Suport i686 and amd64 assembler builds in CMakeLists.txt +- Fix typos in the use of _LARGEFILE64_SOURCE in zconf.h +- Add vc11 and vc12 build files to contrib/vstudio +- Add gzvprintf() as an undocumented function in zlib +- Fix configure for Sun shell +- Remove runtime check in configure for four-byte integer type +- Add casts and consts to ease user conversion to C++ +- Add man pages for minizip and miniunzip +- In Makefile uninstall, don't rm if preceding cd fails +- Do not return Z_BUF_ERROR if deflateParam() has nothing to write + +Changes in 1.2.7 (2 May 2012) +- Replace use of memmove() with a simple copy for portability +- Test for existence of strerror +- Restore gzgetc_ for backward compatibility with 1.2.6 +- Fix build with non-GNU make on Solaris +- Require gcc 4.0 or later on Mac OS X to use the hidden attribute +- Include unistd.h for Watcom C +- Use __WATCOMC__ instead of __WATCOM__ +- Do not use the visibility attribute if NO_VIZ defined +- Improve the detection of no hidden visibility attribute +- Avoid using __int64 for gcc or solo compilation +- Cast to char * in gzprintf to avoid warnings [Zinser] +- Fix make_vms.com for VAX [Zinser] +- Don't use library or built-in byte swaps +- Simplify test and use of gcc hidden attribute +- Fix bug in gzclose_w() when gzwrite() fails to allocate memory +- Add "x" (O_EXCL) and "e" (O_CLOEXEC) modes support to gzopen() +- Fix bug in test/minigzip.c for configure --solo +- Fix contrib/vstudio project link errors [Mohanathas] +- Add ability to choose the builder in make_vms.com [Schweda] +- Add DESTDIR support to mingw32 win32/Makefile.gcc +- Fix comments in win32/Makefile.gcc for proper usage +- Allow overriding the default install locations for cmake +- Generate and install the pkg-config file with cmake +- Build both a static and a shared version of zlib with cmake +- Include version symbols for cmake builds +- If using cmake with MSVC, add the source directory to the includes +- Remove unneeded EXTRA_CFLAGS from win32/Makefile.gcc [Truta] +- Move obsolete emx makefile to old [Truta] +- Allow the use of -Wundef when compiling or using zlib +- Avoid the use of the -u option with mktemp +- Improve inflate() documentation on the use of Z_FINISH +- Recognize clang as gcc +- Add gzopen_w() in Windows for wide character path names +- Rename zconf.h in CMakeLists.txt to move it out of the way +- Add source directory in CMakeLists.txt for building examples +- Look in build directory for zlib.pc in CMakeLists.txt +- Remove gzflags from zlibvc.def in vc9 and vc10 +- Fix contrib/minizip compilation in the MinGW environment +- Update ./configure for Solaris, support --64 [Mooney] +- Remove -R. from Solaris shared build (possible security issue) +- Avoid race condition for parallel make (-j) running example +- Fix type mismatch between get_crc_table() and crc_table +- Fix parsing of version with "-" in CMakeLists.txt [Snider, Ziegler] +- Fix the path to zlib.map in CMakeLists.txt +- Force the native libtool in Mac OS X to avoid GNU libtool [Beebe] +- Add instructions to win32/Makefile.gcc for shared install [Torri] + +Changes in 1.2.6.1 (12 Feb 2012) +- Avoid the use of the Objective-C reserved name "id" +- Include io.h in gzguts.h for Microsoft compilers +- Fix problem with ./configure --prefix and gzgetc macro +- Include gz_header definition when compiling zlib solo +- Put gzflags() functionality back in zutil.c +- Avoid library header include in crc32.c for Z_SOLO +- Use name in GCC_CLASSIC as C compiler for coverage testing, if set +- Minor cleanup in contrib/minizip/zip.c [Vollant] +- Update make_vms.com [Zinser] +- Remove unnecessary gzgetc_ function +- Use optimized byte swap operations for Microsoft and GNU [Snyder] +- Fix minor typo in zlib.h comments [Rzesniowiecki] + +Changes in 1.2.6 (29 Jan 2012) +- Update the Pascal interface in contrib/pascal +- Fix function numbers for gzgetc_ in zlibvc.def files +- Fix configure.ac for contrib/minizip [Schiffer] +- Fix large-entry detection in minizip on 64-bit systems [Schiffer] +- Have ./configure use the compiler return code for error indication +- Fix CMakeLists.txt for cross compilation [McClure] +- Fix contrib/minizip/zip.c for 64-bit architectures [Dalsnes] +- Fix compilation of contrib/minizip on FreeBSD [Marquez] +- Correct suggested usages in win32/Makefile.msc [Shachar, Horvath] +- Include io.h for Turbo C / Borland C on all platforms [Truta] +- Make version explicit in contrib/minizip/configure.ac [Bosmans] +- Avoid warning for no encryption in contrib/minizip/zip.c [Vollant] +- Minor cleanup up contrib/minizip/unzip.c [Vollant] +- Fix bug when compiling minizip with C++ [Vollant] +- Protect for long name and extra fields in contrib/minizip [Vollant] +- Avoid some warnings in contrib/minizip [Vollant] +- Add -I../.. -L../.. to CFLAGS for minizip and miniunzip +- Add missing libs to minizip linker command +- Add support for VPATH builds in contrib/minizip +- Add an --enable-demos option to contrib/minizip/configure +- Add the generation of configure.log by ./configure +- Exit when required parameters not provided to win32/Makefile.gcc +- Have gzputc return the character written instead of the argument +- Use the -m option on ldconfig for BSD systems [Tobias] +- Correct in zlib.map when deflateResetKeep was added + +Changes in 1.2.5.3 (15 Jan 2012) +- Restore gzgetc function for binary compatibility +- Do not use _lseeki64 under Borland C++ [Truta] +- Update win32/Makefile.msc to build test/*.c [Truta] +- Remove old/visualc6 given CMakefile and other alternatives +- Update AS400 build files and documentation [Monnerat] +- Update win32/Makefile.gcc to build test/*.c [Truta] +- Permit stronger flushes after Z_BLOCK flushes +- Avoid extraneous empty blocks when doing empty flushes +- Permit Z_NULL arguments to deflatePending +- Allow deflatePrime() to insert bits in the middle of a stream +- Remove second empty static block for Z_PARTIAL_FLUSH +- Write out all of the available bits when using Z_BLOCK +- Insert the first two strings in the hash table after a flush + +Changes in 1.2.5.2 (17 Dec 2011) +- fix ld error: unable to find version dependency 'ZLIB_1.2.5' +- use relative symlinks for shared libs +- Avoid searching past window for Z_RLE strategy +- Assure that high-water mark initialization is always applied in deflate +- Add assertions to fill_window() in deflate.c to match comments +- Update python link in README +- Correct spelling error in gzread.c +- Fix bug in gzgets() for a concatenated empty gzip stream +- Correct error in comment for gz_make() +- Change gzread() and related to ignore junk after gzip streams +- Allow gzread() and related to continue after gzclearerr() +- Allow gzrewind() and gzseek() after a premature end-of-file +- Simplify gzseek() now that raw after gzip is ignored +- Change gzgetc() to a macro for speed (~40% speedup in testing) +- Fix gzclose() to return the actual error last encountered +- Always add large file support for windows +- Include zconf.h for windows large file support +- Include zconf.h.cmakein for windows large file support +- Update zconf.h.cmakein on make distclean +- Merge vestigial vsnprintf determination from zutil.h to gzguts.h +- Clarify how gzopen() appends in zlib.h comments +- Correct documentation of gzdirect() since junk at end now ignored +- Add a transparent write mode to gzopen() when 'T' is in the mode +- Update python link in zlib man page +- Get inffixed.h and MAKEFIXED result to match +- Add a ./config --solo option to make zlib subset with no libary use +- Add undocumented inflateResetKeep() function for CAB file decoding +- Add --cover option to ./configure for gcc coverage testing +- Add #define ZLIB_CONST option to use const in the z_stream interface +- Add comment to gzdopen() in zlib.h to use dup() when using fileno() +- Note behavior of uncompress() to provide as much data as it can +- Add files in contrib/minizip to aid in building libminizip +- Split off AR options in Makefile.in and configure +- Change ON macro to Z_ARG to avoid application conflicts +- Facilitate compilation with Borland C++ for pragmas and vsnprintf +- Include io.h for Turbo C / Borland C++ +- Move example.c and minigzip.c to test/ +- Simplify incomplete code table filling in inflate_table() +- Remove code from inflate.c and infback.c that is impossible to execute +- Test the inflate code with full coverage +- Allow deflateSetDictionary, inflateSetDictionary at any time (in raw) +- Add deflateResetKeep and fix inflateResetKeep to retain dictionary +- Fix gzwrite.c to accommodate reduced memory zlib compilation +- Have inflate() with Z_FINISH avoid the allocation of a window +- Do not set strm->adler when doing raw inflate +- Fix gzeof() to behave just like feof() when read is not past end of file +- Fix bug in gzread.c when end-of-file is reached +- Avoid use of Z_BUF_ERROR in gz* functions except for premature EOF +- Document gzread() capability to read concurrently written files +- Remove hard-coding of resource compiler in CMakeLists.txt [Blammo] + +Changes in 1.2.5.1 (10 Sep 2011) +- Update FAQ entry on shared builds (#13) +- Avoid symbolic argument to chmod in Makefile.in +- Fix bug and add consts in contrib/puff [Oberhumer] +- Update contrib/puff/zeros.raw test file to have all block types +- Add full coverage test for puff in contrib/puff/Makefile +- Fix static-only-build install in Makefile.in +- Fix bug in unzGetCurrentFileInfo() in contrib/minizip [Kuno] +- Add libz.a dependency to shared in Makefile.in for parallel builds +- Spell out "number" (instead of "nb") in zlib.h for total_in, total_out +- Replace $(...) with `...` in configure for non-bash sh [Bowler] +- Add darwin* to Darwin* and solaris* to SunOS\ 5* in configure [Groffen] +- Add solaris* to Linux* in configure to allow gcc use [Groffen] +- Add *bsd* to Linux* case in configure [Bar-Lev] +- Add inffast.obj to dependencies in win32/Makefile.msc +- Correct spelling error in deflate.h [Kohler] +- Change libzdll.a again to libz.dll.a (!) in win32/Makefile.gcc +- Add test to configure for GNU C looking for gcc in output of $cc -v +- Add zlib.pc generation to win32/Makefile.gcc [Weigelt] +- Fix bug in zlib.h for _FILE_OFFSET_BITS set and _LARGEFILE64_SOURCE not +- Add comment in zlib.h that adler32_combine with len2 < 0 makes no sense +- Make NO_DIVIDE option in adler32.c much faster (thanks to John Reiser) +- Make stronger test in zconf.h to include unistd.h for LFS +- Apply Darwin patches for 64-bit file offsets to contrib/minizip [Slack] +- Fix zlib.h LFS support when Z_PREFIX used +- Add updated as400 support (removed from old) [Monnerat] +- Avoid deflate sensitivity to volatile input data +- Avoid division in adler32_combine for NO_DIVIDE +- Clarify the use of Z_FINISH with deflateBound() amount of space +- Set binary for output file in puff.c +- Use u4 type for crc_table to avoid conversion warnings +- Apply casts in zlib.h to avoid conversion warnings +- Add OF to prototypes for adler32_combine_ and crc32_combine_ [Miller] +- Improve inflateSync() documentation to note indeterminancy +- Add deflatePending() function to return the amount of pending output +- Correct the spelling of "specification" in FAQ [Randers-Pehrson] +- Add a check in configure for stdarg.h, use for gzprintf() +- Check that pointers fit in ints when gzprint() compiled old style +- Add dummy name before $(SHAREDLIBV) in Makefile [Bar-Lev, Bowler] +- Delete line in configure that adds -L. libz.a to LDFLAGS [Weigelt] +- Add debug records in assmebler code [Londer] +- Update RFC references to use http://tools.ietf.org/html/... [Li] +- Add --archs option, use of libtool to configure for Mac OS X [Borstel] + +Changes in 1.2.5 (19 Apr 2010) +- Disable visibility attribute in win32/Makefile.gcc [Bar-Lev] +- Default to libdir as sharedlibdir in configure [Nieder] +- Update copyright dates on modified source files +- Update trees.c to be able to generate modified trees.h +- Exit configure for MinGW, suggesting win32/Makefile.gcc +- Check for NULL path in gz_open [Homurlu] + +Changes in 1.2.4.5 (18 Apr 2010) +- Set sharedlibdir in configure [Torok] +- Set LDFLAGS in Makefile.in [Bar-Lev] +- Avoid mkdir objs race condition in Makefile.in [Bowler] +- Add ZLIB_INTERNAL in front of internal inter-module functions and arrays +- Define ZLIB_INTERNAL to hide internal functions and arrays for GNU C +- Don't use hidden attribute when it is a warning generator (e.g. Solaris) + +Changes in 1.2.4.4 (18 Apr 2010) +- Fix CROSS_PREFIX executable testing, CHOST extract, mingw* [Torok] +- Undefine _LARGEFILE64_SOURCE in zconf.h if it is zero, but not if empty +- Try to use bash or ksh regardless of functionality of /bin/sh +- Fix configure incompatibility with NetBSD sh +- Remove attempt to run under bash or ksh since have better NetBSD fix +- Fix win32/Makefile.gcc for MinGW [Bar-Lev] +- Add diagnostic messages when using CROSS_PREFIX in configure +- Added --sharedlibdir option to configure [Weigelt] +- Use hidden visibility attribute when available [Frysinger] + +Changes in 1.2.4.3 (10 Apr 2010) +- Only use CROSS_PREFIX in configure for ar and ranlib if they exist +- Use CROSS_PREFIX for nm [Bar-Lev] +- Assume _LARGEFILE64_SOURCE defined is equivalent to true +- Avoid use of undefined symbols in #if with && and || +- Make *64 prototypes in gzguts.h consistent with functions +- Add -shared load option for MinGW in configure [Bowler] +- Move z_off64_t to public interface, use instead of off64_t +- Remove ! from shell test in configure (not portable to Solaris) +- Change +0 macro tests to -0 for possibly increased portability + +Changes in 1.2.4.2 (9 Apr 2010) +- Add consistent carriage returns to readme.txt's in masmx86 and masmx64 +- Really provide prototypes for *64 functions when building without LFS +- Only define unlink() in minigzip.c if unistd.h not included +- Update README to point to contrib/vstudio project files +- Move projects/vc6 to old/ and remove projects/ +- Include stdlib.h in minigzip.c for setmode() definition under WinCE +- Clean up assembler builds in win32/Makefile.msc [Rowe] +- Include sys/types.h for Microsoft for off_t definition +- Fix memory leak on error in gz_open() +- Symbolize nm as $NM in configure [Weigelt] +- Use TEST_LDSHARED instead of LDSHARED to link test programs [Weigelt] +- Add +0 to _FILE_OFFSET_BITS and _LFS64_LARGEFILE in case not defined +- Fix bug in gzeof() to take into account unused input data +- Avoid initialization of structures with variables in puff.c +- Updated win32/README-WIN32.txt [Rowe] + +Changes in 1.2.4.1 (28 Mar 2010) +- Remove the use of [a-z] constructs for sed in configure [gentoo 310225] +- Remove $(SHAREDLIB) from LIBS in Makefile.in [Creech] +- Restore "for debugging" comment on sprintf() in gzlib.c +- Remove fdopen for MVS from gzguts.h +- Put new README-WIN32.txt in win32 [Rowe] +- Add check for shell to configure and invoke another shell if needed +- Fix big fat stinking bug in gzseek() on uncompressed files +- Remove vestigial F_OPEN64 define in zutil.h +- Set and check the value of _LARGEFILE_SOURCE and _LARGEFILE64_SOURCE +- Avoid errors on non-LFS systems when applications define LFS macros +- Set EXE to ".exe" in configure for MINGW [Kahle] +- Match crc32() in crc32.c exactly to the prototype in zlib.h [Sherrill] +- Add prefix for cross-compilation in win32/makefile.gcc [Bar-Lev] +- Add DLL install in win32/makefile.gcc [Bar-Lev] +- Allow Linux* or linux* from uname in configure [Bar-Lev] +- Allow ldconfig to be redefined in configure and Makefile.in [Bar-Lev] +- Add cross-compilation prefixes to configure [Bar-Lev] +- Match type exactly in gz_load() invocation in gzread.c +- Match type exactly of zcalloc() in zutil.c to zlib.h alloc_func +- Provide prototypes for *64 functions when building zlib without LFS +- Don't use -lc when linking shared library on MinGW +- Remove errno.h check in configure and vestigial errno code in zutil.h + +Changes in 1.2.4 (14 Mar 2010) +- Fix VER3 extraction in configure for no fourth subversion +- Update zlib.3, add docs to Makefile.in to make .pdf out of it +- Add zlib.3.pdf to distribution +- Don't set error code in gzerror() if passed pointer is NULL +- Apply destination directory fixes to CMakeLists.txt [Lowman] +- Move #cmakedefine's to a new zconf.in.cmakein +- Restore zconf.h for builds that don't use configure or cmake +- Add distclean to dummy Makefile for convenience +- Update and improve INDEX, README, and FAQ +- Update CMakeLists.txt for the return of zconf.h [Lowman] +- Update contrib/vstudio/vc9 and vc10 [Vollant] +- Change libz.dll.a back to libzdll.a in win32/Makefile.gcc +- Apply license and readme changes to contrib/asm686 [Raiter] +- Check file name lengths and add -c option in minigzip.c [Li] +- Update contrib/amd64 and contrib/masmx86/ [Vollant] +- Avoid use of "eof" parameter in trees.c to not shadow library variable +- Update make_vms.com for removal of zlibdefs.h [Zinser] +- Update assembler code and vstudio projects in contrib [Vollant] +- Remove outdated assembler code contrib/masm686 and contrib/asm586 +- Remove old vc7 and vc8 from contrib/vstudio +- Update win32/Makefile.msc, add ZLIB_VER_SUBREVISION [Rowe] +- Fix memory leaks in gzclose_r() and gzclose_w(), file leak in gz_open() +- Add contrib/gcc_gvmat64 for longest_match and inflate_fast [Vollant] +- Remove *64 functions from win32/zlib.def (they're not 64-bit yet) +- Fix bug in void-returning vsprintf() case in gzwrite.c +- Fix name change from inflate.h in contrib/inflate86/inffas86.c +- Check if temporary file exists before removing in make_vms.com [Zinser] +- Fix make install and uninstall for --static option +- Fix usage of _MSC_VER in gzguts.h and zutil.h [Truta] +- Update readme.txt in contrib/masmx64 and masmx86 to assemble + +Changes in 1.2.3.9 (21 Feb 2010) +- Expunge gzio.c +- Move as400 build information to old +- Fix updates in contrib/minizip and contrib/vstudio +- Add const to vsnprintf test in configure to avoid warnings [Weigelt] +- Delete zconf.h (made by configure) [Weigelt] +- Change zconf.in.h to zconf.h.in per convention [Weigelt] +- Check for NULL buf in gzgets() +- Return empty string for gzgets() with len == 1 (like fgets()) +- Fix description of gzgets() in zlib.h for end-of-file, NULL return +- Update minizip to 1.1 [Vollant] +- Avoid MSVC loss of data warnings in gzread.c, gzwrite.c +- Note in zlib.h that gzerror() should be used to distinguish from EOF +- Remove use of snprintf() from gzlib.c +- Fix bug in gzseek() +- Update contrib/vstudio, adding vc9 and vc10 [Kuno, Vollant] +- Fix zconf.h generation in CMakeLists.txt [Lowman] +- Improve comments in zconf.h where modified by configure + +Changes in 1.2.3.8 (13 Feb 2010) +- Clean up text files (tabs, trailing whitespace, etc.) [Oberhumer] +- Use z_off64_t in gz_zero() and gz_skip() to match state->skip +- Avoid comparison problem when sizeof(int) == sizeof(z_off64_t) +- Revert to Makefile.in from 1.2.3.6 (live with the clutter) +- Fix missing error return in gzflush(), add zlib.h note +- Add *64 functions to zlib.map [Levin] +- Fix signed/unsigned comparison in gz_comp() +- Use SFLAGS when testing shared linking in configure +- Add --64 option to ./configure to use -m64 with gcc +- Fix ./configure --help to correctly name options +- Have make fail if a test fails [Levin] +- Avoid buffer overrun in contrib/masmx64/gvmat64.asm [Simpson] +- Remove assembler object files from contrib + +Changes in 1.2.3.7 (24 Jan 2010) +- Always gzopen() with O_LARGEFILE if available +- Fix gzdirect() to work immediately after gzopen() or gzdopen() +- Make gzdirect() more precise when the state changes while reading +- Improve zlib.h documentation in many places +- Catch memory allocation failure in gz_open() +- Complete close operation if seek forward in gzclose_w() fails +- Return Z_ERRNO from gzclose_r() if close() fails +- Return Z_STREAM_ERROR instead of EOF for gzclose() being passed NULL +- Return zero for gzwrite() errors to match zlib.h description +- Return -1 on gzputs() error to match zlib.h description +- Add zconf.in.h to allow recovery from configure modification [Weigelt] +- Fix static library permissions in Makefile.in [Weigelt] +- Avoid warnings in configure tests that hide functionality [Weigelt] +- Add *BSD and DragonFly to Linux case in configure [gentoo 123571] +- Change libzdll.a to libz.dll.a in win32/Makefile.gcc [gentoo 288212] +- Avoid access of uninitialized data for first inflateReset2 call [Gomes] +- Keep object files in subdirectories to reduce the clutter somewhat +- Remove default Makefile and zlibdefs.h, add dummy Makefile +- Add new external functions to Z_PREFIX, remove duplicates, z_z_ -> z_ +- Remove zlibdefs.h completely -- modify zconf.h instead + +Changes in 1.2.3.6 (17 Jan 2010) +- Avoid void * arithmetic in gzread.c and gzwrite.c +- Make compilers happier with const char * for gz_error message +- Avoid unused parameter warning in inflate.c +- Avoid signed-unsigned comparison warning in inflate.c +- Indent #pragma's for traditional C +- Fix usage of strwinerror() in glib.c, change to gz_strwinerror() +- Correct email address in configure for system options +- Update make_vms.com and add make_vms.com to contrib/minizip [Zinser] +- Update zlib.map [Brown] +- Fix Makefile.in for Solaris 10 make of example64 and minizip64 [Torok] +- Apply various fixes to CMakeLists.txt [Lowman] +- Add checks on len in gzread() and gzwrite() +- Add error message for no more room for gzungetc() +- Remove zlib version check in gzwrite() +- Defer compression of gzprintf() result until need to +- Use snprintf() in gzdopen() if available +- Remove USE_MMAP configuration determination (only used by minigzip) +- Remove examples/pigz.c (available separately) +- Update examples/gun.c to 1.6 + +Changes in 1.2.3.5 (8 Jan 2010) +- Add space after #if in zutil.h for some compilers +- Fix relatively harmless bug in deflate_fast() [Exarevsky] +- Fix same problem in deflate_slow() +- Add $(SHAREDLIBV) to LIBS in Makefile.in [Brown] +- Add deflate_rle() for faster Z_RLE strategy run-length encoding +- Add deflate_huff() for faster Z_HUFFMAN_ONLY encoding +- Change name of "write" variable in inffast.c to avoid library collisions +- Fix premature EOF from gzread() in gzio.c [Brown] +- Use zlib header window size if windowBits is 0 in inflateInit2() +- Remove compressBound() call in deflate.c to avoid linking compress.o +- Replace use of errno in gz* with functions, support WinCE [Alves] +- Provide alternative to perror() in minigzip.c for WinCE [Alves] +- Don't use _vsnprintf on later versions of MSVC [Lowman] +- Add CMake build script and input file [Lowman] +- Update contrib/minizip to 1.1 [Svensson, Vollant] +- Moved nintendods directory from contrib to . +- Replace gzio.c with a new set of routines with the same functionality +- Add gzbuffer(), gzoffset(), gzclose_r(), gzclose_w() as part of above +- Update contrib/minizip to 1.1b +- Change gzeof() to return 0 on error instead of -1 to agree with zlib.h + +Changes in 1.2.3.4 (21 Dec 2009) +- Use old school .SUFFIXES in Makefile.in for FreeBSD compatibility +- Update comments in configure and Makefile.in for default --shared +- Fix test -z's in configure [Marquess] +- Build examplesh and minigzipsh when not testing +- Change NULL's to Z_NULL's in deflate.c and in comments in zlib.h +- Import LDFLAGS from the environment in configure +- Fix configure to populate SFLAGS with discovered CFLAGS options +- Adapt make_vms.com to the new Makefile.in [Zinser] +- Add zlib2ansi script for C++ compilation [Marquess] +- Add _FILE_OFFSET_BITS=64 test to make test (when applicable) +- Add AMD64 assembler code for longest match to contrib [Teterin] +- Include options from $SFLAGS when doing $LDSHARED +- Simplify 64-bit file support by introducing z_off64_t type +- Make shared object files in objs directory to work around old Sun cc +- Use only three-part version number for Darwin shared compiles +- Add rc option to ar in Makefile.in for when ./configure not run +- Add -WI,-rpath,. to LDFLAGS for OSF 1 V4* +- Set LD_LIBRARYN32_PATH for SGI IRIX shared compile +- Protect against _FILE_OFFSET_BITS being defined when compiling zlib +- Rename Makefile.in targets allstatic to static and allshared to shared +- Fix static and shared Makefile.in targets to be independent +- Correct error return bug in gz_open() by setting state [Brown] +- Put spaces before ;;'s in configure for better sh compatibility +- Add pigz.c (parallel implementation of gzip) to examples/ +- Correct constant in crc32.c to UL [Leventhal] +- Reject negative lengths in crc32_combine() +- Add inflateReset2() function to work like inflateEnd()/inflateInit2() +- Include sys/types.h for _LARGEFILE64_SOURCE [Brown] +- Correct typo in doc/algorithm.txt [Janik] +- Fix bug in adler32_combine() [Zhu] +- Catch missing-end-of-block-code error in all inflates and in puff + Assures that random input to inflate eventually results in an error +- Added enough.c (calculation of ENOUGH for inftrees.h) to examples/ +- Update ENOUGH and its usage to reflect discovered bounds +- Fix gzerror() error report on empty input file [Brown] +- Add ush casts in trees.c to avoid pedantic runtime errors +- Fix typo in zlib.h uncompress() description [Reiss] +- Correct inflate() comments with regard to automatic header detection +- Remove deprecation comment on Z_PARTIAL_FLUSH (it stays) +- Put new version of gzlog (2.0) in examples with interruption recovery +- Add puff compile option to permit invalid distance-too-far streams +- Add puff TEST command options, ability to read piped input +- Prototype the *64 functions in zlib.h when _FILE_OFFSET_BITS == 64, but + _LARGEFILE64_SOURCE not defined +- Fix Z_FULL_FLUSH to truly erase the past by resetting s->strstart +- Fix deflateSetDictionary() to use all 32K for output consistency +- Remove extraneous #define MIN_LOOKAHEAD in deflate.c (in deflate.h) +- Clear bytes after deflate lookahead to avoid use of uninitialized data +- Change a limit in inftrees.c to be more transparent to Coverity Prevent +- Update win32/zlib.def with exported symbols from zlib.h +- Correct spelling errors in zlib.h [Willem, Sobrado] +- Allow Z_BLOCK for deflate() to force a new block +- Allow negative bits in inflatePrime() to delete existing bit buffer +- Add Z_TREES flush option to inflate() to return at end of trees +- Add inflateMark() to return current state information for random access +- Add Makefile for NintendoDS to contrib [Costa] +- Add -w in configure compile tests to avoid spurious warnings [Beucler] +- Fix typos in zlib.h comments for deflateSetDictionary() +- Fix EOF detection in transparent gzread() [Maier] + +Changes in 1.2.3.3 (2 October 2006) +- Make --shared the default for configure, add a --static option +- Add compile option to permit invalid distance-too-far streams +- Add inflateUndermine() function which is required to enable above +- Remove use of "this" variable name for C++ compatibility [Marquess] +- Add testing of shared library in make test, if shared library built +- Use ftello() and fseeko() if available instead of ftell() and fseek() +- Provide two versions of all functions that use the z_off_t type for + binary compatibility -- a normal version and a 64-bit offset version, + per the Large File Support Extension when _LARGEFILE64_SOURCE is + defined; use the 64-bit versions by default when _FILE_OFFSET_BITS + is defined to be 64 +- Add a --uname= option to configure to perhaps help with cross-compiling + +Changes in 1.2.3.2 (3 September 2006) +- Turn off silly Borland warnings [Hay] +- Use off64_t and define _LARGEFILE64_SOURCE when present +- Fix missing dependency on inffixed.h in Makefile.in +- Rig configure --shared to build both shared and static [Teredesai, Truta] +- Remove zconf.in.h and instead create a new zlibdefs.h file +- Fix contrib/minizip/unzip.c non-encrypted after encrypted [Vollant] +- Add treebuild.xml (see http://treebuild.metux.de/) [Weigelt] + +Changes in 1.2.3.1 (16 August 2006) +- Add watcom directory with OpenWatcom make files [Daniel] +- Remove #undef of FAR in zconf.in.h for MVS [Fedtke] +- Update make_vms.com [Zinser] +- Use -fPIC for shared build in configure [Teredesai, Nicholson] +- Use only major version number for libz.so on IRIX and OSF1 [Reinholdtsen] +- Use fdopen() (not _fdopen()) for Interix in zutil.h [BŠck] +- Add some FAQ entries about the contrib directory +- Update the MVS question in the FAQ +- Avoid extraneous reads after EOF in gzio.c [Brown] +- Correct spelling of "successfully" in gzio.c [Randers-Pehrson] +- Add comments to zlib.h about gzerror() usage [Brown] +- Set extra flags in gzip header in gzopen() like deflate() does +- Make configure options more compatible with double-dash conventions + [Weigelt] +- Clean up compilation under Solaris SunStudio cc [Rowe, Reinholdtsen] +- Fix uninstall target in Makefile.in [Truta] +- Add pkgconfig support [Weigelt] +- Use $(DESTDIR) macro in Makefile.in [Reinholdtsen, Weigelt] +- Replace set_data_type() with a more accurate detect_data_type() in + trees.c, according to the txtvsbin.txt document [Truta] +- Swap the order of #include and #include "zlib.h" in + gzio.c, example.c and minigzip.c [Truta] +- Shut up annoying VS2005 warnings about standard C deprecation [Rowe, + Truta] (where?) +- Fix target "clean" from win32/Makefile.bor [Truta] +- Create .pdb and .manifest files in win32/makefile.msc [Ziegler, Rowe] +- Update zlib www home address in win32/DLL_FAQ.txt [Truta] +- Update contrib/masmx86/inffas32.asm for VS2005 [Vollant, Van Wassenhove] +- Enable browse info in the "Debug" and "ASM Debug" configurations in + the Visual C++ 6 project, and set (non-ASM) "Debug" as default [Truta] +- Add pkgconfig support [Weigelt] +- Add ZLIB_VER_MAJOR, ZLIB_VER_MINOR and ZLIB_VER_REVISION in zlib.h, + for use in win32/zlib1.rc [Polushin, Rowe, Truta] +- Add a document that explains the new text detection scheme to + doc/txtvsbin.txt [Truta] +- Add rfc1950.txt, rfc1951.txt and rfc1952.txt to doc/ [Truta] +- Move algorithm.txt into doc/ [Truta] +- Synchronize FAQ with website +- Fix compressBound(), was low for some pathological cases [Fearnley] +- Take into account wrapper variations in deflateBound() +- Set examples/zpipe.c input and output to binary mode for Windows +- Update examples/zlib_how.html with new zpipe.c (also web site) +- Fix some warnings in examples/gzlog.c and examples/zran.c (it seems + that gcc became pickier in 4.0) +- Add zlib.map for Linux: "All symbols from zlib-1.1.4 remain + un-versioned, the patch adds versioning only for symbols introduced in + zlib-1.2.0 or later. It also declares as local those symbols which are + not designed to be exported." [Levin] +- Update Z_PREFIX list in zconf.in.h, add --zprefix option to configure +- Do not initialize global static by default in trees.c, add a response + NO_INIT_GLOBAL_POINTERS to initialize them if needed [Marquess] +- Don't use strerror() in gzio.c under WinCE [Yakimov] +- Don't use errno.h in zutil.h under WinCE [Yakimov] +- Move arguments for AR to its usage to allow replacing ar [Marot] +- Add HAVE_VISIBILITY_PRAGMA in zconf.in.h for Mozilla [Randers-Pehrson] +- Improve inflateInit() and inflateInit2() documentation +- Fix structure size comment in inflate.h +- Change configure help option from --h* to --help [Santos] + +Changes in 1.2.3 (18 July 2005) +- Apply security vulnerability fixes to contrib/infback9 as well +- Clean up some text files (carriage returns, trailing space) +- Update testzlib, vstudio, masmx64, and masmx86 in contrib [Vollant] + +Changes in 1.2.2.4 (11 July 2005) +- Add inflatePrime() function for starting inflation at bit boundary +- Avoid some Visual C warnings in deflate.c +- Avoid more silly Visual C warnings in inflate.c and inftrees.c for 64-bit + compile +- Fix some spelling errors in comments [Betts] +- Correct inflateInit2() error return documentation in zlib.h +- Add zran.c example of compressed data random access to examples + directory, shows use of inflatePrime() +- Fix cast for assignments to strm->state in inflate.c and infback.c +- Fix zlibCompileFlags() in zutil.c to use 1L for long shifts [Oberhumer] +- Move declarations of gf2 functions to right place in crc32.c [Oberhumer] +- Add cast in trees.c t avoid a warning [Oberhumer] +- Avoid some warnings in fitblk.c, gun.c, gzjoin.c in examples [Oberhumer] +- Update make_vms.com [Zinser] +- Initialize state->write in inflateReset() since copied in inflate_fast() +- Be more strict on incomplete code sets in inflate_table() and increase + ENOUGH and MAXD -- this repairs a possible security vulnerability for + invalid inflate input. Thanks to Tavis Ormandy and Markus Oberhumer for + discovering the vulnerability and providing test cases. +- Add ia64 support to configure for HP-UX [Smith] +- Add error return to gzread() for format or i/o error [Levin] +- Use malloc.h for OS/2 [Necasek] + +Changes in 1.2.2.3 (27 May 2005) +- Replace 1U constants in inflate.c and inftrees.c for 64-bit compile +- Typecast fread() return values in gzio.c [Vollant] +- Remove trailing space in minigzip.c outmode (VC++ can't deal with it) +- Fix crc check bug in gzread() after gzungetc() [Heiner] +- Add the deflateTune() function to adjust internal compression parameters +- Add a fast gzip decompressor, gun.c, to examples (use of inflateBack) +- Remove an incorrect assertion in examples/zpipe.c +- Add C++ wrapper in infback9.h [Donais] +- Fix bug in inflateCopy() when decoding fixed codes +- Note in zlib.h how much deflateSetDictionary() actually uses +- Remove USE_DICT_HEAD in deflate.c (would mess up inflate if used) +- Add _WIN32_WCE to define WIN32 in zconf.in.h [Spencer] +- Don't include stderr.h or errno.h for _WIN32_WCE in zutil.h [Spencer] +- Add gzdirect() function to indicate transparent reads +- Update contrib/minizip [Vollant] +- Fix compilation of deflate.c when both ASMV and FASTEST [Oberhumer] +- Add casts in crc32.c to avoid warnings [Oberhumer] +- Add contrib/masmx64 [Vollant] +- Update contrib/asm586, asm686, masmx86, testzlib, vstudio [Vollant] + +Changes in 1.2.2.2 (30 December 2004) +- Replace structure assignments in deflate.c and inflate.c with zmemcpy to + avoid implicit memcpy calls (portability for no-library compilation) +- Increase sprintf() buffer size in gzdopen() to allow for large numbers +- Add INFLATE_STRICT to check distances against zlib header +- Improve WinCE errno handling and comments [Chang] +- Remove comment about no gzip header processing in FAQ +- Add Z_FIXED strategy option to deflateInit2() to force fixed trees +- Add updated make_vms.com [Coghlan], update README +- Create a new "examples" directory, move gzappend.c there, add zpipe.c, + fitblk.c, gzlog.[ch], gzjoin.c, and zlib_how.html. +- Add FAQ entry and comments in deflate.c on uninitialized memory access +- Add Solaris 9 make options in configure [Gilbert] +- Allow strerror() usage in gzio.c for STDC +- Fix DecompressBuf in contrib/delphi/ZLib.pas [ManChesTer] +- Update contrib/masmx86/inffas32.asm and gvmat32.asm [Vollant] +- Use z_off_t for adler32_combine() and crc32_combine() lengths +- Make adler32() much faster for small len +- Use OS_CODE in deflate() default gzip header + +Changes in 1.2.2.1 (31 October 2004) +- Allow inflateSetDictionary() call for raw inflate +- Fix inflate header crc check bug for file names and comments +- Add deflateSetHeader() and gz_header structure for custom gzip headers +- Add inflateGetheader() to retrieve gzip headers +- Add crc32_combine() and adler32_combine() functions +- Add alloc_func, free_func, in_func, out_func to Z_PREFIX list +- Use zstreamp consistently in zlib.h (inflate_back functions) +- Remove GUNZIP condition from definition of inflate_mode in inflate.h + and in contrib/inflate86/inffast.S [Truta, Anderson] +- Add support for AMD64 in contrib/inflate86/inffas86.c [Anderson] +- Update projects/README.projects and projects/visualc6 [Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Avoid warning under NO_GZCOMPRESS in gzio.c; fix typo [Truta] +- Deprecate Z_ASCII; use Z_TEXT instead [Truta] +- Use a new algorithm for setting strm->data_type in trees.c [Truta] +- Do not define an exit() prototype in zutil.c unless DEBUG defined +- Remove prototype of exit() from zutil.c, example.c, minigzip.c [Truta] +- Add comment in zlib.h for Z_NO_FLUSH parameter to deflate() +- Fix Darwin build version identification [Peterson] + +Changes in 1.2.2 (3 October 2004) +- Update zlib.h comments on gzip in-memory processing +- Set adler to 1 in inflateReset() to support Java test suite [Walles] +- Add contrib/dotzlib [Ravn] +- Update win32/DLL_FAQ.txt [Truta] +- Update contrib/minizip [Vollant] +- Move contrib/visual-basic.txt to old/ [Truta] +- Fix assembler builds in projects/visualc6/ [Truta] + +Changes in 1.2.1.2 (9 September 2004) +- Update INDEX file +- Fix trees.c to update strm->data_type (no one ever noticed!) +- Fix bug in error case in inflate.c, infback.c, and infback9.c [Brown] +- Add "volatile" to crc table flag declaration (for DYNAMIC_CRC_TABLE) +- Add limited multitasking protection to DYNAMIC_CRC_TABLE +- Add NO_vsnprintf for VMS in zutil.h [Mozilla] +- Don't declare strerror() under VMS [Mozilla] +- Add comment to DYNAMIC_CRC_TABLE to use get_crc_table() to initialize +- Update contrib/ada [Anisimkov] +- Update contrib/minizip [Vollant] +- Fix configure to not hardcode directories for Darwin [Peterson] +- Fix gzio.c to not return error on empty files [Brown] +- Fix indentation; update version in contrib/delphi/ZLib.pas and + contrib/pascal/zlibpas.pas [Truta] +- Update mkasm.bat in contrib/masmx86 [Truta] +- Update contrib/untgz [Truta] +- Add projects/README.projects [Truta] +- Add project for MS Visual C++ 6.0 in projects/visualc6 [Cadieux, Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Update list of Z_PREFIX symbols in zconf.h [Randers-Pehrson, Truta] +- Remove an unnecessary assignment to curr in inftrees.c [Truta] +- Add OS/2 to exe builds in configure [Poltorak] +- Remove err dummy parameter in zlib.h [Kientzle] + +Changes in 1.2.1.1 (9 January 2004) +- Update email address in README +- Several FAQ updates +- Fix a big fat bug in inftrees.c that prevented decoding valid + dynamic blocks with only literals and no distance codes -- + Thanks to "Hot Emu" for the bug report and sample file +- Add a note to puff.c on no distance codes case. + +Changes in 1.2.1 (17 November 2003) +- Remove a tab in contrib/gzappend/gzappend.c +- Update some interfaces in contrib for new zlib functions +- Update zlib version number in some contrib entries +- Add Windows CE definition for ptrdiff_t in zutil.h [Mai, Truta] +- Support shared libraries on Hurd and KFreeBSD [Brown] +- Fix error in NO_DIVIDE option of adler32.c + +Changes in 1.2.0.8 (4 November 2003) +- Update version in contrib/delphi/ZLib.pas and contrib/pascal/zlibpas.pas +- Add experimental NO_DIVIDE #define in adler32.c + - Possibly faster on some processors (let me know if it is) +- Correct Z_BLOCK to not return on first inflate call if no wrap +- Fix strm->data_type on inflate() return to correctly indicate EOB +- Add deflatePrime() function for appending in the middle of a byte +- Add contrib/gzappend for an example of appending to a stream +- Update win32/DLL_FAQ.txt [Truta] +- Delete Turbo C comment in README [Truta] +- Improve some indentation in zconf.h [Truta] +- Fix infinite loop on bad input in configure script [Church] +- Fix gzeof() for concatenated gzip files [Johnson] +- Add example to contrib/visual-basic.txt [Michael B.] +- Add -p to mkdir's in Makefile.in [vda] +- Fix configure to properly detect presence or lack of printf functions +- Add AS400 support [Monnerat] +- Add a little Cygwin support [Wilson] + +Changes in 1.2.0.7 (21 September 2003) +- Correct some debug formats in contrib/infback9 +- Cast a type in a debug statement in trees.c +- Change search and replace delimiter in configure from % to # [Beebe] +- Update contrib/untgz to 0.2 with various fixes [Truta] +- Add build support for Amiga [Nikl] +- Remove some directories in old that have been updated to 1.2 +- Add dylib building for Mac OS X in configure and Makefile.in +- Remove old distribution stuff from Makefile +- Update README to point to DLL_FAQ.txt, and add comment on Mac OS X +- Update links in README + +Changes in 1.2.0.6 (13 September 2003) +- Minor FAQ updates +- Update contrib/minizip to 1.00 [Vollant] +- Remove test of gz functions in example.c when GZ_COMPRESS defined [Truta] +- Update POSTINC comment for 68060 [Nikl] +- Add contrib/infback9 with deflate64 decoding (unsupported) +- For MVS define NO_vsnprintf and undefine FAR [van Burik] +- Add pragma for fdopen on MVS [van Burik] + +Changes in 1.2.0.5 (8 September 2003) +- Add OF to inflateBackEnd() declaration in zlib.h +- Remember start when using gzdopen in the middle of a file +- Use internal off_t counters in gz* functions to properly handle seeks +- Perform more rigorous check for distance-too-far in inffast.c +- Add Z_BLOCK flush option to return from inflate at block boundary +- Set strm->data_type on return from inflate + - Indicate bits unused, if at block boundary, and if in last block +- Replace size_t with ptrdiff_t in crc32.c, and check for correct size +- Add condition so old NO_DEFLATE define still works for compatibility +- FAQ update regarding the Windows DLL [Truta] +- INDEX update: add qnx entry, remove aix entry [Truta] +- Install zlib.3 into mandir [Wilson] +- Move contrib/zlib_dll_FAQ.txt to win32/DLL_FAQ.txt; update [Truta] +- Adapt the zlib interface to the new DLL convention guidelines [Truta] +- Introduce ZLIB_WINAPI macro to allow the export of functions using + the WINAPI calling convention, for Visual Basic [Vollant, Truta] +- Update msdos and win32 scripts and makefiles [Truta] +- Export symbols by name, not by ordinal, in win32/zlib.def [Truta] +- Add contrib/ada [Anisimkov] +- Move asm files from contrib/vstudio/vc70_32 to contrib/asm386 [Truta] +- Rename contrib/asm386 to contrib/masmx86 [Truta, Vollant] +- Add contrib/masm686 [Truta] +- Fix offsets in contrib/inflate86 and contrib/masmx86/inffas32.asm + [Truta, Vollant] +- Update contrib/delphi; rename to contrib/pascal; add example [Truta] +- Remove contrib/delphi2; add a new contrib/delphi [Truta] +- Avoid inclusion of the nonstandard in contrib/iostream, + and fix some method prototypes [Truta] +- Fix the ZCR_SEED2 constant to avoid warnings in contrib/minizip + [Truta] +- Avoid the use of backslash (\) in contrib/minizip [Vollant] +- Fix file time handling in contrib/untgz; update makefiles [Truta] +- Update contrib/vstudio/vc70_32 to comply with the new DLL guidelines + [Vollant] +- Remove contrib/vstudio/vc15_16 [Vollant] +- Rename contrib/vstudio/vc70_32 to contrib/vstudio/vc7 [Truta] +- Update README.contrib [Truta] +- Invert the assignment order of match_head and s->prev[...] in + INSERT_STRING [Truta] +- Compare TOO_FAR with 32767 instead of 32768, to avoid 16-bit warnings + [Truta] +- Compare function pointers with 0, not with NULL or Z_NULL [Truta] +- Fix prototype of syncsearch in inflate.c [Truta] +- Introduce ASMINF macro to be enabled when using an ASM implementation + of inflate_fast [Truta] +- Change NO_DEFLATE to NO_GZCOMPRESS [Truta] +- Modify test_gzio in example.c to take a single file name as a + parameter [Truta] +- Exit the example.c program if gzopen fails [Truta] +- Add type casts around strlen in example.c [Truta] +- Remove casting to sizeof in minigzip.c; give a proper type + to the variable compared with SUFFIX_LEN [Truta] +- Update definitions of STDC and STDC99 in zconf.h [Truta] +- Synchronize zconf.h with the new Windows DLL interface [Truta] +- Use SYS16BIT instead of __32BIT__ to distinguish between + 16- and 32-bit platforms [Truta] +- Use far memory allocators in small 16-bit memory models for + Turbo C [Truta] +- Add info about the use of ASMV, ASMINF and ZLIB_WINAPI in + zlibCompileFlags [Truta] +- Cygwin has vsnprintf [Wilson] +- In Windows16, OS_CODE is 0, as in MSDOS [Truta] +- In Cygwin, OS_CODE is 3 (Unix), not 11 (Windows32) [Wilson] + +Changes in 1.2.0.4 (10 August 2003) +- Minor FAQ updates +- Be more strict when checking inflateInit2's windowBits parameter +- Change NO_GUNZIP compile option to NO_GZIP to cover deflate as well +- Add gzip wrapper option to deflateInit2 using windowBits +- Add updated QNX rule in configure and qnx directory [Bonnefoy] +- Make inflate distance-too-far checks more rigorous +- Clean up FAR usage in inflate +- Add casting to sizeof() in gzio.c and minigzip.c + +Changes in 1.2.0.3 (19 July 2003) +- Fix silly error in gzungetc() implementation [Vollant] +- Update contrib/minizip and contrib/vstudio [Vollant] +- Fix printf format in example.c +- Correct cdecl support in zconf.in.h [Anisimkov] +- Minor FAQ updates + +Changes in 1.2.0.2 (13 July 2003) +- Add ZLIB_VERNUM in zlib.h for numerical preprocessor comparisons +- Attempt to avoid warnings in crc32.c for pointer-int conversion +- Add AIX to configure, remove aix directory [Bakker] +- Add some casts to minigzip.c +- Improve checking after insecure sprintf() or vsprintf() calls +- Remove #elif's from crc32.c +- Change leave label to inf_leave in inflate.c and infback.c to avoid + library conflicts +- Remove inflate gzip decoding by default--only enable gzip decoding by + special request for stricter backward compatibility +- Add zlibCompileFlags() function to return compilation information +- More typecasting in deflate.c to avoid warnings +- Remove leading underscore from _Capital #defines [Truta] +- Fix configure to link shared library when testing +- Add some Windows CE target adjustments [Mai] +- Remove #define ZLIB_DLL in zconf.h [Vollant] +- Add zlib.3 [Rodgers] +- Update RFC URL in deflate.c and algorithm.txt [Mai] +- Add zlib_dll_FAQ.txt to contrib [Truta] +- Add UL to some constants [Truta] +- Update minizip and vstudio [Vollant] +- Remove vestigial NEED_DUMMY_RETURN from zconf.in.h +- Expand use of NO_DUMMY_DECL to avoid all dummy structures +- Added iostream3 to contrib [Schwardt] +- Replace rewind() with fseek() for WinCE [Truta] +- Improve setting of zlib format compression level flags + - Report 0 for huffman and rle strategies and for level == 0 or 1 + - Report 2 only for level == 6 +- Only deal with 64K limit when necessary at compile time [Truta] +- Allow TOO_FAR check to be turned off at compile time [Truta] +- Add gzclearerr() function [Souza] +- Add gzungetc() function + +Changes in 1.2.0.1 (17 March 2003) +- Add Z_RLE strategy for run-length encoding [Truta] + - When Z_RLE requested, restrict matches to distance one + - Update zlib.h, minigzip.c, gzopen(), gzdopen() for Z_RLE +- Correct FASTEST compilation to allow level == 0 +- Clean up what gets compiled for FASTEST +- Incorporate changes to zconf.in.h [Vollant] + - Refine detection of Turbo C need for dummy returns + - Refine ZLIB_DLL compilation + - Include additional header file on VMS for off_t typedef +- Try to use _vsnprintf where it supplants vsprintf [Vollant] +- Add some casts in inffast.c +- Enchance comments in zlib.h on what happens if gzprintf() tries to + write more than 4095 bytes before compression +- Remove unused state from inflateBackEnd() +- Remove exit(0) from minigzip.c, example.c +- Get rid of all those darn tabs +- Add "check" target to Makefile.in that does the same thing as "test" +- Add "mostlyclean" and "maintainer-clean" targets to Makefile.in +- Update contrib/inflate86 [Anderson] +- Update contrib/testzlib, contrib/vstudio, contrib/minizip [Vollant] +- Add msdos and win32 directories with makefiles [Truta] +- More additions and improvements to the FAQ + +Changes in 1.2.0 (9 March 2003) +- New and improved inflate code + - About 20% faster + - Does not allocate 32K window unless and until needed + - Automatically detects and decompresses gzip streams + - Raw inflate no longer needs an extra dummy byte at end + - Added inflateBack functions using a callback interface--even faster + than inflate, useful for file utilities (gzip, zip) + - Added inflateCopy() function to record state for random access on + externally generated deflate streams (e.g. in gzip files) + - More readable code (I hope) +- New and improved crc32() + - About 50% faster, thanks to suggestions from Rodney Brown +- Add deflateBound() and compressBound() functions +- Fix memory leak in deflateInit2() +- Permit setting dictionary for raw deflate (for parallel deflate) +- Fix const declaration for gzwrite() +- Check for some malloc() failures in gzio.c +- Fix bug in gzopen() on single-byte file 0x1f +- Fix bug in gzread() on concatenated file with 0x1f at end of buffer + and next buffer doesn't start with 0x8b +- Fix uncompress() to return Z_DATA_ERROR on truncated input +- Free memory at end of example.c +- Remove MAX #define in trees.c (conflicted with some libraries) +- Fix static const's in deflate.c, gzio.c, and zutil.[ch] +- Declare malloc() and free() in gzio.c if STDC not defined +- Use malloc() instead of calloc() in zutil.c if int big enough +- Define STDC for AIX +- Add aix/ with approach for compiling shared library on AIX +- Add HP-UX support for shared libraries in configure +- Add OpenUNIX support for shared libraries in configure +- Use $cc instead of gcc to build shared library +- Make prefix directory if needed when installing +- Correct Macintosh avoidance of typedef Byte in zconf.h +- Correct Turbo C memory allocation when under Linux +- Use libz.a instead of -lz in Makefile (assure use of compiled library) +- Update configure to check for snprintf or vsnprintf functions and their + return value, warn during make if using an insecure function +- Fix configure problem with compile-time knowledge of HAVE_UNISTD_H that + is lost when library is used--resolution is to build new zconf.h +- Documentation improvements (in zlib.h): + - Document raw deflate and inflate + - Update RFCs URL + - Point out that zlib and gzip formats are different + - Note that Z_BUF_ERROR is not fatal + - Document string limit for gzprintf() and possible buffer overflow + - Note requirement on avail_out when flushing + - Note permitted values of flush parameter of inflate() +- Add some FAQs (and even answers) to the FAQ +- Add contrib/inflate86/ for x86 faster inflate +- Add contrib/blast/ for PKWare Data Compression Library decompression +- Add contrib/puff/ simple inflate for deflate format description + +Changes in 1.1.4 (11 March 2002) +- ZFREE was repeated on same allocation on some error conditions. + This creates a security problem described in + http://www.zlib.org/advisory-2002-03-11.txt +- Returned incorrect error (Z_MEM_ERROR) on some invalid data +- Avoid accesses before window for invalid distances with inflate window + less than 32K. +- force windowBits > 8 to avoid a bug in the encoder for a window size + of 256 bytes. (A complete fix will be available in 1.1.5). + +Changes in 1.1.3 (9 July 1998) +- fix "an inflate input buffer bug that shows up on rare but persistent + occasions" (Mark) +- fix gzread and gztell for concatenated .gz files (Didier Le Botlan) +- fix gzseek(..., SEEK_SET) in write mode +- fix crc check after a gzeek (Frank Faubert) +- fix miniunzip when the last entry in a zip file is itself a zip file + (J Lillge) +- add contrib/asm586 and contrib/asm686 (Brian Raiter) + See http://www.muppetlabs.com/~breadbox/software/assembly.html +- add support for Delphi 3 in contrib/delphi (Bob Dellaca) +- add support for C++Builder 3 and Delphi 3 in contrib/delphi2 (Davide Moretti) +- do not exit prematurely in untgz if 0 at start of block (Magnus Holmgren) +- use macro EXTERN instead of extern to support DLL for BeOS (Sander Stoks) +- added a FAQ file + +- Support gzdopen on Mac with Metrowerks (Jason Linhart) +- Do not redefine Byte on Mac (Brad Pettit & Jason Linhart) +- define SEEK_END too if SEEK_SET is not defined (Albert Chin-A-Young) +- avoid some warnings with Borland C (Tom Tanner) +- fix a problem in contrib/minizip/zip.c for 16-bit MSDOS (Gilles Vollant) +- emulate utime() for WIN32 in contrib/untgz (Gilles Vollant) +- allow several arguments to configure (Tim Mooney, Frodo Looijaard) +- use libdir and includedir in Makefile.in (Tim Mooney) +- support shared libraries on OSF1 V4 (Tim Mooney) +- remove so_locations in "make clean" (Tim Mooney) +- fix maketree.c compilation error (Glenn, Mark) +- Python interface to zlib now in Python 1.5 (Jeremy Hylton) +- new Makefile.riscos (Rich Walker) +- initialize static descriptors in trees.c for embedded targets (Nick Smith) +- use "foo-gz" in example.c for RISCOS and VMS (Nick Smith) +- add the OS/2 files in Makefile.in too (Andrew Zabolotny) +- fix fdopen and halloc macros for Microsoft C 6.0 (Tom Lane) +- fix maketree.c to allow clean compilation of inffixed.h (Mark) +- fix parameter check in deflateCopy (Gunther Nikl) +- cleanup trees.c, use compressed_len only in debug mode (Christian Spieler) +- Many portability patches by Christian Spieler: + . zutil.c, zutil.h: added "const" for zmem* + . Make_vms.com: fixed some typos + . Make_vms.com: msdos/Makefile.*: removed zutil.h from some dependency lists + . msdos/Makefile.msc: remove "default rtl link library" info from obj files + . msdos/Makefile.*: use model-dependent name for the built zlib library + . msdos/Makefile.emx, nt/Makefile.emx, nt/Makefile.gcc: + new makefiles, for emx (DOS/OS2), emx&rsxnt and mingw32 (Windows 9x / NT) +- use define instead of typedef for Bytef also for MSC small/medium (Tom Lane) +- replace __far with _far for better portability (Christian Spieler, Tom Lane) +- fix test for errno.h in configure (Tim Newsham) + +Changes in 1.1.2 (19 March 98) +- added contrib/minzip, mini zip and unzip based on zlib (Gilles Vollant) + See http://www.winimage.com/zLibDll/unzip.html +- preinitialize the inflate tables for fixed codes, to make the code + completely thread safe (Mark) +- some simplifications and slight speed-up to the inflate code (Mark) +- fix gzeof on non-compressed files (Allan Schrum) +- add -std1 option in configure for OSF1 to fix gzprintf (Martin Mokrejs) +- use default value of 4K for Z_BUFSIZE for 16-bit MSDOS (Tim Wegner + Glenn) +- added os2/Makefile.def and os2/zlib.def (Andrew Zabolotny) +- add shared lib support for UNIX_SV4.2MP (MATSUURA Takanori) +- do not wrap extern "C" around system includes (Tom Lane) +- mention zlib binding for TCL in README (Andreas Kupries) +- added amiga/Makefile.pup for Amiga powerUP SAS/C PPC (Andreas Kleinert) +- allow "make install prefix=..." even after configure (Glenn Randers-Pehrson) +- allow "configure --prefix $HOME" (Tim Mooney) +- remove warnings in example.c and gzio.c (Glenn Randers-Pehrson) +- move Makefile.sas to amiga/Makefile.sas + +Changes in 1.1.1 (27 Feb 98) +- fix macros _tr_tally_* in deflate.h for debug mode (Glenn Randers-Pehrson) +- remove block truncation heuristic which had very marginal effect for zlib + (smaller lit_bufsize than in gzip 1.2.4) and degraded a little the + compression ratio on some files. This also allows inlining _tr_tally for + matches in deflate_slow. +- added msdos/Makefile.w32 for WIN32 Microsoft Visual C++ (Bob Frazier) + +Changes in 1.1.0 (24 Feb 98) +- do not return STREAM_END prematurely in inflate (John Bowler) +- revert to the zlib 1.0.8 inflate to avoid the gcc 2.8.0 bug (Jeremy Buhler) +- compile with -DFASTEST to get compression code optimized for speed only +- in minigzip, try mmap'ing the input file first (Miguel Albrecht) +- increase size of I/O buffers in minigzip.c and gzio.c (not a big gain + on Sun but significant on HP) + +- add a pointer to experimental unzip library in README (Gilles Vollant) +- initialize variable gcc in configure (Chris Herborth) + +Changes in 1.0.9 (17 Feb 1998) +- added gzputs and gzgets functions +- do not clear eof flag in gzseek (Mark Diekhans) +- fix gzseek for files in transparent mode (Mark Diekhans) +- do not assume that vsprintf returns the number of bytes written (Jens Krinke) +- replace EXPORT with ZEXPORT to avoid conflict with other programs +- added compress2 in zconf.h, zlib.def, zlib.dnt +- new asm code from Gilles Vollant in contrib/asm386 +- simplify the inflate code (Mark): + . Replace ZALLOC's in huft_build() with single ZALLOC in inflate_blocks_new() + . ZALLOC the length list in inflate_trees_fixed() instead of using stack + . ZALLOC the value area for huft_build() instead of using stack + . Simplify Z_FINISH check in inflate() + +- Avoid gcc 2.8.0 comparison bug a little differently than zlib 1.0.8 +- in inftrees.c, avoid cc -O bug on HP (Farshid Elahi) +- in zconf.h move the ZLIB_DLL stuff earlier to avoid problems with + the declaration of FAR (Gilles VOllant) +- install libz.so* with mode 755 (executable) instead of 644 (Marc Lehmann) +- read_buf buf parameter of type Bytef* instead of charf* +- zmemcpy parameters are of type Bytef*, not charf* (Joseph Strout) +- do not redeclare unlink in minigzip.c for WIN32 (John Bowler) +- fix check for presence of directories in "make install" (Ian Willis) + +Changes in 1.0.8 (27 Jan 1998) +- fixed offsets in contrib/asm386/gvmat32.asm (Gilles Vollant) +- fix gzgetc and gzputc for big endian systems (Markus Oberhumer) +- added compress2() to allow setting the compression level +- include sys/types.h to get off_t on some systems (Marc Lehmann & QingLong) +- use constant arrays for the static trees in trees.c instead of computing + them at run time (thanks to Ken Raeburn for this suggestion). To create + trees.h, compile with GEN_TREES_H and run "make test". +- check return code of example in "make test" and display result +- pass minigzip command line options to file_compress +- simplifying code of inflateSync to avoid gcc 2.8 bug + +- support CC="gcc -Wall" in configure -s (QingLong) +- avoid a flush caused by ftell in gzopen for write mode (Ken Raeburn) +- fix test for shared library support to avoid compiler warnings +- zlib.lib -> zlib.dll in msdos/zlib.rc (Gilles Vollant) +- check for TARGET_OS_MAC in addition to MACOS (Brad Pettit) +- do not use fdopen for Metrowerks on Mac (Brad Pettit)) +- add checks for gzputc and gzputc in example.c +- avoid warnings in gzio.c and deflate.c (Andreas Kleinert) +- use const for the CRC table (Ken Raeburn) +- fixed "make uninstall" for shared libraries +- use Tracev instead of Trace in infblock.c +- in example.c use correct compressed length for test_sync +- suppress +vnocompatwarnings in configure for HPUX (not always supported) + +Changes in 1.0.7 (20 Jan 1998) +- fix gzseek which was broken in write mode +- return error for gzseek to negative absolute position +- fix configure for Linux (Chun-Chung Chen) +- increase stack space for MSC (Tim Wegner) +- get_crc_table and inflateSyncPoint are EXPORTed (Gilles Vollant) +- define EXPORTVA for gzprintf (Gilles Vollant) +- added man page zlib.3 (Rick Rodgers) +- for contrib/untgz, fix makedir() and improve Makefile + +- check gzseek in write mode in example.c +- allocate extra buffer for seeks only if gzseek is actually called +- avoid signed/unsigned comparisons (Tim Wegner, Gilles Vollant) +- add inflateSyncPoint in zconf.h +- fix list of exported functions in nt/zlib.dnt and mdsos/zlib.def + +Changes in 1.0.6 (19 Jan 1998) +- add functions gzprintf, gzputc, gzgetc, gztell, gzeof, gzseek, gzrewind and + gzsetparams (thanks to Roland Giersig and Kevin Ruland for some of this code) +- Fix a deflate bug occurring only with compression level 0 (thanks to + Andy Buckler for finding this one). +- In minigzip, pass transparently also the first byte for .Z files. +- return Z_BUF_ERROR instead of Z_OK if output buffer full in uncompress() +- check Z_FINISH in inflate (thanks to Marc Schluper) +- Implement deflateCopy (thanks to Adam Costello) +- make static libraries by default in configure, add --shared option. +- move MSDOS or Windows specific files to directory msdos +- suppress the notion of partial flush to simplify the interface + (but the symbol Z_PARTIAL_FLUSH is kept for compatibility with 1.0.4) +- suppress history buffer provided by application to simplify the interface + (this feature was not implemented anyway in 1.0.4) +- next_in and avail_in must be initialized before calling inflateInit or + inflateInit2 +- add EXPORT in all exported functions (for Windows DLL) +- added Makefile.nt (thanks to Stephen Williams) +- added the unsupported "contrib" directory: + contrib/asm386/ by Gilles Vollant + 386 asm code replacing longest_match(). + contrib/iostream/ by Kevin Ruland + A C++ I/O streams interface to the zlib gz* functions + contrib/iostream2/ by Tyge Løvset + Another C++ I/O streams interface + contrib/untgz/ by "Pedro A. Aranda Guti\irrez" + A very simple tar.gz file extractor using zlib + contrib/visual-basic.txt by Carlos Rios + How to use compress(), uncompress() and the gz* functions from VB. +- pass params -f (filtered data), -h (huffman only), -1 to -9 (compression + level) in minigzip (thanks to Tom Lane) + +- use const for rommable constants in deflate +- added test for gzseek and gztell in example.c +- add undocumented function inflateSyncPoint() (hack for Paul Mackerras) +- add undocumented function zError to convert error code to string + (for Tim Smithers) +- Allow compilation of gzio with -DNO_DEFLATE to avoid the compression code. +- Use default memcpy for Symantec MSDOS compiler. +- Add EXPORT keyword for check_func (needed for Windows DLL) +- add current directory to LD_LIBRARY_PATH for "make test" +- create also a link for libz.so.1 +- added support for FUJITSU UXP/DS (thanks to Toshiaki Nomura) +- use $(SHAREDLIB) instead of libz.so in Makefile.in (for HPUX) +- added -soname for Linux in configure (Chun-Chung Chen, +- assign numbers to the exported functions in zlib.def (for Windows DLL) +- add advice in zlib.h for best usage of deflateSetDictionary +- work around compiler bug on Atari (cast Z_NULL in call of s->checkfn) +- allow compilation with ANSI keywords only enabled for TurboC in large model +- avoid "versionString"[0] (Borland bug) +- add NEED_DUMMY_RETURN for Borland +- use variable z_verbose for tracing in debug mode (L. Peter Deutsch). +- allow compilation with CC +- defined STDC for OS/2 (David Charlap) +- limit external names to 8 chars for MVS (Thomas Lund) +- in minigzip.c, use static buffers only for 16-bit systems +- fix suffix check for "minigzip -d foo.gz" +- do not return an error for the 2nd of two consecutive gzflush() (Felix Lee) +- use _fdopen instead of fdopen for MSC >= 6.0 (Thomas Fanslau) +- added makelcc.bat for lcc-win32 (Tom St Denis) +- in Makefile.dj2, use copy and del instead of install and rm (Frank Donahoe) +- Avoid expanded $Id$. Use "rcs -kb" or "cvs admin -kb" to avoid Id expansion. +- check for unistd.h in configure (for off_t) +- remove useless check parameter in inflate_blocks_free +- avoid useless assignment of s->check to itself in inflate_blocks_new +- do not flush twice in gzclose (thanks to Ken Raeburn) +- rename FOPEN as F_OPEN to avoid clash with /usr/include/sys/file.h +- use NO_ERRNO_H instead of enumeration of operating systems with errno.h +- work around buggy fclose on pipes for HP/UX +- support zlib DLL with BORLAND C++ 5.0 (thanks to Glenn Randers-Pehrson) +- fix configure if CC is already equal to gcc + +Changes in 1.0.5 (3 Jan 98) +- Fix inflate to terminate gracefully when fed corrupted or invalid data +- Use const for rommable constants in inflate +- Eliminate memory leaks on error conditions in inflate +- Removed some vestigial code in inflate +- Update web address in README + +Changes in 1.0.4 (24 Jul 96) +- In very rare conditions, deflate(s, Z_FINISH) could fail to produce an EOF + bit, so the decompressor could decompress all the correct data but went + on to attempt decompressing extra garbage data. This affected minigzip too. +- zlibVersion and gzerror return const char* (needed for DLL) +- port to RISCOS (no fdopen, no multiple dots, no unlink, no fileno) +- use z_error only for DEBUG (avoid problem with DLLs) + +Changes in 1.0.3 (2 Jul 96) +- use z_streamp instead of z_stream *, which is now a far pointer in MSDOS + small and medium models; this makes the library incompatible with previous + versions for these models. (No effect in large model or on other systems.) +- return OK instead of BUF_ERROR if previous deflate call returned with + avail_out as zero but there is nothing to do +- added memcmp for non STDC compilers +- define NO_DUMMY_DECL for more Mac compilers (.h files merged incorrectly) +- define __32BIT__ if __386__ or i386 is defined (pb. with Watcom and SCO) +- better check for 16-bit mode MSC (avoids problem with Symantec) + +Changes in 1.0.2 (23 May 96) +- added Windows DLL support +- added a function zlibVersion (for the DLL support) +- fixed declarations using Bytef in infutil.c (pb with MSDOS medium model) +- Bytef is define's instead of typedef'd only for Borland C +- avoid reading uninitialized memory in example.c +- mention in README that the zlib format is now RFC1950 +- updated Makefile.dj2 +- added algorithm.doc + +Changes in 1.0.1 (20 May 96) [1.0 skipped to avoid confusion] +- fix array overlay in deflate.c which sometimes caused bad compressed data +- fix inflate bug with empty stored block +- fix MSDOS medium model which was broken in 0.99 +- fix deflateParams() which could generated bad compressed data. +- Bytef is define'd instead of typedef'ed (work around Borland bug) +- added an INDEX file +- new makefiles for DJGPP (Makefile.dj2), 32-bit Borland (Makefile.b32), + Watcom (Makefile.wat), Amiga SAS/C (Makefile.sas) +- speed up adler32 for modern machines without auto-increment +- added -ansi for IRIX in configure +- static_init_done in trees.c is an int +- define unlink as delete for VMS +- fix configure for QNX +- add configure branch for SCO and HPUX +- avoid many warnings (unused variables, dead assignments, etc...) +- no fdopen for BeOS +- fix the Watcom fix for 32 bit mode (define FAR as empty) +- removed redefinition of Byte for MKWERKS +- work around an MWKERKS bug (incorrect merge of all .h files) + +Changes in 0.99 (27 Jan 96) +- allow preset dictionary shared between compressor and decompressor +- allow compression level 0 (no compression) +- add deflateParams in zlib.h: allow dynamic change of compression level + and compression strategy. +- test large buffers and deflateParams in example.c +- add optional "configure" to build zlib as a shared library +- suppress Makefile.qnx, use configure instead +- fixed deflate for 64-bit systems (detected on Cray) +- fixed inflate_blocks for 64-bit systems (detected on Alpha) +- declare Z_DEFLATED in zlib.h (possible parameter for deflateInit2) +- always return Z_BUF_ERROR when deflate() has nothing to do +- deflateInit and inflateInit are now macros to allow version checking +- prefix all global functions and types with z_ with -DZ_PREFIX +- make falloc completely reentrant (inftrees.c) +- fixed very unlikely race condition in ct_static_init +- free in reverse order of allocation to help memory manager +- use zlib-1.0/* instead of zlib/* inside the tar.gz +- make zlib warning-free with "gcc -O3 -Wall -Wwrite-strings -Wpointer-arith + -Wconversion -Wstrict-prototypes -Wmissing-prototypes" +- allow gzread on concatenated .gz files +- deflateEnd now returns Z_DATA_ERROR if it was premature +- deflate is finally (?) fully deterministic (no matches beyond end of input) +- Document Z_SYNC_FLUSH +- add uninstall in Makefile +- Check for __cpluplus in zlib.h +- Better test in ct_align for partial flush +- avoid harmless warnings for Borland C++ +- initialize hash_head in deflate.c +- avoid warning on fdopen (gzio.c) for HP cc -Aa +- include stdlib.h for STDC compilers +- include errno.h for Cray +- ignore error if ranlib doesn't exist +- call ranlib twice for NeXTSTEP +- use exec_prefix instead of prefix for libz.a +- renamed ct_* as _tr_* to avoid conflict with applications +- clear z->msg in inflateInit2 before any error return +- initialize opaque in example.c, gzio.c, deflate.c and inflate.c +- fixed typo in zconf.h (_GNUC__ => __GNUC__) +- check for WIN32 in zconf.h and zutil.c (avoid farmalloc in 32-bit mode) +- fix typo in Make_vms.com (f$trnlnm -> f$getsyi) +- in fcalloc, normalize pointer if size > 65520 bytes +- don't use special fcalloc for 32 bit Borland C++ +- use STDC instead of __GO32__ to avoid redeclaring exit, calloc, etc... +- use Z_BINARY instead of BINARY +- document that gzclose after gzdopen will close the file +- allow "a" as mode in gzopen. +- fix error checking in gzread +- allow skipping .gz extra-field on pipes +- added reference to Perl interface in README +- put the crc table in FAR data (I dislike more and more the medium model :) +- added get_crc_table +- added a dimension to all arrays (Borland C can't count). +- workaround Borland C bug in declaration of inflate_codes_new & inflate_fast +- guard against multiple inclusion of *.h (for precompiled header on Mac) +- Watcom C pretends to be Microsoft C small model even in 32 bit mode. +- don't use unsized arrays to avoid silly warnings by Visual C++: + warning C4746: 'inflate_mask' : unsized array treated as '__far' + (what's wrong with far data in far model?). +- define enum out of inflate_blocks_state to allow compilation with C++ + +Changes in 0.95 (16 Aug 95) +- fix MSDOS small and medium model (now easier to adapt to any compiler) +- inlined send_bits +- fix the final (:-) bug for deflate with flush (output was correct but + not completely flushed in rare occasions). +- default window size is same for compression and decompression + (it's now sufficient to set MAX_WBITS in zconf.h). +- voidp -> voidpf and voidnp -> voidp (for consistency with other + typedefs and because voidnp was not near in large model). + +Changes in 0.94 (13 Aug 95) +- support MSDOS medium model +- fix deflate with flush (could sometimes generate bad output) +- fix deflateReset (zlib header was incorrectly suppressed) +- added support for VMS +- allow a compression level in gzopen() +- gzflush now calls fflush +- For deflate with flush, flush even if no more input is provided. +- rename libgz.a as libz.a +- avoid complex expression in infcodes.c triggering Turbo C bug +- work around a problem with gcc on Alpha (in INSERT_STRING) +- don't use inline functions (problem with some gcc versions) +- allow renaming of Byte, uInt, etc... with #define. +- avoid warning about (unused) pointer before start of array in deflate.c +- avoid various warnings in gzio.c, example.c, infblock.c, adler32.c, zutil.c +- avoid reserved word 'new' in trees.c + +Changes in 0.93 (25 June 95) +- temporarily disable inline functions +- make deflate deterministic +- give enough lookahead for PARTIAL_FLUSH +- Set binary mode for stdin/stdout in minigzip.c for OS/2 +- don't even use signed char in inflate (not portable enough) +- fix inflate memory leak for segmented architectures + +Changes in 0.92 (3 May 95) +- don't assume that char is signed (problem on SGI) +- Clear bit buffer when starting a stored block +- no memcpy on Pyramid +- suppressed inftest.c +- optimized fill_window, put longest_match inline for gcc +- optimized inflate on stored blocks. +- untabify all sources to simplify patches + +Changes in 0.91 (2 May 95) +- Default MEM_LEVEL is 8 (not 9 for Unix) as documented in zlib.h +- Document the memory requirements in zconf.h +- added "make install" +- fix sync search logic in inflateSync +- deflate(Z_FULL_FLUSH) now works even if output buffer too short +- after inflateSync, don't scare people with just "lo world" +- added support for DJGPP + +Changes in 0.9 (1 May 95) +- don't assume that zalloc clears the allocated memory (the TurboC bug + was Mark's bug after all :) +- let again gzread copy uncompressed data unchanged (was working in 0.71) +- deflate(Z_FULL_FLUSH), inflateReset and inflateSync are now fully implemented +- added a test of inflateSync in example.c +- moved MAX_WBITS to zconf.h because users might want to change that. +- document explicitly that zalloc(64K) on MSDOS must return a normalized + pointer (zero offset) +- added Makefiles for Microsoft C, Turbo C, Borland C++ +- faster crc32() + +Changes in 0.8 (29 April 95) +- added fast inflate (inffast.c) +- deflate(Z_FINISH) now returns Z_STREAM_END when done. Warning: this + is incompatible with previous versions of zlib which returned Z_OK. +- work around a TurboC compiler bug (bad code for b << 0, see infutil.h) + (actually that was not a compiler bug, see 0.81 above) +- gzread no longer reads one extra byte in certain cases +- In gzio destroy(), don't reference a freed structure +- avoid many warnings for MSDOS +- avoid the ERROR symbol which is used by MS Windows + +Changes in 0.71 (14 April 95) +- Fixed more MSDOS compilation problems :( There is still a bug with + TurboC large model. + +Changes in 0.7 (14 April 95) +- Added full inflate support. +- Simplified the crc32() interface. The pre- and post-conditioning + (one's complement) is now done inside crc32(). WARNING: this is + incompatible with previous versions; see zlib.h for the new usage. + +Changes in 0.61 (12 April 95) +- workaround for a bug in TurboC. example and minigzip now work on MSDOS. + +Changes in 0.6 (11 April 95) +- added minigzip.c +- added gzdopen to reopen a file descriptor as gzFile +- added transparent reading of non-gziped files in gzread. +- fixed bug in gzread (don't read crc as data) +- fixed bug in destroy (gzio.c) (don't return Z_STREAM_END for gzclose). +- don't allocate big arrays in the stack (for MSDOS) +- fix some MSDOS compilation problems + +Changes in 0.5: +- do real compression in deflate.c. Z_PARTIAL_FLUSH is supported but + not yet Z_FULL_FLUSH. +- support decompression but only in a single step (forced Z_FINISH) +- added opaque object for zalloc and zfree. +- added deflateReset and inflateReset +- added a variable zlib_version for consistency checking. +- renamed the 'filter' parameter of deflateInit2 as 'strategy'. + Added Z_FILTERED and Z_HUFFMAN_ONLY constants. + +Changes in 0.4: +- avoid "zip" everywhere, use zlib instead of ziplib. +- suppress Z_BLOCK_FLUSH, interpret Z_PARTIAL_FLUSH as block flush + if compression method == 8. +- added adler32 and crc32 +- renamed deflateOptions as deflateInit2, call one or the other but not both +- added the method parameter for deflateInit2. +- added inflateInit2 +- simplied considerably deflateInit and inflateInit by not supporting + user-provided history buffer. This is supported only in deflateInit2 + and inflateInit2. + +Changes in 0.3: +- prefix all macro names with Z_ +- use Z_FINISH instead of deflateEnd to finish compression. +- added Z_HUFFMAN_ONLY +- added gzerror() diff --git a/zlib/zlib/FAQ b/zlib/zlib/FAQ new file mode 100644 index 00000000..99b7cf92 --- /dev/null +++ b/zlib/zlib/FAQ @@ -0,0 +1,368 @@ + + Frequently Asked Questions about zlib + + +If your question is not there, please check the zlib home page +http://zlib.net/ which may have more recent information. +The lastest zlib FAQ is at http://zlib.net/zlib_faq.html + + + 1. Is zlib Y2K-compliant? + + Yes. zlib doesn't handle dates. + + 2. Where can I get a Windows DLL version? + + The zlib sources can be compiled without change to produce a DLL. See the + file win32/DLL_FAQ.txt in the zlib distribution. Pointers to the + precompiled DLL are found in the zlib web site at http://zlib.net/ . + + 3. Where can I get a Visual Basic interface to zlib? + + See + * http://marknelson.us/1997/01/01/zlib-engine/ + * win32/DLL_FAQ.txt in the zlib distribution + + 4. compress() returns Z_BUF_ERROR. + + Make sure that before the call of compress(), the length of the compressed + buffer is equal to the available size of the compressed buffer and not + zero. For Visual Basic, check that this parameter is passed by reference + ("as any"), not by value ("as long"). + + 5. deflate() or inflate() returns Z_BUF_ERROR. + + Before making the call, make sure that avail_in and avail_out are not zero. + When setting the parameter flush equal to Z_FINISH, also make sure that + avail_out is big enough to allow processing all pending input. Note that a + Z_BUF_ERROR is not fatal--another call to deflate() or inflate() can be + made with more input or output space. A Z_BUF_ERROR may in fact be + unavoidable depending on how the functions are used, since it is not + possible to tell whether or not there is more output pending when + strm.avail_out returns with zero. See http://zlib.net/zlib_how.html for a + heavily annotated example. + + 6. Where's the zlib documentation (man pages, etc.)? + + It's in zlib.h . Examples of zlib usage are in the files test/example.c + and test/minigzip.c, with more in examples/ . + + 7. Why don't you use GNU autoconf or libtool or ...? + + Because we would like to keep zlib as a very small and simple package. + zlib is rather portable and doesn't need much configuration. + + 8. I found a bug in zlib. + + Most of the time, such problems are due to an incorrect usage of zlib. + Please try to reproduce the problem with a small program and send the + corresponding source to us at zlib@gzip.org . Do not send multi-megabyte + data files without prior agreement. + + 9. Why do I get "undefined reference to gzputc"? + + If "make test" produces something like + + example.o(.text+0x154): undefined reference to `gzputc' + + check that you don't have old files libz.* in /usr/lib, /usr/local/lib or + /usr/X11R6/lib. Remove any old versions, then do "make install". + +10. I need a Delphi interface to zlib. + + See the contrib/delphi directory in the zlib distribution. + +11. Can zlib handle .zip archives? + + Not by itself, no. See the directory contrib/minizip in the zlib + distribution. + +12. Can zlib handle .Z files? + + No, sorry. You have to spawn an uncompress or gunzip subprocess, or adapt + the code of uncompress on your own. + +13. How can I make a Unix shared library? + + By default a shared (and a static) library is built for Unix. So: + + make distclean + ./configure + make + +14. How do I install a shared zlib library on Unix? + + After the above, then: + + make install + + However, many flavors of Unix come with a shared zlib already installed. + Before going to the trouble of compiling a shared version of zlib and + trying to install it, you may want to check if it's already there! If you + can #include , it's there. The -lz option will probably link to + it. You can check the version at the top of zlib.h or with the + ZLIB_VERSION symbol defined in zlib.h . + +15. I have a question about OttoPDF. + + We are not the authors of OttoPDF. The real author is on the OttoPDF web + site: Joel Hainley, jhainley@myndkryme.com. + +16. Can zlib decode Flate data in an Adobe PDF file? + + Yes. See http://www.pdflib.com/ . To modify PDF forms, see + http://sourceforge.net/projects/acroformtool/ . + +17. Why am I getting this "register_frame_info not found" error on Solaris? + + After installing zlib 1.1.4 on Solaris 2.6, running applications using zlib + generates an error such as: + + ld.so.1: rpm: fatal: relocation error: file /usr/local/lib/libz.so: + symbol __register_frame_info: referenced symbol not found + + The symbol __register_frame_info is not part of zlib, it is generated by + the C compiler (cc or gcc). You must recompile applications using zlib + which have this problem. This problem is specific to Solaris. See + http://www.sunfreeware.com for Solaris versions of zlib and applications + using zlib. + +18. Why does gzip give an error on a file I make with compress/deflate? + + The compress and deflate functions produce data in the zlib format, which + is different and incompatible with the gzip format. The gz* functions in + zlib on the other hand use the gzip format. Both the zlib and gzip formats + use the same compressed data format internally, but have different headers + and trailers around the compressed data. + +19. Ok, so why are there two different formats? + + The gzip format was designed to retain the directory information about a + single file, such as the name and last modification date. The zlib format + on the other hand was designed for in-memory and communication channel + applications, and has a much more compact header and trailer and uses a + faster integrity check than gzip. + +20. Well that's nice, but how do I make a gzip file in memory? + + You can request that deflate write the gzip format instead of the zlib + format using deflateInit2(). You can also request that inflate decode the + gzip format using inflateInit2(). Read zlib.h for more details. + +21. Is zlib thread-safe? + + Yes. However any library routines that zlib uses and any application- + provided memory allocation routines must also be thread-safe. zlib's gz* + functions use stdio library routines, and most of zlib's functions use the + library memory allocation routines by default. zlib's *Init* functions + allow for the application to provide custom memory allocation routines. + + Of course, you should only operate on any given zlib or gzip stream from a + single thread at a time. + +22. Can I use zlib in my commercial application? + + Yes. Please read the license in zlib.h. + +23. Is zlib under the GNU license? + + No. Please read the license in zlib.h. + +24. The license says that altered source versions must be "plainly marked". So + what exactly do I need to do to meet that requirement? + + You need to change the ZLIB_VERSION and ZLIB_VERNUM #defines in zlib.h. In + particular, the final version number needs to be changed to "f", and an + identification string should be appended to ZLIB_VERSION. Version numbers + x.x.x.f are reserved for modifications to zlib by others than the zlib + maintainers. For example, if the version of the base zlib you are altering + is "1.2.3.4", then in zlib.h you should change ZLIB_VERNUM to 0x123f, and + ZLIB_VERSION to something like "1.2.3.f-zachary-mods-v3". You can also + update the version strings in deflate.c and inftrees.c. + + For altered source distributions, you should also note the origin and + nature of the changes in zlib.h, as well as in ChangeLog and README, along + with the dates of the alterations. The origin should include at least your + name (or your company's name), and an email address to contact for help or + issues with the library. + + Note that distributing a compiled zlib library along with zlib.h and + zconf.h is also a source distribution, and so you should change + ZLIB_VERSION and ZLIB_VERNUM and note the origin and nature of the changes + in zlib.h as you would for a full source distribution. + +25. Will zlib work on a big-endian or little-endian architecture, and can I + exchange compressed data between them? + + Yes and yes. + +26. Will zlib work on a 64-bit machine? + + Yes. It has been tested on 64-bit machines, and has no dependence on any + data types being limited to 32-bits in length. If you have any + difficulties, please provide a complete problem report to zlib@gzip.org + +27. Will zlib decompress data from the PKWare Data Compression Library? + + No. The PKWare DCL uses a completely different compressed data format than + does PKZIP and zlib. However, you can look in zlib's contrib/blast + directory for a possible solution to your problem. + +28. Can I access data randomly in a compressed stream? + + No, not without some preparation. If when compressing you periodically use + Z_FULL_FLUSH, carefully write all the pending data at those points, and + keep an index of those locations, then you can start decompression at those + points. You have to be careful to not use Z_FULL_FLUSH too often, since it + can significantly degrade compression. Alternatively, you can scan a + deflate stream once to generate an index, and then use that index for + random access. See examples/zran.c . + +29. Does zlib work on MVS, OS/390, CICS, etc.? + + It has in the past, but we have not heard of any recent evidence. There + were working ports of zlib 1.1.4 to MVS, but those links no longer work. + If you know of recent, successful applications of zlib on these operating + systems, please let us know. Thanks. + +30. Is there some simpler, easier to read version of inflate I can look at to + understand the deflate format? + + First off, you should read RFC 1951. Second, yes. Look in zlib's + contrib/puff directory. + +31. Does zlib infringe on any patents? + + As far as we know, no. In fact, that was originally the whole point behind + zlib. Look here for some more information: + + http://www.gzip.org/#faq11 + +32. Can zlib work with greater than 4 GB of data? + + Yes. inflate() and deflate() will process any amount of data correctly. + Each call of inflate() or deflate() is limited to input and output chunks + of the maximum value that can be stored in the compiler's "unsigned int" + type, but there is no limit to the number of chunks. Note however that the + strm.total_in and strm_total_out counters may be limited to 4 GB. These + counters are provided as a convenience and are not used internally by + inflate() or deflate(). The application can easily set up its own counters + updated after each call of inflate() or deflate() to count beyond 4 GB. + compress() and uncompress() may be limited to 4 GB, since they operate in a + single call. gzseek() and gztell() may be limited to 4 GB depending on how + zlib is compiled. See the zlibCompileFlags() function in zlib.h. + + The word "may" appears several times above since there is a 4 GB limit only + if the compiler's "long" type is 32 bits. If the compiler's "long" type is + 64 bits, then the limit is 16 exabytes. + +33. Does zlib have any security vulnerabilities? + + The only one that we are aware of is potentially in gzprintf(). If zlib is + compiled to use sprintf() or vsprintf(), then there is no protection + against a buffer overflow of an 8K string space (or other value as set by + gzbuffer()), other than the caller of gzprintf() assuring that the output + will not exceed 8K. On the other hand, if zlib is compiled to use + snprintf() or vsnprintf(), which should normally be the case, then there is + no vulnerability. The ./configure script will display warnings if an + insecure variation of sprintf() will be used by gzprintf(). Also the + zlibCompileFlags() function will return information on what variant of + sprintf() is used by gzprintf(). + + If you don't have snprintf() or vsnprintf() and would like one, you can + find a portable implementation here: + + http://www.ijs.si/software/snprintf/ + + Note that you should be using the most recent version of zlib. Versions + 1.1.3 and before were subject to a double-free vulnerability, and versions + 1.2.1 and 1.2.2 were subject to an access exception when decompressing + invalid compressed data. + +34. Is there a Java version of zlib? + + Probably what you want is to use zlib in Java. zlib is already included + as part of the Java SDK in the java.util.zip package. If you really want + a version of zlib written in the Java language, look on the zlib home + page for links: http://zlib.net/ . + +35. I get this or that compiler or source-code scanner warning when I crank it + up to maximally-pedantic. Can't you guys write proper code? + + Many years ago, we gave up attempting to avoid warnings on every compiler + in the universe. It just got to be a waste of time, and some compilers + were downright silly as well as contradicted each other. So now, we simply + make sure that the code always works. + +36. Valgrind (or some similar memory access checker) says that deflate is + performing a conditional jump that depends on an uninitialized value. + Isn't that a bug? + + No. That is intentional for performance reasons, and the output of deflate + is not affected. This only started showing up recently since zlib 1.2.x + uses malloc() by default for allocations, whereas earlier versions used + calloc(), which zeros out the allocated memory. Even though the code was + correct, versions 1.2.4 and later was changed to not stimulate these + checkers. + +37. Will zlib read the (insert any ancient or arcane format here) compressed + data format? + + Probably not. Look in the comp.compression FAQ for pointers to various + formats and associated software. + +38. How can I encrypt/decrypt zip files with zlib? + + zlib doesn't support encryption. The original PKZIP encryption is very + weak and can be broken with freely available programs. To get strong + encryption, use GnuPG, http://www.gnupg.org/ , which already includes zlib + compression. For PKZIP compatible "encryption", look at + http://www.info-zip.org/ + +39. What's the difference between the "gzip" and "deflate" HTTP 1.1 encodings? + + "gzip" is the gzip format, and "deflate" is the zlib format. They should + probably have called the second one "zlib" instead to avoid confusion with + the raw deflate compressed data format. While the HTTP 1.1 RFC 2616 + correctly points to the zlib specification in RFC 1950 for the "deflate" + transfer encoding, there have been reports of servers and browsers that + incorrectly produce or expect raw deflate data per the deflate + specification in RFC 1951, most notably Microsoft. So even though the + "deflate" transfer encoding using the zlib format would be the more + efficient approach (and in fact exactly what the zlib format was designed + for), using the "gzip" transfer encoding is probably more reliable due to + an unfortunate choice of name on the part of the HTTP 1.1 authors. + + Bottom line: use the gzip format for HTTP 1.1 encoding. + +40. Does zlib support the new "Deflate64" format introduced by PKWare? + + No. PKWare has apparently decided to keep that format proprietary, since + they have not documented it as they have previous compression formats. In + any case, the compression improvements are so modest compared to other more + modern approaches, that it's not worth the effort to implement. + +41. I'm having a problem with the zip functions in zlib, can you help? + + There are no zip functions in zlib. You are probably using minizip by + Giles Vollant, which is found in the contrib directory of zlib. It is not + part of zlib. In fact none of the stuff in contrib is part of zlib. The + files in there are not supported by the zlib authors. You need to contact + the authors of the respective contribution for help. + +42. The match.asm code in contrib is under the GNU General Public License. + Since it's part of zlib, doesn't that mean that all of zlib falls under the + GNU GPL? + + No. The files in contrib are not part of zlib. They were contributed by + other authors and are provided as a convenience to the user within the zlib + distribution. Each item in contrib has its own license. + +43. Is zlib subject to export controls? What is its ECCN? + + zlib is not subject to export controls, and so is classified as EAR99. + +44. Can you please sign these lengthy legal documents and fax them back to us + so that we can use your software in our product? + + No. Go away. Shoo. diff --git a/zlib/zlib/INDEX b/zlib/zlib/INDEX new file mode 100644 index 00000000..2ba06412 --- /dev/null +++ b/zlib/zlib/INDEX @@ -0,0 +1,68 @@ +CMakeLists.txt cmake build file +ChangeLog history of changes +FAQ Frequently Asked Questions about zlib +INDEX this file +Makefile dummy Makefile that tells you to ./configure +Makefile.in template for Unix Makefile +README guess what +configure configure script for Unix +make_vms.com makefile for VMS +test/example.c zlib usages examples for build testing +test/minigzip.c minimal gzip-like functionality for build testing +test/infcover.c inf*.c code coverage for build coverage testing +treebuild.xml XML description of source file dependencies +zconf.h.cmakein zconf.h template for cmake +zconf.h.in zconf.h template for configure +zlib.3 Man page for zlib +zlib.3.pdf Man page in PDF format +zlib.map Linux symbol information +zlib.pc.in Template for pkg-config descriptor +zlib.pc.cmakein zlib.pc template for cmake +zlib2ansi perl script to convert source files for C++ compilation + +amiga/ makefiles for Amiga SAS C +as400/ makefiles for AS/400 +doc/ documentation for formats and algorithms +msdos/ makefiles for MSDOS +nintendods/ makefile for Nintendo DS +old/ makefiles for various architectures and zlib documentation + files that have not yet been updated for zlib 1.2.x +qnx/ makefiles for QNX +watcom/ makefiles for OpenWatcom +win32/ makefiles for Windows + + zlib public header files (required for library use): +zconf.h +zlib.h + + private source files used to build the zlib library: +adler32.c +compress.c +crc32.c +crc32.h +deflate.c +deflate.h +gzclose.c +gzguts.h +gzlib.c +gzread.c +gzwrite.c +infback.c +inffast.c +inffast.h +inffixed.h +inflate.c +inflate.h +inftrees.c +inftrees.h +trees.c +trees.h +uncompr.c +zutil.c +zutil.h + + source files for sample programs +See examples/README.examples + + unsupported contributions by third parties +See contrib/README.contrib diff --git a/zlib/zlib/Makefile b/zlib/zlib/Makefile new file mode 100644 index 00000000..6bba86c7 --- /dev/null +++ b/zlib/zlib/Makefile @@ -0,0 +1,5 @@ +all: + -@echo "Please use ./configure first. Thank you." + +distclean: + make -f Makefile.in distclean diff --git a/zlib/zlib/Makefile.in b/zlib/zlib/Makefile.in new file mode 100644 index 00000000..c61aa300 --- /dev/null +++ b/zlib/zlib/Makefile.in @@ -0,0 +1,288 @@ +# Makefile for zlib +# Copyright (C) 1995-2013 Jean-loup Gailly, Mark Adler +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# ./configure; make test +# Normally configure builds both a static and a shared library. +# If you want to build just a static library, use: ./configure --static + +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o + +# To install /usr/local/lib/libz.* and /usr/local/include/zlib.h, type: +# make install +# To install in $HOME instead of /usr/local, use: +# make install prefix=$HOME + +CC=cc + +CFLAGS=-O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DDEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +SFLAGS=-O +LDFLAGS= +TEST_LDFLAGS=-L. libz.a +LDSHARED=$(CC) +CPP=$(CC) -E + +STATICLIB=libz.a +SHAREDLIB=libz.so +SHAREDLIBV=libz.so.1.2.8 +SHAREDLIBM=libz.so.1 +LIBS=$(STATICLIB) $(SHAREDLIBV) + +AR=ar +ARFLAGS=rc +RANLIB=ranlib +LDCONFIG=ldconfig +LDSHAREDLIBC=-lc +TAR=tar +SHELL=/bin/sh +EXE= + +prefix = /usr/local +exec_prefix = ${prefix} +libdir = ${exec_prefix}/lib +sharedlibdir = ${libdir} +includedir = ${prefix}/include +mandir = ${prefix}/share/man +man3dir = ${mandir}/man3 +pkgconfigdir = ${libdir}/pkgconfig + +OBJZ = adler32.o crc32.o deflate.o infback.o inffast.o inflate.o inftrees.o trees.o zutil.o +OBJG = compress.o uncompr.o gzclose.o gzlib.o gzread.o gzwrite.o +OBJC = $(OBJZ) $(OBJG) + +PIC_OBJZ = adler32.lo crc32.lo deflate.lo infback.lo inffast.lo inflate.lo inftrees.lo trees.lo zutil.lo +PIC_OBJG = compress.lo uncompr.lo gzclose.lo gzlib.lo gzread.lo gzwrite.lo +PIC_OBJC = $(PIC_OBJZ) $(PIC_OBJG) + +# to use the asm code: make OBJA=match.o, PIC_OBJA=match.lo +OBJA = +PIC_OBJA = + +OBJS = $(OBJC) $(OBJA) + +PIC_OBJS = $(PIC_OBJC) $(PIC_OBJA) + +all: static shared + +static: example$(EXE) minigzip$(EXE) + +shared: examplesh$(EXE) minigzipsh$(EXE) + +all64: example64$(EXE) minigzip64$(EXE) + +check: test + +test: all teststatic testshared + +teststatic: static + @TMPST=tmpst_$$; \ + if echo hello world | ./minigzip | ./minigzip -d && ./example $$TMPST ; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; false; \ + fi; \ + rm -f $$TMPST + +testshared: shared + @LD_LIBRARY_PATH=`pwd`:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + LD_LIBRARYN32_PATH=`pwd`:$(LD_LIBRARYN32_PATH) ; export LD_LIBRARYN32_PATH; \ + DYLD_LIBRARY_PATH=`pwd`:$(DYLD_LIBRARY_PATH) ; export DYLD_LIBRARY_PATH; \ + SHLIB_PATH=`pwd`:$(SHLIB_PATH) ; export SHLIB_PATH; \ + TMPSH=tmpsh_$$; \ + if echo hello world | ./minigzipsh | ./minigzipsh -d && ./examplesh $$TMPSH; then \ + echo ' *** zlib shared test OK ***'; \ + else \ + echo ' *** zlib shared test FAILED ***'; false; \ + fi; \ + rm -f $$TMPSH + +test64: all64 + @TMP64=tmp64_$$; \ + if echo hello world | ./minigzip64 | ./minigzip64 -d && ./example64 $$TMP64; then \ + echo ' *** zlib 64-bit test OK ***'; \ + else \ + echo ' *** zlib 64-bit test FAILED ***'; false; \ + fi; \ + rm -f $$TMP64 + +infcover.o: test/infcover.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/infcover.c + +infcover: infcover.o libz.a + $(CC) $(CFLAGS) -o $@ infcover.o libz.a + +cover: infcover + rm -f *.gcda + ./infcover + gcov inf*.c + +libz.a: $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +match.o: match.S + $(CPP) match.S > _match.s + $(CC) -c _match.s + mv _match.o match.o + rm -f _match.s + +match.lo: match.S + $(CPP) match.S > _match.s + $(CC) -c -fPIC _match.s + mv _match.o match.lo + rm -f _match.s + +example.o: test/example.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/example.c + +minigzip.o: test/minigzip.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/minigzip.c + +example64.o: test/example.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -D_FILE_OFFSET_BITS=64 -c -o $@ test/example.c + +minigzip64.o: test/minigzip.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -D_FILE_OFFSET_BITS=64 -c -o $@ test/minigzip.c + +.SUFFIXES: .lo + +.c.lo: + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) -DPIC -c -o objs/$*.o $< + -@mv objs/$*.o $@ + +placebo $(SHAREDLIBV): $(PIC_OBJS) libz.a + $(LDSHARED) $(SFLAGS) -o $@ $(PIC_OBJS) $(LDSHAREDLIBC) $(LDFLAGS) + rm -f $(SHAREDLIB) $(SHAREDLIBM) + ln -s $@ $(SHAREDLIB) + ln -s $@ $(SHAREDLIBM) + -@rmdir objs + +example$(EXE): example.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ example.o $(TEST_LDFLAGS) + +minigzip$(EXE): minigzip.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ minigzip.o $(TEST_LDFLAGS) + +examplesh$(EXE): example.o $(SHAREDLIBV) + $(CC) $(CFLAGS) -o $@ example.o -L. $(SHAREDLIBV) + +minigzipsh$(EXE): minigzip.o $(SHAREDLIBV) + $(CC) $(CFLAGS) -o $@ minigzip.o -L. $(SHAREDLIBV) + +example64$(EXE): example64.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ example64.o $(TEST_LDFLAGS) + +minigzip64$(EXE): minigzip64.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ minigzip64.o $(TEST_LDFLAGS) + +install-libs: $(LIBS) + -@if [ ! -d $(DESTDIR)$(exec_prefix) ]; then mkdir -p $(DESTDIR)$(exec_prefix); fi + -@if [ ! -d $(DESTDIR)$(libdir) ]; then mkdir -p $(DESTDIR)$(libdir); fi + -@if [ ! -d $(DESTDIR)$(sharedlibdir) ]; then mkdir -p $(DESTDIR)$(sharedlibdir); fi + -@if [ ! -d $(DESTDIR)$(man3dir) ]; then mkdir -p $(DESTDIR)$(man3dir); fi + -@if [ ! -d $(DESTDIR)$(pkgconfigdir) ]; then mkdir -p $(DESTDIR)$(pkgconfigdir); fi + cp $(STATICLIB) $(DESTDIR)$(libdir) + chmod 644 $(DESTDIR)$(libdir)/$(STATICLIB) + -@($(RANLIB) $(DESTDIR)$(libdir)/libz.a || true) >/dev/null 2>&1 + -@if test -n "$(SHAREDLIBV)"; then \ + cp $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir); \ + echo "cp $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)"; \ + chmod 755 $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBV); \ + echo "chmod 755 $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBV)"; \ + rm -f $(DESTDIR)$(sharedlibdir)/$(SHAREDLIB) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBM); \ + ln -s $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIB); \ + ln -s $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBM); \ + ($(LDCONFIG) || true) >/dev/null 2>&1; \ + fi + cp zlib.3 $(DESTDIR)$(man3dir) + chmod 644 $(DESTDIR)$(man3dir)/zlib.3 + cp zlib.pc $(DESTDIR)$(pkgconfigdir) + chmod 644 $(DESTDIR)$(pkgconfigdir)/zlib.pc +# The ranlib in install is needed on NeXTSTEP which checks file times +# ldconfig is for Linux + +install: install-libs + -@if [ ! -d $(DESTDIR)$(includedir) ]; then mkdir -p $(DESTDIR)$(includedir); fi + cp zlib.h zconf.h $(DESTDIR)$(includedir) + chmod 644 $(DESTDIR)$(includedir)/zlib.h $(DESTDIR)$(includedir)/zconf.h + +uninstall: + cd $(DESTDIR)$(includedir) && rm -f zlib.h zconf.h + cd $(DESTDIR)$(libdir) && rm -f libz.a; \ + if test -n "$(SHAREDLIBV)" -a -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIBV) $(SHAREDLIB) $(SHAREDLIBM); \ + fi + cd $(DESTDIR)$(man3dir) && rm -f zlib.3 + cd $(DESTDIR)$(pkgconfigdir) && rm -f zlib.pc + +docs: zlib.3.pdf + +zlib.3.pdf: zlib.3 + groff -mandoc -f H -T ps zlib.3 | ps2pdf - zlib.3.pdf + +zconf.h.cmakein: zconf.h.in + -@ TEMPFILE=zconfh_$$; \ + echo "/#define ZCONF_H/ a\\\\\n#cmakedefine Z_PREFIX\\\\\n#cmakedefine Z_HAVE_UNISTD_H\n" >> $$TEMPFILE &&\ + sed -f $$TEMPFILE zconf.h.in > zconf.h.cmakein &&\ + touch -r zconf.h.in zconf.h.cmakein &&\ + rm $$TEMPFILE + +zconf: zconf.h.in + cp -p zconf.h.in zconf.h + +mostlyclean: clean +clean: + rm -f *.o *.lo *~ \ + example$(EXE) minigzip$(EXE) examplesh$(EXE) minigzipsh$(EXE) \ + example64$(EXE) minigzip64$(EXE) \ + infcover \ + libz.* foo.gz so_locations \ + _match.s maketree contrib/infback9/*.o + rm -rf objs + rm -f *.gcda *.gcno *.gcov + rm -f contrib/infback9/*.gcda contrib/infback9/*.gcno contrib/infback9/*.gcov + +maintainer-clean: distclean +distclean: clean zconf zconf.h.cmakein docs + rm -f Makefile zlib.pc configure.log + -@rm -f .DS_Store + -@printf 'all:\n\t-@echo "Please use ./configure first. Thank you."\n' > Makefile + -@printf '\ndistclean:\n\tmake -f Makefile.in distclean\n' >> Makefile + -@touch -r Makefile.in Makefile + +tags: + etags *.[ch] + +depend: + makedepend -- $(CFLAGS) -- *.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o zutil.o: zutil.h zlib.h zconf.h +gzclose.o gzlib.o gzread.o gzwrite.o: zlib.h zconf.h gzguts.h +compress.o example.o minigzip.o uncompr.o: zlib.h zconf.h +crc32.o: zutil.h zlib.h zconf.h crc32.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +infback.o inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h + +adler32.lo zutil.lo: zutil.h zlib.h zconf.h +gzclose.lo gzlib.lo gzread.lo gzwrite.lo: zlib.h zconf.h gzguts.h +compress.lo example.lo minigzip.lo uncompr.lo: zlib.h zconf.h +crc32.lo: zutil.h zlib.h zconf.h crc32.h +deflate.lo: deflate.h zutil.h zlib.h zconf.h +infback.lo inflate.lo: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h inffixed.h +inffast.lo: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.lo: zutil.h zlib.h zconf.h inftrees.h +trees.lo: deflate.h zutil.h zlib.h zconf.h trees.h diff --git a/zlib/zlib/README b/zlib/zlib/README new file mode 100644 index 00000000..5ca9d127 --- /dev/null +++ b/zlib/zlib/README @@ -0,0 +1,115 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.8 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and +rfc1952 (gzip format). + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file test/example.c which also tests that +the library is working correctly. Another example is given in the file +test/minigzip.c. The compression library itself is composed of all source +files in the root directory. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile.in. In short "./configure; make test", and if that goes +well, "make install" should work for most flavors of Unix. For Windows, use +one of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use +make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://zlib.net/ . Before reporting a problem, please check this site to +verify that you have the latest version of zlib; otherwise get the latest +version and check whether the problem still exists or not. + +PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available at +http://marknelson.us/1997/01/01/zlib-engine/ . + +The changes made in version 1.2.8 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory contrib/ . + +zlib is available in Java using the java.util.zip package, documented at +http://java.sun.com/developer/technicalArticles/Programming/compression/ . + +A Perl interface to zlib written by Paul Marquess is available +at CPAN (Comprehensive Perl Archive Network) sites, including +http://search.cpan.org/~pmqs/IO-Compress-Zlib/ . + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://docs.python.org/library/zlib.html . + +zlib is built into tcl: http://wiki.tcl.tk/4610 . + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS or BEOS. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate and + zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; they + are too numerous to cite here. + +Copyright notice: + + (C) 1995-2013 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. diff --git a/zlib/zlib/adler32.c b/zlib/zlib/adler32.c new file mode 100644 index 00000000..a868f073 --- /dev/null +++ b/zlib/zlib/adler32.c @@ -0,0 +1,179 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2011 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" + +#define local static + +local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); + +#define BASE 65521 /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware -- + try it both ways to see which is faster */ +#ifdef NO_DIVIDE +/* note that this assumes BASE is 65521, where 65536 % 65521 == 15 + (thank you to John Reiser for pointing this out) */ +# define CHOP(a) \ + do { \ + unsigned long tmp = a >> 16; \ + a &= 0xffffUL; \ + a += (tmp << 4) - tmp; \ + } while (0) +# define MOD28(a) \ + do { \ + CHOP(a); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD(a) \ + do { \ + CHOP(a); \ + MOD28(a); \ + } while (0) +# define MOD63(a) \ + do { /* this assumes a is not negative */ \ + z_off64_t tmp = a >> 32; \ + a &= 0xffffffffL; \ + a += (tmp << 8) - (tmp << 5) + tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD28(a) a %= BASE +# define MOD63(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD28(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +local uLong adler32_combine_(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off64_t len2; +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* for negative len, return invalid adler32 as a clue for debugging */ + if (len2 < 0) + return 0xffffffffUL; + + /* the derivation of this formula is left as an exercise for the reader */ + MOD63(len2); /* assumes len2 >= 0 */ + rem = (unsigned)len2; + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 >= BASE) sum1 -= BASE; + if (sum1 >= BASE) sum1 -= BASE; + if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 >= BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off_t len2; +{ + return adler32_combine_(adler1, adler2, len2); +} + +uLong ZEXPORT adler32_combine64(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off64_t len2; +{ + return adler32_combine_(adler1, adler2, len2); +} diff --git a/zlib/zlib/amiga/Makefile.pup b/zlib/zlib/amiga/Makefile.pup new file mode 100644 index 00000000..8940c120 --- /dev/null +++ b/zlib/zlib/amiga/Makefile.pup @@ -0,0 +1,69 @@ +# Amiga powerUP (TM) Makefile +# makefile for libpng and SAS C V6.58/7.00 PPC compiler +# Copyright (C) 1998 by Andreas R. Kleinert + +LIBNAME = libzip.a + +CC = scppc +CFLAGS = NOSTKCHK NOSINT OPTIMIZE OPTGO OPTPEEP OPTINLOCAL OPTINL \ + OPTLOOP OPTRDEP=8 OPTDEP=8 OPTCOMP=8 NOVER +AR = ppc-amigaos-ar cr +RANLIB = ppc-amigaos-ranlib +LD = ppc-amigaos-ld -r +LDFLAGS = -o +LDLIBS = LIB:scppc.a LIB:end.o +RM = delete quiet + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: example minigzip + +check: test +test: all + example + echo hello world | minigzip | minigzip -d + +$(LIBNAME): $(OBJS) + $(AR) $@ $(OBJS) + -$(RANLIB) $@ + +example: example.o $(LIBNAME) + $(LD) $(LDFLAGS) $@ LIB:c_ppc.o $@.o $(LIBNAME) $(LDLIBS) + +minigzip: minigzip.o $(LIBNAME) + $(LD) $(LDFLAGS) $@ LIB:c_ppc.o $@.o $(LIBNAME) $(LDLIBS) + +mostlyclean: clean +clean: + $(RM) *.o example minigzip $(LIBNAME) foo.gz + +zip: + zip -ul9 zlib README ChangeLog Makefile Make????.??? Makefile.?? \ + descrip.mms *.[ch] + +tgz: + cd ..; tar cfz zlib/zlib.tgz zlib/README zlib/ChangeLog zlib/Makefile \ + zlib/Make????.??? zlib/Makefile.?? zlib/descrip.mms zlib/*.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzclose.o: zlib.h zconf.h gzguts.h +gzlib.o: zlib.h zconf.h gzguts.h +gzread.o: zlib.h zconf.h gzguts.h +gzwrite.o: zlib.h zconf.h gzguts.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h diff --git a/zlib/zlib/amiga/Makefile.sas b/zlib/zlib/amiga/Makefile.sas new file mode 100644 index 00000000..749e2915 --- /dev/null +++ b/zlib/zlib/amiga/Makefile.sas @@ -0,0 +1,68 @@ +# SMakefile for zlib +# Modified from the standard UNIX Makefile Copyright Jean-loup Gailly +# Osma Ahvenlampi +# Amiga, SAS/C 6.56 & Smake + +CC=sc +CFLAGS=OPT +#CFLAGS=OPT CPU=68030 +#CFLAGS=DEBUG=LINE +LDFLAGS=LIB z.lib + +SCOPTIONS=OPTSCHED OPTINLINE OPTALIAS OPTTIME OPTINLOCAL STRMERGE \ + NOICONS PARMS=BOTH NOSTACKCHECK UTILLIB NOVERSION ERRORREXX \ + DEF=POSTINC + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: SCOPTIONS example minigzip + +check: test +test: all + example + echo hello world | minigzip | minigzip -d + +install: z.lib + copy clone zlib.h zconf.h INCLUDE: + copy clone z.lib LIB: + +z.lib: $(OBJS) + oml z.lib r $(OBJS) + +example: example.o z.lib + $(CC) $(CFLAGS) LINK TO $@ example.o $(LDFLAGS) + +minigzip: minigzip.o z.lib + $(CC) $(CFLAGS) LINK TO $@ minigzip.o $(LDFLAGS) + +mostlyclean: clean +clean: + -delete force quiet example minigzip *.o z.lib foo.gz *.lnk SCOPTIONS + +SCOPTIONS: Makefile.sas + copy to $@ 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} + +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound (sourceLen) + uLong sourceLen; +{ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13; +} diff --git a/zlib/zlib/configure b/zlib/zlib/configure new file mode 100755 index 00000000..b77a8a8c --- /dev/null +++ b/zlib/zlib/configure @@ -0,0 +1,831 @@ +#!/bin/sh +# configure script for zlib. +# +# Normally configure builds both a static and a shared library. +# If you want to build just a static library, use: ./configure --static +# +# To impose specific compiler or flags or install directory, use for example: +# prefix=$HOME CC=cc CFLAGS="-O4" ./configure +# or for csh/tcsh users: +# (setenv prefix $HOME; setenv CC cc; setenv CFLAGS "-O4"; ./configure) + +# Incorrect settings of CC or CFLAGS may prevent creating a shared library. +# If you have problems, try without defining CC and CFLAGS before reporting +# an error. + +# start off configure.log +echo -------------------- >> configure.log +echo $0 $* >> configure.log +date >> configure.log + +# set command prefix for cross-compilation +if [ -n "${CHOST}" ]; then + uname="`echo "${CHOST}" | sed -e 's/^[^-]*-\([^-]*\)$/\1/' -e 's/^[^-]*-[^-]*-\([^-]*\)$/\1/' -e 's/^[^-]*-[^-]*-\([^-]*\)-.*$/\1/'`" + CROSS_PREFIX="${CHOST}-" +fi + +# destination name for static library +STATICLIB=libz.a + +# extract zlib version numbers from zlib.h +VER=`sed -n -e '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h` +VER3=`sed -n -e '/VERSION "/s/.*"\([0-9]*\\.[0-9]*\\.[0-9]*\).*/\1/p' < zlib.h` +VER2=`sed -n -e '/VERSION "/s/.*"\([0-9]*\\.[0-9]*\)\\..*/\1/p' < zlib.h` +VER1=`sed -n -e '/VERSION "/s/.*"\([0-9]*\)\\..*/\1/p' < zlib.h` + +# establish commands for library building +if "${CROSS_PREFIX}ar" --version >/dev/null 2>/dev/null || test $? -lt 126; then + AR=${AR-"${CROSS_PREFIX}ar"} + test -n "${CROSS_PREFIX}" && echo Using ${AR} | tee -a configure.log +else + AR=${AR-"ar"} + test -n "${CROSS_PREFIX}" && echo Using ${AR} | tee -a configure.log +fi +ARFLAGS=${ARFLAGS-"rc"} +if "${CROSS_PREFIX}ranlib" --version >/dev/null 2>/dev/null || test $? -lt 126; then + RANLIB=${RANLIB-"${CROSS_PREFIX}ranlib"} + test -n "${CROSS_PREFIX}" && echo Using ${RANLIB} | tee -a configure.log +else + RANLIB=${RANLIB-"ranlib"} +fi +if "${CROSS_PREFIX}nm" --version >/dev/null 2>/dev/null || test $? -lt 126; then + NM=${NM-"${CROSS_PREFIX}nm"} + test -n "${CROSS_PREFIX}" && echo Using ${NM} | tee -a configure.log +else + NM=${NM-"nm"} +fi + +# set defaults before processing command line options +LDCONFIG=${LDCONFIG-"ldconfig"} +LDSHAREDLIBC="${LDSHAREDLIBC--lc}" +ARCHS= +prefix=${prefix-/usr/local} +exec_prefix=${exec_prefix-'${prefix}'} +libdir=${libdir-'${exec_prefix}/lib'} +sharedlibdir=${sharedlibdir-'${libdir}'} +includedir=${includedir-'${prefix}/include'} +mandir=${mandir-'${prefix}/share/man'} +shared_ext='.so' +shared=1 +solo=0 +cover=0 +zprefix=0 +zconst=0 +build64=0 +gcc=0 +old_cc="$CC" +old_cflags="$CFLAGS" +OBJC='$(OBJZ) $(OBJG)' +PIC_OBJC='$(PIC_OBJZ) $(PIC_OBJG)' + +# leave this script, optionally in a bad way +leave() +{ + if test "$*" != "0"; then + echo "** $0 aborting." | tee -a configure.log + fi + rm -f $test.[co] $test $test$shared_ext $test.gcno ./--version + echo -------------------- >> configure.log + echo >> configure.log + echo >> configure.log + exit $1 +} + +# process command line options +while test $# -ge 1 +do +case "$1" in + -h* | --help) + echo 'usage:' | tee -a configure.log + echo ' configure [--const] [--zprefix] [--prefix=PREFIX] [--eprefix=EXPREFIX]' | tee -a configure.log + echo ' [--static] [--64] [--libdir=LIBDIR] [--sharedlibdir=LIBDIR]' | tee -a configure.log + echo ' [--includedir=INCLUDEDIR] [--archs="-arch i386 -arch x86_64"]' | tee -a configure.log + exit 0 ;; + -p*=* | --prefix=*) prefix=`echo $1 | sed 's/.*=//'`; shift ;; + -e*=* | --eprefix=*) exec_prefix=`echo $1 | sed 's/.*=//'`; shift ;; + -l*=* | --libdir=*) libdir=`echo $1 | sed 's/.*=//'`; shift ;; + --sharedlibdir=*) sharedlibdir=`echo $1 | sed 's/.*=//'`; shift ;; + -i*=* | --includedir=*) includedir=`echo $1 | sed 's/.*=//'`;shift ;; + -u*=* | --uname=*) uname=`echo $1 | sed 's/.*=//'`;shift ;; + -p* | --prefix) prefix="$2"; shift; shift ;; + -e* | --eprefix) exec_prefix="$2"; shift; shift ;; + -l* | --libdir) libdir="$2"; shift; shift ;; + -i* | --includedir) includedir="$2"; shift; shift ;; + -s* | --shared | --enable-shared) shared=1; shift ;; + -t | --static) shared=0; shift ;; + --solo) solo=1; shift ;; + --cover) cover=1; shift ;; + -z* | --zprefix) zprefix=1; shift ;; + -6* | --64) build64=1; shift ;; + -a*=* | --archs=*) ARCHS=`echo $1 | sed 's/.*=//'`; shift ;; + --sysconfdir=*) echo "ignored option: --sysconfdir" | tee -a configure.log; shift ;; + --localstatedir=*) echo "ignored option: --localstatedir" | tee -a configure.log; shift ;; + -c* | --const) zconst=1; shift ;; + *) + echo "unknown option: $1" | tee -a configure.log + echo "$0 --help for help" | tee -a configure.log + leave 1;; + esac +done + +# temporary file name +test=ztest$$ + +# put arguments in log, also put test file in log if used in arguments +show() +{ + case "$*" in + *$test.c*) + echo === $test.c === >> configure.log + cat $test.c >> configure.log + echo === >> configure.log;; + esac + echo $* >> configure.log +} + +# check for gcc vs. cc and set compile and link flags based on the system identified by uname +cat > $test.c <&1` in + *gcc*) gcc=1 ;; +esac + +show $cc -c $test.c +if test "$gcc" -eq 1 && ($cc -c $test.c) >> configure.log 2>&1; then + echo ... using gcc >> configure.log + CC="$cc" + CFLAGS="${CFLAGS--O3} ${ARCHS}" + SFLAGS="${CFLAGS--O3} -fPIC" + LDFLAGS="${LDFLAGS} ${ARCHS}" + if test $build64 -eq 1; then + CFLAGS="${CFLAGS} -m64" + SFLAGS="${SFLAGS} -m64" + fi + if test "${ZLIBGCCWARN}" = "YES"; then + if test "$zconst" -eq 1; then + CFLAGS="${CFLAGS} -Wall -Wextra -Wcast-qual -pedantic -DZLIB_CONST" + else + CFLAGS="${CFLAGS} -Wall -Wextra -pedantic" + fi + fi + if test -z "$uname"; then + uname=`(uname -s || echo unknown) 2>/dev/null` + fi + case "$uname" in + Linux* | linux* | GNU | GNU/* | solaris*) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-soname,libz.so.1,--version-script,zlib.map"} ;; + *BSD | *bsd* | DragonFly) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-soname,libz.so.1,--version-script,zlib.map"} + LDCONFIG="ldconfig -m" ;; + CYGWIN* | Cygwin* | cygwin* | OS/2*) + EXE='.exe' ;; + MINGW* | mingw*) +# temporary bypass + rm -f $test.[co] $test $test$shared_ext + echo "Please use win32/Makefile.gcc instead." | tee -a configure.log + leave 1 + LDSHARED=${LDSHARED-"$cc -shared"} + LDSHAREDLIBC="" + EXE='.exe' ;; + QNX*) # This is for QNX6. I suppose that the QNX rule below is for QNX2,QNX4 + # (alain.bonnefoy@icbt.com) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-hlibz.so.1"} ;; + HP-UX*) + LDSHARED=${LDSHARED-"$cc -shared $SFLAGS"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so' ;; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl' ;; + esac ;; + Darwin* | darwin*) + shared_ext='.dylib' + SHAREDLIB=libz$shared_ext + SHAREDLIBV=libz.$VER$shared_ext + SHAREDLIBM=libz.$VER1$shared_ext + LDSHARED=${LDSHARED-"$cc -dynamiclib -install_name $libdir/$SHAREDLIBM -compatibility_version $VER1 -current_version $VER3"} + if libtool -V 2>&1 | grep Apple > /dev/null; then + AR="libtool" + else + AR="/usr/bin/libtool" + fi + ARFLAGS="-o" ;; + *) LDSHARED=${LDSHARED-"$cc -shared"} ;; + esac +else + # find system name and corresponding cc options + CC=${CC-cc} + gcc=0 + echo ... using $CC >> configure.log + if test -z "$uname"; then + uname=`(uname -sr || echo unknown) 2>/dev/null` + fi + case "$uname" in + HP-UX*) SFLAGS=${CFLAGS-"-O +z"} + CFLAGS=${CFLAGS-"-O"} +# LDSHARED=${LDSHARED-"ld -b +vnocompatwarnings"} + LDSHARED=${LDSHARED-"ld -b"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so' ;; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl' ;; + esac ;; + IRIX*) SFLAGS=${CFLAGS-"-ansi -O2 -rpath ."} + CFLAGS=${CFLAGS-"-ansi -O2"} + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so.1"} ;; + OSF1\ V4*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDFLAGS="${LDFLAGS} -Wl,-rpath,." + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so -Wl,-msym -Wl,-rpath,$(libdir) -Wl,-set_version,${VER}:1.0"} ;; + OSF1*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so.1"} ;; + QNX*) SFLAGS=${CFLAGS-"-4 -O"} + CFLAGS=${CFLAGS-"-4 -O"} + LDSHARED=${LDSHARED-"cc"} + RANLIB=${RANLIB-"true"} + AR="cc" + ARFLAGS="-A" ;; + SCO_SV\ 3.2*) SFLAGS=${CFLAGS-"-O3 -dy -KPIC "} + CFLAGS=${CFLAGS-"-O3"} + LDSHARED=${LDSHARED-"cc -dy -KPIC -G"} ;; + SunOS\ 5* | solaris*) + LDSHARED=${LDSHARED-"cc -G -h libz$shared_ext.$VER1"} + SFLAGS=${CFLAGS-"-fast -KPIC"} + CFLAGS=${CFLAGS-"-fast"} + if test $build64 -eq 1; then + # old versions of SunPRO/Workshop/Studio don't support -m64, + # but newer ones do. Check for it. + flag64=`$CC -flags | egrep -- '^-m64'` + if test x"$flag64" != x"" ; then + CFLAGS="${CFLAGS} -m64" + SFLAGS="${SFLAGS} -m64" + else + case `(uname -m || echo unknown) 2>/dev/null` in + i86*) + SFLAGS="$SFLAGS -xarch=amd64" + CFLAGS="$CFLAGS -xarch=amd64" ;; + *) + SFLAGS="$SFLAGS -xarch=v9" + CFLAGS="$CFLAGS -xarch=v9" ;; + esac + fi + fi + ;; + SunOS\ 4*) SFLAGS=${CFLAGS-"-O2 -PIC"} + CFLAGS=${CFLAGS-"-O2"} + LDSHARED=${LDSHARED-"ld"} ;; + SunStudio\ 9*) SFLAGS=${CFLAGS-"-fast -xcode=pic32 -xtarget=ultra3 -xarch=v9b"} + CFLAGS=${CFLAGS-"-fast -xtarget=ultra3 -xarch=v9b"} + LDSHARED=${LDSHARED-"cc -xarch=v9b"} ;; + UNIX_System_V\ 4.2.0) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + UNIX_SV\ 4.2MP) + SFLAGS=${CFLAGS-"-Kconform_pic -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + OpenUNIX\ 5) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + AIX*) # Courtesy of dbakker@arrayasolutions.com + SFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + CFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + LDSHARED=${LDSHARED-"xlc -G"} ;; + # send working options for other systems to zlib@gzip.org + *) SFLAGS=${CFLAGS-"-O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -shared"} ;; + esac +fi + +# destination names for shared library if not defined above +SHAREDLIB=${SHAREDLIB-"libz$shared_ext"} +SHAREDLIBV=${SHAREDLIBV-"libz$shared_ext.$VER"} +SHAREDLIBM=${SHAREDLIBM-"libz$shared_ext.$VER1"} + +echo >> configure.log + +# define functions for testing compiler and library characteristics and logging the results + +cat > $test.c </dev/null; then + try() + { + show $* + test "`( $* ) 2>&1 | tee -a configure.log`" = "" + } + echo - using any output from compiler to indicate an error >> configure.log +else +try() +{ + show $* + ( $* ) >> configure.log 2>&1 + ret=$? + if test $ret -ne 0; then + echo "(exit code "$ret")" >> configure.log + fi + return $ret +} +fi + +tryboth() +{ + show $* + got=`( $* ) 2>&1` + ret=$? + printf %s "$got" >> configure.log + if test $ret -ne 0; then + return $ret + fi + test "$got" = "" +} + +cat > $test.c << EOF +int foo() { return 0; } +EOF +echo "Checking for obsessive-compulsive compiler options..." >> configure.log +if try $CC -c $CFLAGS $test.c; then + : +else + echo "Compiler error reporting is too harsh for $0 (perhaps remove -Werror)." | tee -a configure.log + leave 1 +fi + +echo >> configure.log + +# see if shared library build supported +cat > $test.c <> configure.log + show "$NM $test.o | grep _hello" + if test "`$NM $test.o | grep _hello | tee -a configure.log`" = ""; then + CPP="$CPP -DNO_UNDERLINE" + echo Checking for underline in external names... No. | tee -a configure.log + else + echo Checking for underline in external names... Yes. | tee -a configure.log + fi ;; +esac + +echo >> configure.log + +# check for large file support, and if none, check for fseeko() +cat > $test.c < +off64_t dummy = 0; +EOF +if try $CC -c $CFLAGS -D_LARGEFILE64_SOURCE=1 $test.c; then + CFLAGS="${CFLAGS} -D_LARGEFILE64_SOURCE=1" + SFLAGS="${SFLAGS} -D_LARGEFILE64_SOURCE=1" + ALL="${ALL} all64" + TEST="${TEST} test64" + echo "Checking for off64_t... Yes." | tee -a configure.log + echo "Checking for fseeko... Yes." | tee -a configure.log +else + echo "Checking for off64_t... No." | tee -a configure.log + echo >> configure.log + cat > $test.c < +int main(void) { + fseeko(NULL, 0, 0); + return 0; +} +EOF + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for fseeko... Yes." | tee -a configure.log + else + CFLAGS="${CFLAGS} -DNO_FSEEKO" + SFLAGS="${SFLAGS} -DNO_FSEEKO" + echo "Checking for fseeko... No." | tee -a configure.log + fi +fi + +echo >> configure.log + +# check for strerror() for use by gz* functions +cat > $test.c < +#include +int main() { return strlen(strerror(errno)); } +EOF +if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for strerror... Yes." | tee -a configure.log +else + CFLAGS="${CFLAGS} -DNO_STRERROR" + SFLAGS="${SFLAGS} -DNO_STRERROR" + echo "Checking for strerror... No." | tee -a configure.log +fi + +# copy clean zconf.h for subsequent edits +cp -p zconf.h.in zconf.h + +echo >> configure.log + +# check for unistd.h and save result in zconf.h +cat > $test.c < +int main() { return 0; } +EOF +if try $CC -c $CFLAGS $test.c; then + sed < zconf.h "/^#ifdef HAVE_UNISTD_H.* may be/s/def HAVE_UNISTD_H\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo "Checking for unistd.h... Yes." | tee -a configure.log +else + echo "Checking for unistd.h... No." | tee -a configure.log +fi + +echo >> configure.log + +# check for stdarg.h and save result in zconf.h +cat > $test.c < +int main() { return 0; } +EOF +if try $CC -c $CFLAGS $test.c; then + sed < zconf.h "/^#ifdef HAVE_STDARG_H.* may be/s/def HAVE_STDARG_H\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo "Checking for stdarg.h... Yes." | tee -a configure.log +else + echo "Checking for stdarg.h... No." | tee -a configure.log +fi + +# if the z_ prefix was requested, save that in zconf.h +if test $zprefix -eq 1; then + sed < zconf.h "/#ifdef Z_PREFIX.* may be/s/def Z_PREFIX\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo >> configure.log + echo "Using z_ prefix on all symbols." | tee -a configure.log +fi + +# if --solo compilation was requested, save that in zconf.h and remove gz stuff from object lists +if test $solo -eq 1; then + sed '/#define ZCONF_H/a\ +#define Z_SOLO + +' < zconf.h > zconf.temp.h + mv zconf.temp.h zconf.h +OBJC='$(OBJZ)' +PIC_OBJC='$(PIC_OBJZ)' +fi + +# if code coverage testing was requested, use older gcc if defined, e.g. "gcc-4.2" on Mac OS X +if test $cover -eq 1; then + CFLAGS="${CFLAGS} -fprofile-arcs -ftest-coverage" + if test -n "$GCC_CLASSIC"; then + CC=$GCC_CLASSIC + fi +fi + +echo >> configure.log + +# conduct a series of tests to resolve eight possible cases of using "vs" or "s" printf functions +# (using stdarg or not), with or without "n" (proving size of buffer), and with or without a +# return value. The most secure result is vsnprintf() with a return value. snprintf() with a +# return value is secure as well, but then gzprintf() will be limited to 20 arguments. +cat > $test.c < +#include +#include "zconf.h" +int main() +{ +#ifndef STDC + choke me +#endif + return 0; +} +EOF +if try $CC -c $CFLAGS $test.c; then + echo "Checking whether to use vs[n]printf() or s[n]printf()... using vs[n]printf()." | tee -a configure.log + + echo >> configure.log + cat > $test.c < +#include +int mytest(const char *fmt, ...) +{ + char buf[20]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return 0; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for vsnprintf() in stdio.h... Yes." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +#include +int mytest(const char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return n; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of vsnprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_vsnprintf_void" + SFLAGS="$SFLAGS -DHAS_vsnprintf_void" + echo "Checking for return value of vsnprintf()... No." | tee -a configure.log + echo " WARNING: apparently vsnprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + else + CFLAGS="$CFLAGS -DNO_vsnprintf" + SFLAGS="$SFLAGS -DNO_vsnprintf" + echo "Checking for vsnprintf() in stdio.h... No." | tee -a configure.log + echo " WARNING: vsnprintf() not found, falling back to vsprintf(). zlib" | tee -a configure.log + echo " can build but will be open to possible buffer-overflow security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +#include +int mytest(const char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + va_start(ap, fmt); + n = vsprintf(buf, fmt, ap); + va_end(ap); + return n; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of vsprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_vsprintf_void" + SFLAGS="$SFLAGS -DHAS_vsprintf_void" + echo "Checking for return value of vsprintf()... No." | tee -a configure.log + echo " WARNING: apparently vsprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + fi +else + echo "Checking whether to use vs[n]printf() or s[n]printf()... using s[n]printf()." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + snprintf(buf, sizeof(buf), "%s", "foo"); + return 0; +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for snprintf() in stdio.h... Yes." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + return snprintf(buf, sizeof(buf), "%s", "foo"); +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of snprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_snprintf_void" + SFLAGS="$SFLAGS -DHAS_snprintf_void" + echo "Checking for return value of snprintf()... No." | tee -a configure.log + echo " WARNING: apparently snprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + else + CFLAGS="$CFLAGS -DNO_snprintf" + SFLAGS="$SFLAGS -DNO_snprintf" + echo "Checking for snprintf() in stdio.h... No." | tee -a configure.log + echo " WARNING: snprintf() not found, falling back to sprintf(). zlib" | tee -a configure.log + echo " can build but will be open to possible buffer-overflow security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + return sprintf(buf, "%s", "foo"); +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of sprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_sprintf_void" + SFLAGS="$SFLAGS -DHAS_sprintf_void" + echo "Checking for return value of sprintf()... No." | tee -a configure.log + echo " WARNING: apparently sprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + fi +fi + +# see if we can hide zlib internal symbols that are linked between separate source files +if test "$gcc" -eq 1; then + echo >> configure.log + cat > $test.c <> configure.log +echo ALL = $ALL >> configure.log +echo AR = $AR >> configure.log +echo ARFLAGS = $ARFLAGS >> configure.log +echo CC = $CC >> configure.log +echo CFLAGS = $CFLAGS >> configure.log +echo CPP = $CPP >> configure.log +echo EXE = $EXE >> configure.log +echo LDCONFIG = $LDCONFIG >> configure.log +echo LDFLAGS = $LDFLAGS >> configure.log +echo LDSHARED = $LDSHARED >> configure.log +echo LDSHAREDLIBC = $LDSHAREDLIBC >> configure.log +echo OBJC = $OBJC >> configure.log +echo PIC_OBJC = $PIC_OBJC >> configure.log +echo RANLIB = $RANLIB >> configure.log +echo SFLAGS = $SFLAGS >> configure.log +echo SHAREDLIB = $SHAREDLIB >> configure.log +echo SHAREDLIBM = $SHAREDLIBM >> configure.log +echo SHAREDLIBV = $SHAREDLIBV >> configure.log +echo STATICLIB = $STATICLIB >> configure.log +echo TEST = $TEST >> configure.log +echo VER = $VER >> configure.log +echo Z_U4 = $Z_U4 >> configure.log +echo exec_prefix = $exec_prefix >> configure.log +echo includedir = $includedir >> configure.log +echo libdir = $libdir >> configure.log +echo mandir = $mandir >> configure.log +echo prefix = $prefix >> configure.log +echo sharedlibdir = $sharedlibdir >> configure.log +echo uname = $uname >> configure.log + +# udpate Makefile with the configure results +sed < Makefile.in " +/^CC *=/s#=.*#=$CC# +/^CFLAGS *=/s#=.*#=$CFLAGS# +/^SFLAGS *=/s#=.*#=$SFLAGS# +/^LDFLAGS *=/s#=.*#=$LDFLAGS# +/^LDSHARED *=/s#=.*#=$LDSHARED# +/^CPP *=/s#=.*#=$CPP# +/^STATICLIB *=/s#=.*#=$STATICLIB# +/^SHAREDLIB *=/s#=.*#=$SHAREDLIB# +/^SHAREDLIBV *=/s#=.*#=$SHAREDLIBV# +/^SHAREDLIBM *=/s#=.*#=$SHAREDLIBM# +/^AR *=/s#=.*#=$AR# +/^ARFLAGS *=/s#=.*#=$ARFLAGS# +/^RANLIB *=/s#=.*#=$RANLIB# +/^LDCONFIG *=/s#=.*#=$LDCONFIG# +/^LDSHAREDLIBC *=/s#=.*#=$LDSHAREDLIBC# +/^EXE *=/s#=.*#=$EXE# +/^prefix *=/s#=.*#=$prefix# +/^exec_prefix *=/s#=.*#=$exec_prefix# +/^libdir *=/s#=.*#=$libdir# +/^sharedlibdir *=/s#=.*#=$sharedlibdir# +/^includedir *=/s#=.*#=$includedir# +/^mandir *=/s#=.*#=$mandir# +/^OBJC *=/s#=.*#= $OBJC# +/^PIC_OBJC *=/s#=.*#= $PIC_OBJC# +/^all: */s#:.*#: $ALL# +/^test: */s#:.*#: $TEST# +" > Makefile + +# create zlib.pc with the configure results +sed < zlib.pc.in " +/^CC *=/s#=.*#=$CC# +/^CFLAGS *=/s#=.*#=$CFLAGS# +/^CPP *=/s#=.*#=$CPP# +/^LDSHARED *=/s#=.*#=$LDSHARED# +/^STATICLIB *=/s#=.*#=$STATICLIB# +/^SHAREDLIB *=/s#=.*#=$SHAREDLIB# +/^SHAREDLIBV *=/s#=.*#=$SHAREDLIBV# +/^SHAREDLIBM *=/s#=.*#=$SHAREDLIBM# +/^AR *=/s#=.*#=$AR# +/^ARFLAGS *=/s#=.*#=$ARFLAGS# +/^RANLIB *=/s#=.*#=$RANLIB# +/^EXE *=/s#=.*#=$EXE# +/^prefix *=/s#=.*#=$prefix# +/^exec_prefix *=/s#=.*#=$exec_prefix# +/^libdir *=/s#=.*#=$libdir# +/^sharedlibdir *=/s#=.*#=$sharedlibdir# +/^includedir *=/s#=.*#=$includedir# +/^mandir *=/s#=.*#=$mandir# +/^LDFLAGS *=/s#=.*#=$LDFLAGS# +" | sed -e " +s/\@VERSION\@/$VER/g; +" > zlib.pc + +# done +leave 0 diff --git a/zlib/zlib/contrib/README.contrib b/zlib/zlib/contrib/README.contrib new file mode 100644 index 00000000..c66349b7 --- /dev/null +++ b/zlib/zlib/contrib/README.contrib @@ -0,0 +1,78 @@ +All files under this contrib directory are UNSUPPORTED. There were +provided by users of zlib and were not tested by the authors of zlib. +Use at your own risk. Please contact the authors of the contributions +for help about these, not the zlib authors. Thanks. + + +ada/ by Dmitriy Anisimkov + Support for Ada + See http://zlib-ada.sourceforge.net/ + +amd64/ by Mikhail Teterin + asm code for AMD64 + See patch at http://www.freebsd.org/cgi/query-pr.cgi?pr=bin/96393 + +asm686/ by Brian Raiter + asm code for Pentium and PPro/PII, using the AT&T (GNU as) syntax + See http://www.muppetlabs.com/~breadbox/software/assembly.html + +blast/ by Mark Adler + Decompressor for output of PKWare Data Compression Library (DCL) + +delphi/ by Cosmin Truta + Support for Delphi and C++ Builder + +dotzlib/ by Henrik Ravn + Support for Microsoft .Net and Visual C++ .Net + +gcc_gvmat64/by Gilles Vollant + GCC Version of x86 64-bit (AMD64 and Intel EM64t) code for x64 + assembler to replace longest_match() and inflate_fast() + +infback9/ by Mark Adler + Unsupported diffs to infback to decode the deflate64 format + +inflate86/ by Chris Anderson + Tuned x86 gcc asm code to replace inflate_fast() + +iostream/ by Kevin Ruland + A C++ I/O streams interface to the zlib gz* functions + +iostream2/ by Tyge Løvset + Another C++ I/O streams interface + +iostream3/ by Ludwig Schwardt + and Kevin Ruland + Yet another C++ I/O streams interface + +masmx64/ by Gilles Vollant + x86 64-bit (AMD64 and Intel EM64t) code for x64 assembler to + replace longest_match() and inflate_fast(), also masm x86 + 64-bits translation of Chris Anderson inflate_fast() + +masmx86/ by Gilles Vollant + x86 asm code to replace longest_match() and inflate_fast(), + for Visual C++ and MASM (32 bits). + Based on Brian Raiter (asm686) and Chris Anderson (inflate86) + +minizip/ by Gilles Vollant + Mini zip and unzip based on zlib + Includes Zip64 support by Mathias Svensson + See http://www.winimage.com/zLibDll/unzip.html + +pascal/ by Bob Dellaca et al. + Support for Pascal + +puff/ by Mark Adler + Small, low memory usage inflate. Also serves to provide an + unambiguous description of the deflate format. + +testzlib/ by Gilles Vollant + Example of the use of zlib + +untgz/ by Pedro A. Aranda Gutierrez + A very simple tar.gz file extractor using zlib + +vstudio/ by Gilles Vollant + Building a minizip-enhanced zlib with Microsoft Visual Studio + Includes vc11 from kreuzerkrieg and vc12 from davispuh diff --git a/zlib/zlib/contrib/ada/buffer_demo.adb b/zlib/zlib/contrib/ada/buffer_demo.adb new file mode 100644 index 00000000..46b86381 --- /dev/null +++ b/zlib/zlib/contrib/ada/buffer_demo.adb @@ -0,0 +1,106 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- +-- +-- $Id: buffer_demo.adb,v 1.3 2004/09/06 06:55:35 vagul Exp $ + +-- This demo program provided by Dr Steve Sangwine +-- +-- Demonstration of a problem with Zlib-Ada (already fixed) when a buffer +-- of exactly the correct size is used for decompressed data, and the last +-- few bytes passed in to Zlib are checksum bytes. + +-- This program compresses a string of text, and then decompresses the +-- compressed text into a buffer of the same size as the original text. + +with Ada.Streams; use Ada.Streams; +with Ada.Text_IO; + +with ZLib; use ZLib; + +procedure Buffer_Demo is + EOL : Character renames ASCII.LF; + Text : constant String + := "Four score and seven years ago our fathers brought forth," & EOL & + "upon this continent, a new nation, conceived in liberty," & EOL & + "and dedicated to the proposition that `all men are created equal'."; + + Source : Stream_Element_Array (1 .. Text'Length); + for Source'Address use Text'Address; + +begin + Ada.Text_IO.Put (Text); + Ada.Text_IO.New_Line; + Ada.Text_IO.Put_Line + ("Uncompressed size : " & Positive'Image (Text'Length) & " bytes"); + + declare + Compressed_Data : Stream_Element_Array (1 .. Text'Length); + L : Stream_Element_Offset; + begin + Compress : declare + Compressor : Filter_Type; + I : Stream_Element_Offset; + begin + Deflate_Init (Compressor); + + -- Compress the whole of T at once. + + Translate (Compressor, Source, I, Compressed_Data, L, Finish); + pragma Assert (I = Source'Last); + + Close (Compressor); + + Ada.Text_IO.Put_Line + ("Compressed size : " + & Stream_Element_Offset'Image (L) & " bytes"); + end Compress; + + -- Now we decompress the data, passing short blocks of data to Zlib + -- (because this demonstrates the problem - the last block passed will + -- contain checksum information and there will be no output, only a + -- check inside Zlib that the checksum is correct). + + Decompress : declare + Decompressor : Filter_Type; + + Uncompressed_Data : Stream_Element_Array (1 .. Text'Length); + + Block_Size : constant := 4; + -- This makes sure that the last block contains + -- only Adler checksum data. + + P : Stream_Element_Offset := Compressed_Data'First - 1; + O : Stream_Element_Offset; + begin + Inflate_Init (Decompressor); + + loop + Translate + (Decompressor, + Compressed_Data + (P + 1 .. Stream_Element_Offset'Min (P + Block_Size, L)), + P, + Uncompressed_Data + (Total_Out (Decompressor) + 1 .. Uncompressed_Data'Last), + O, + No_Flush); + + Ada.Text_IO.Put_Line + ("Total in : " & Count'Image (Total_In (Decompressor)) & + ", out : " & Count'Image (Total_Out (Decompressor))); + + exit when P = L; + end loop; + + Ada.Text_IO.New_Line; + Ada.Text_IO.Put_Line + ("Decompressed text matches original text : " + & Boolean'Image (Uncompressed_Data = Source)); + end Decompress; + end; +end Buffer_Demo; diff --git a/zlib/zlib/contrib/ada/mtest.adb b/zlib/zlib/contrib/ada/mtest.adb new file mode 100644 index 00000000..c4dfd080 --- /dev/null +++ b/zlib/zlib/contrib/ada/mtest.adb @@ -0,0 +1,156 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- +-- Continuous test for ZLib multithreading. If the test would fail +-- we should provide thread safe allocation routines for the Z_Stream. +-- +-- $Id: mtest.adb,v 1.4 2004/07/23 07:49:54 vagul Exp $ + +with ZLib; +with Ada.Streams; +with Ada.Numerics.Discrete_Random; +with Ada.Text_IO; +with Ada.Exceptions; +with Ada.Task_Identification; + +procedure MTest is + use Ada.Streams; + use ZLib; + + Stop : Boolean := False; + + pragma Atomic (Stop); + + subtype Visible_Symbols is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is + new Ada.Numerics.Discrete_Random (Visible_Symbols); + + task type Test_Task; + + task body Test_Task is + Buffer : Stream_Element_Array (1 .. 100_000); + Gen : Random_Elements.Generator; + + Buffer_First : Stream_Element_Offset; + Compare_First : Stream_Element_Offset; + + Deflate : Filter_Type; + Inflate : Filter_Type; + + procedure Further (Item : in Stream_Element_Array); + + procedure Read_Buffer + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + + ------------- + -- Further -- + ------------- + + procedure Further (Item : in Stream_Element_Array) is + + procedure Compare (Item : in Stream_Element_Array); + + ------------- + -- Compare -- + ------------- + + procedure Compare (Item : in Stream_Element_Array) is + Next_First : Stream_Element_Offset := Compare_First + Item'Length; + begin + if Buffer (Compare_First .. Next_First - 1) /= Item then + raise Program_Error; + end if; + + Compare_First := Next_First; + end Compare; + + procedure Compare_Write is new ZLib.Write (Write => Compare); + begin + Compare_Write (Inflate, Item, No_Flush); + end Further; + + ----------------- + -- Read_Buffer -- + ----------------- + + procedure Read_Buffer + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset) + is + Buff_Diff : Stream_Element_Offset := Buffer'Last - Buffer_First; + Next_First : Stream_Element_Offset; + begin + if Item'Length <= Buff_Diff then + Last := Item'Last; + + Next_First := Buffer_First + Item'Length; + + Item := Buffer (Buffer_First .. Next_First - 1); + + Buffer_First := Next_First; + else + Last := Item'First + Buff_Diff; + Item (Item'First .. Last) := Buffer (Buffer_First .. Buffer'Last); + Buffer_First := Buffer'Last + 1; + end if; + end Read_Buffer; + + procedure Translate is new Generic_Translate + (Data_In => Read_Buffer, + Data_Out => Further); + + begin + Random_Elements.Reset (Gen); + + Buffer := (others => 20); + + Main : loop + for J in Buffer'Range loop + Buffer (J) := Random_Elements.Random (Gen); + + Deflate_Init (Deflate); + Inflate_Init (Inflate); + + Buffer_First := Buffer'First; + Compare_First := Buffer'First; + + Translate (Deflate); + + if Compare_First /= Buffer'Last + 1 then + raise Program_Error; + end if; + + Ada.Text_IO.Put_Line + (Ada.Task_Identification.Image + (Ada.Task_Identification.Current_Task) + & Stream_Element_Offset'Image (J) + & ZLib.Count'Image (Total_Out (Deflate))); + + Close (Deflate); + Close (Inflate); + + exit Main when Stop; + end loop; + end loop Main; + exception + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + Stop := True; + end Test_Task; + + Test : array (1 .. 4) of Test_Task; + + pragma Unreferenced (Test); + + Dummy : Character; + +begin + Ada.Text_IO.Get_Immediate (Dummy); + Stop := True; +end MTest; diff --git a/zlib/zlib/contrib/ada/read.adb b/zlib/zlib/contrib/ada/read.adb new file mode 100644 index 00000000..1f2efbfe --- /dev/null +++ b/zlib/zlib/contrib/ada/read.adb @@ -0,0 +1,156 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: read.adb,v 1.8 2004/05/31 10:53:40 vagul Exp $ + +-- Test/demo program for the generic read interface. + +with Ada.Numerics.Discrete_Random; +with Ada.Streams; +with Ada.Text_IO; + +with ZLib; + +procedure Read is + + use Ada.Streams; + + ------------------------------------ + -- Test configuration parameters -- + ------------------------------------ + + File_Size : Stream_Element_Offset := 100_000; + + Continuous : constant Boolean := False; + -- If this constant is True, the test would be repeated again and again, + -- with increment File_Size for every iteration. + + Header : constant ZLib.Header_Type := ZLib.Default; + -- Do not use Header other than Default in ZLib versions 1.1.4 and older. + + Init_Random : constant := 8; + -- We are using the same random sequence, in case of we catch bug, + -- so we would be able to reproduce it. + + -- End -- + + Pack_Size : Stream_Element_Offset; + Offset : Stream_Element_Offset; + + Filter : ZLib.Filter_Type; + + subtype Visible_Symbols + is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is new + Ada.Numerics.Discrete_Random (Visible_Symbols); + + Gen : Random_Elements.Generator; + Period : constant Stream_Element_Offset := 200; + -- Period constant variable for random generator not to be very random. + -- Bigger period, harder random. + + Read_Buffer : Stream_Element_Array (1 .. 2048); + Read_First : Stream_Element_Offset; + Read_Last : Stream_Element_Offset; + + procedure Reset; + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + -- this procedure is for generic instantiation of + -- ZLib.Read + -- reading data from the File_In. + + procedure Read is new ZLib.Read + (Read, + Read_Buffer, + Rest_First => Read_First, + Rest_Last => Read_Last); + + ---------- + -- Read -- + ---------- + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Last := Stream_Element_Offset'Min + (Item'Last, + Item'First + File_Size - Offset); + + for J in Item'First .. Last loop + if J < Item'First + Period then + Item (J) := Random_Elements.Random (Gen); + else + Item (J) := Item (J - Period); + end if; + + Offset := Offset + 1; + end loop; + end Read; + + ----------- + -- Reset -- + ----------- + + procedure Reset is + begin + Random_Elements.Reset (Gen, Init_Random); + Pack_Size := 0; + Offset := 1; + Read_First := Read_Buffer'Last + 1; + Read_Last := Read_Buffer'Last; + end Reset; + +begin + Ada.Text_IO.Put_Line ("ZLib " & ZLib.Version); + + loop + for Level in ZLib.Compression_Level'Range loop + + Ada.Text_IO.Put ("Level =" + & ZLib.Compression_Level'Image (Level)); + + -- Deflate using generic instantiation. + + ZLib.Deflate_Init + (Filter, + Level, + Header => Header); + + Reset; + + Ada.Text_IO.Put + (Stream_Element_Offset'Image (File_Size) & " ->"); + + loop + declare + Buffer : Stream_Element_Array (1 .. 1024); + Last : Stream_Element_Offset; + begin + Read (Filter, Buffer, Last); + + Pack_Size := Pack_Size + Last - Buffer'First + 1; + + exit when Last < Buffer'Last; + end; + end loop; + + Ada.Text_IO.Put_Line (Stream_Element_Offset'Image (Pack_Size)); + + ZLib.Close (Filter); + end loop; + + exit when not Continuous; + + File_Size := File_Size + 1; + end loop; +end Read; diff --git a/zlib/zlib/contrib/ada/readme.txt b/zlib/zlib/contrib/ada/readme.txt new file mode 100644 index 00000000..ce4d2cad --- /dev/null +++ b/zlib/zlib/contrib/ada/readme.txt @@ -0,0 +1,65 @@ + ZLib for Ada thick binding (ZLib.Ada) + Release 1.3 + +ZLib.Ada is a thick binding interface to the popular ZLib data +compression library, available at http://www.gzip.org/zlib/. +It provides Ada-style access to the ZLib C library. + + + Here are the main changes since ZLib.Ada 1.2: + +- Attension: ZLib.Read generic routine have a initialization requirement + for Read_Last parameter now. It is a bit incompartible with previous version, + but extends functionality, we could use new parameters Allow_Read_Some and + Flush now. + +- Added Is_Open routines to ZLib and ZLib.Streams packages. + +- Add pragma Assert to check Stream_Element is 8 bit. + +- Fix extraction to buffer with exact known decompressed size. Error reported by + Steve Sangwine. + +- Fix definition of ULong (changed to unsigned_long), fix regression on 64 bits + computers. Patch provided by Pascal Obry. + +- Add Status_Error exception definition. + +- Add pragma Assertion that Ada.Streams.Stream_Element size is 8 bit. + + + How to build ZLib.Ada under GNAT + +You should have the ZLib library already build on your computer, before +building ZLib.Ada. Make the directory of ZLib.Ada sources current and +issue the command: + + gnatmake test -largs -L -lz + +Or use the GNAT project file build for GNAT 3.15 or later: + + gnatmake -Pzlib.gpr -L + + + How to build ZLib.Ada under Aonix ObjectAda for Win32 7.2.2 + +1. Make a project with all *.ads and *.adb files from the distribution. +2. Build the libz.a library from the ZLib C sources. +3. Rename libz.a to z.lib. +4. Add the library z.lib to the project. +5. Add the libc.lib library from the ObjectAda distribution to the project. +6. Build the executable using test.adb as a main procedure. + + + How to use ZLib.Ada + +The source files test.adb and read.adb are small demo programs that show +the main functionality of ZLib.Ada. + +The routines from the package specifications are commented. + + +Homepage: http://zlib-ada.sourceforge.net/ +Author: Dmitriy Anisimkov + +Contributors: Pascal Obry , Steve Sangwine diff --git a/zlib/zlib/contrib/ada/test.adb b/zlib/zlib/contrib/ada/test.adb new file mode 100644 index 00000000..90773acf --- /dev/null +++ b/zlib/zlib/contrib/ada/test.adb @@ -0,0 +1,463 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: test.adb,v 1.17 2003/08/12 12:13:30 vagul Exp $ + +-- The program has a few aims. +-- 1. Test ZLib.Ada95 thick binding functionality. +-- 2. Show the example of use main functionality of the ZLib.Ada95 binding. +-- 3. Build this program automatically compile all ZLib.Ada95 packages under +-- GNAT Ada95 compiler. + +with ZLib.Streams; +with Ada.Streams.Stream_IO; +with Ada.Numerics.Discrete_Random; + +with Ada.Text_IO; + +with Ada.Calendar; + +procedure Test is + + use Ada.Streams; + use Stream_IO; + + ------------------------------------ + -- Test configuration parameters -- + ------------------------------------ + + File_Size : Count := 100_000; + Continuous : constant Boolean := False; + + Header : constant ZLib.Header_Type := ZLib.Default; + -- ZLib.None; + -- ZLib.Auto; + -- ZLib.GZip; + -- Do not use Header other then Default in ZLib versions 1.1.4 + -- and older. + + Strategy : constant ZLib.Strategy_Type := ZLib.Default_Strategy; + Init_Random : constant := 10; + + -- End -- + + In_File_Name : constant String := "testzlib.in"; + -- Name of the input file + + Z_File_Name : constant String := "testzlib.zlb"; + -- Name of the compressed file. + + Out_File_Name : constant String := "testzlib.out"; + -- Name of the decompressed file. + + File_In : File_Type; + File_Out : File_Type; + File_Back : File_Type; + File_Z : ZLib.Streams.Stream_Type; + + Filter : ZLib.Filter_Type; + + Time_Stamp : Ada.Calendar.Time; + + procedure Generate_File; + -- Generate file of spetsified size with some random data. + -- The random data is repeatable, for the good compression. + + procedure Compare_Streams + (Left, Right : in out Root_Stream_Type'Class); + -- The procedure compearing data in 2 streams. + -- It is for compare data before and after compression/decompression. + + procedure Compare_Files (Left, Right : String); + -- Compare files. Based on the Compare_Streams. + + procedure Copy_Streams + (Source, Target : in out Root_Stream_Type'Class; + Buffer_Size : in Stream_Element_Offset := 1024); + -- Copying data from one stream to another. It is for test stream + -- interface of the library. + + procedure Data_In + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + -- this procedure is for generic instantiation of + -- ZLib.Generic_Translate. + -- reading data from the File_In. + + procedure Data_Out (Item : in Stream_Element_Array); + -- this procedure is for generic instantiation of + -- ZLib.Generic_Translate. + -- writing data to the File_Out. + + procedure Stamp; + -- Store the timestamp to the local variable. + + procedure Print_Statistic (Msg : String; Data_Size : ZLib.Count); + -- Print the time statistic with the message. + + procedure Translate is new ZLib.Generic_Translate + (Data_In => Data_In, + Data_Out => Data_Out); + -- This procedure is moving data from File_In to File_Out + -- with compression or decompression, depend on initialization of + -- Filter parameter. + + ------------------- + -- Compare_Files -- + ------------------- + + procedure Compare_Files (Left, Right : String) is + Left_File, Right_File : File_Type; + begin + Open (Left_File, In_File, Left); + Open (Right_File, In_File, Right); + Compare_Streams (Stream (Left_File).all, Stream (Right_File).all); + Close (Left_File); + Close (Right_File); + end Compare_Files; + + --------------------- + -- Compare_Streams -- + --------------------- + + procedure Compare_Streams + (Left, Right : in out Ada.Streams.Root_Stream_Type'Class) + is + Left_Buffer, Right_Buffer : Stream_Element_Array (0 .. 16#FFF#); + Left_Last, Right_Last : Stream_Element_Offset; + begin + loop + Read (Left, Left_Buffer, Left_Last); + Read (Right, Right_Buffer, Right_Last); + + if Left_Last /= Right_Last then + Ada.Text_IO.Put_Line ("Compare error :" + & Stream_Element_Offset'Image (Left_Last) + & " /= " + & Stream_Element_Offset'Image (Right_Last)); + + raise Constraint_Error; + + elsif Left_Buffer (0 .. Left_Last) + /= Right_Buffer (0 .. Right_Last) + then + Ada.Text_IO.Put_Line ("ERROR: IN and OUT files is not equal."); + raise Constraint_Error; + + end if; + + exit when Left_Last < Left_Buffer'Last; + end loop; + end Compare_Streams; + + ------------------ + -- Copy_Streams -- + ------------------ + + procedure Copy_Streams + (Source, Target : in out Ada.Streams.Root_Stream_Type'Class; + Buffer_Size : in Stream_Element_Offset := 1024) + is + Buffer : Stream_Element_Array (1 .. Buffer_Size); + Last : Stream_Element_Offset; + begin + loop + Read (Source, Buffer, Last); + Write (Target, Buffer (1 .. Last)); + + exit when Last < Buffer'Last; + end loop; + end Copy_Streams; + + ------------- + -- Data_In -- + ------------- + + procedure Data_In + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Read (File_In, Item, Last); + end Data_In; + + -------------- + -- Data_Out -- + -------------- + + procedure Data_Out (Item : in Stream_Element_Array) is + begin + Write (File_Out, Item); + end Data_Out; + + ------------------- + -- Generate_File -- + ------------------- + + procedure Generate_File is + subtype Visible_Symbols is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is + new Ada.Numerics.Discrete_Random (Visible_Symbols); + + Gen : Random_Elements.Generator; + Buffer : Stream_Element_Array := (1 .. 77 => 16#20#) & 10; + + Buffer_Count : constant Count := File_Size / Buffer'Length; + -- Number of same buffers in the packet. + + Density : constant Count := 30; -- from 0 to Buffer'Length - 2; + + procedure Fill_Buffer (J, D : in Count); + -- Change the part of the buffer. + + ----------------- + -- Fill_Buffer -- + ----------------- + + procedure Fill_Buffer (J, D : in Count) is + begin + for K in 0 .. D loop + Buffer + (Stream_Element_Offset ((J + K) mod (Buffer'Length - 1) + 1)) + := Random_Elements.Random (Gen); + + end loop; + end Fill_Buffer; + + begin + Random_Elements.Reset (Gen, Init_Random); + + Create (File_In, Out_File, In_File_Name); + + Fill_Buffer (1, Buffer'Length - 2); + + for J in 1 .. Buffer_Count loop + Write (File_In, Buffer); + + Fill_Buffer (J, Density); + end loop; + + -- fill remain size. + + Write + (File_In, + Buffer + (1 .. Stream_Element_Offset + (File_Size - Buffer'Length * Buffer_Count))); + + Flush (File_In); + Close (File_In); + end Generate_File; + + --------------------- + -- Print_Statistic -- + --------------------- + + procedure Print_Statistic (Msg : String; Data_Size : ZLib.Count) is + use Ada.Calendar; + use Ada.Text_IO; + + package Count_IO is new Integer_IO (ZLib.Count); + + Curr_Dur : Duration := Clock - Time_Stamp; + begin + Put (Msg); + + Set_Col (20); + Ada.Text_IO.Put ("size ="); + + Count_IO.Put + (Data_Size, + Width => Stream_IO.Count'Image (File_Size)'Length); + + Put_Line (" duration =" & Duration'Image (Curr_Dur)); + end Print_Statistic; + + ----------- + -- Stamp -- + ----------- + + procedure Stamp is + begin + Time_Stamp := Ada.Calendar.Clock; + end Stamp; + +begin + Ada.Text_IO.Put_Line ("ZLib " & ZLib.Version); + + loop + Generate_File; + + for Level in ZLib.Compression_Level'Range loop + + Ada.Text_IO.Put_Line ("Level =" + & ZLib.Compression_Level'Image (Level)); + + -- Test generic interface. + Open (File_In, In_File, In_File_Name); + Create (File_Out, Out_File, Z_File_Name); + + Stamp; + + -- Deflate using generic instantiation. + + ZLib.Deflate_Init + (Filter => Filter, + Level => Level, + Strategy => Strategy, + Header => Header); + + Translate (Filter); + Print_Statistic ("Generic compress", ZLib.Total_Out (Filter)); + ZLib.Close (Filter); + + Close (File_In); + Close (File_Out); + + Open (File_In, In_File, Z_File_Name); + Create (File_Out, Out_File, Out_File_Name); + + Stamp; + + -- Inflate using generic instantiation. + + ZLib.Inflate_Init (Filter, Header => Header); + + Translate (Filter); + Print_Statistic ("Generic decompress", ZLib.Total_Out (Filter)); + + ZLib.Close (Filter); + + Close (File_In); + Close (File_Out); + + Compare_Files (In_File_Name, Out_File_Name); + + -- Test stream interface. + + -- Compress to the back stream. + + Open (File_In, In_File, In_File_Name); + Create (File_Back, Out_File, Z_File_Name); + + Stamp; + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.Out_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => True, + Level => Level, + Strategy => Strategy, + Header => Header); + + Copy_Streams + (Source => Stream (File_In).all, + Target => File_Z); + + -- Flushing internal buffers to the back stream. + + ZLib.Streams.Flush (File_Z, ZLib.Finish); + + Print_Statistic ("Write compress", + ZLib.Streams.Write_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + + Close (File_In); + Close (File_Back); + + -- Compare reading from original file and from + -- decompression stream. + + Open (File_In, In_File, In_File_Name); + Open (File_Back, In_File, Z_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.In_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => True, + Header => Header); + + Stamp; + Compare_Streams (Stream (File_In).all, File_Z); + + Print_Statistic ("Read decompress", + ZLib.Streams.Read_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + Close (File_In); + Close (File_Back); + + -- Compress by reading from compression stream. + + Open (File_Back, In_File, In_File_Name); + Create (File_Out, Out_File, Z_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.In_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => False, + Level => Level, + Strategy => Strategy, + Header => Header); + + Stamp; + Copy_Streams + (Source => File_Z, + Target => Stream (File_Out).all); + + Print_Statistic ("Read compress", + ZLib.Streams.Read_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + + Close (File_Out); + Close (File_Back); + + -- Decompress to decompression stream. + + Open (File_In, In_File, Z_File_Name); + Create (File_Back, Out_File, Out_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.Out_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => False, + Header => Header); + + Stamp; + + Copy_Streams + (Source => Stream (File_In).all, + Target => File_Z); + + Print_Statistic ("Write decompress", + ZLib.Streams.Write_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + Close (File_In); + Close (File_Back); + + Compare_Files (In_File_Name, Out_File_Name); + end loop; + + Ada.Text_IO.Put_Line (Count'Image (File_Size) & " Ok."); + + exit when not Continuous; + + File_Size := File_Size + 1; + end loop; +end Test; diff --git a/zlib/zlib/contrib/ada/zlib-streams.adb b/zlib/zlib/contrib/ada/zlib-streams.adb new file mode 100644 index 00000000..b6497bae --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib-streams.adb @@ -0,0 +1,225 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-streams.adb,v 1.10 2004/05/31 10:53:40 vagul Exp $ + +with Ada.Unchecked_Deallocation; + +package body ZLib.Streams is + + ----------- + -- Close -- + ----------- + + procedure Close (Stream : in out Stream_Type) is + procedure Free is new Ada.Unchecked_Deallocation + (Stream_Element_Array, Buffer_Access); + begin + if Stream.Mode = Out_Stream or Stream.Mode = Duplex then + -- We should flush the data written by the writer. + + Flush (Stream, Finish); + + Close (Stream.Writer); + end if; + + if Stream.Mode = In_Stream or Stream.Mode = Duplex then + Close (Stream.Reader); + Free (Stream.Buffer); + end if; + end Close; + + ------------ + -- Create -- + ------------ + + procedure Create + (Stream : out Stream_Type; + Mode : in Stream_Mode; + Back : in Stream_Access; + Back_Compressed : in Boolean; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Header : in Header_Type := Default; + Read_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + Write_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size) + is + + subtype Buffer_Subtype is Stream_Element_Array (1 .. Read_Buffer_Size); + + procedure Init_Filter + (Filter : in out Filter_Type; + Compress : in Boolean); + + ----------------- + -- Init_Filter -- + ----------------- + + procedure Init_Filter + (Filter : in out Filter_Type; + Compress : in Boolean) is + begin + if Compress then + Deflate_Init + (Filter, Level, Strategy, Header => Header); + else + Inflate_Init (Filter, Header => Header); + end if; + end Init_Filter; + + begin + Stream.Back := Back; + Stream.Mode := Mode; + + if Mode = Out_Stream or Mode = Duplex then + Init_Filter (Stream.Writer, Back_Compressed); + Stream.Buffer_Size := Write_Buffer_Size; + else + Stream.Buffer_Size := 0; + end if; + + if Mode = In_Stream or Mode = Duplex then + Init_Filter (Stream.Reader, not Back_Compressed); + + Stream.Buffer := new Buffer_Subtype; + Stream.Rest_First := Stream.Buffer'Last + 1; + Stream.Rest_Last := Stream.Buffer'Last; + end if; + end Create; + + ----------- + -- Flush -- + ----------- + + procedure Flush + (Stream : in out Stream_Type; + Mode : in Flush_Mode := Sync_Flush) + is + Buffer : Stream_Element_Array (1 .. Stream.Buffer_Size); + Last : Stream_Element_Offset; + begin + loop + Flush (Stream.Writer, Buffer, Last, Mode); + + Ada.Streams.Write (Stream.Back.all, Buffer (1 .. Last)); + + exit when Last < Buffer'Last; + end loop; + end Flush; + + ------------- + -- Is_Open -- + ------------- + + function Is_Open (Stream : Stream_Type) return Boolean is + begin + return Is_Open (Stream.Reader) or else Is_Open (Stream.Writer); + end Is_Open; + + ---------- + -- Read -- + ---------- + + procedure Read + (Stream : in out Stream_Type; + Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) + is + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + + ---------- + -- Read -- + ---------- + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Ada.Streams.Read (Stream.Back.all, Item, Last); + end Read; + + procedure Read is new ZLib.Read + (Read => Read, + Buffer => Stream.Buffer.all, + Rest_First => Stream.Rest_First, + Rest_Last => Stream.Rest_Last); + + begin + Read (Stream.Reader, Item, Last); + end Read; + + ------------------- + -- Read_Total_In -- + ------------------- + + function Read_Total_In (Stream : in Stream_Type) return Count is + begin + return Total_In (Stream.Reader); + end Read_Total_In; + + -------------------- + -- Read_Total_Out -- + -------------------- + + function Read_Total_Out (Stream : in Stream_Type) return Count is + begin + return Total_Out (Stream.Reader); + end Read_Total_Out; + + ----------- + -- Write -- + ----------- + + procedure Write + (Stream : in out Stream_Type; + Item : in Stream_Element_Array) + is + + procedure Write (Item : in Stream_Element_Array); + + ----------- + -- Write -- + ----------- + + procedure Write (Item : in Stream_Element_Array) is + begin + Ada.Streams.Write (Stream.Back.all, Item); + end Write; + + procedure Write is new ZLib.Write + (Write => Write, + Buffer_Size => Stream.Buffer_Size); + + begin + Write (Stream.Writer, Item, No_Flush); + end Write; + + -------------------- + -- Write_Total_In -- + -------------------- + + function Write_Total_In (Stream : in Stream_Type) return Count is + begin + return Total_In (Stream.Writer); + end Write_Total_In; + + --------------------- + -- Write_Total_Out -- + --------------------- + + function Write_Total_Out (Stream : in Stream_Type) return Count is + begin + return Total_Out (Stream.Writer); + end Write_Total_Out; + +end ZLib.Streams; diff --git a/zlib/zlib/contrib/ada/zlib-streams.ads b/zlib/zlib/contrib/ada/zlib-streams.ads new file mode 100644 index 00000000..f0193c6b --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib-streams.ads @@ -0,0 +1,114 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-streams.ads,v 1.12 2004/05/31 10:53:40 vagul Exp $ + +package ZLib.Streams is + + type Stream_Mode is (In_Stream, Out_Stream, Duplex); + + type Stream_Access is access all Ada.Streams.Root_Stream_Type'Class; + + type Stream_Type is + new Ada.Streams.Root_Stream_Type with private; + + procedure Read + (Stream : in out Stream_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + + procedure Write + (Stream : in out Stream_Type; + Item : in Ada.Streams.Stream_Element_Array); + + procedure Flush + (Stream : in out Stream_Type; + Mode : in Flush_Mode := Sync_Flush); + -- Flush the written data to the back stream, + -- all data placed to the compressor is flushing to the Back stream. + -- Should not be used untill necessary, becouse it is decreasing + -- compression. + + function Read_Total_In (Stream : in Stream_Type) return Count; + pragma Inline (Read_Total_In); + -- Return total number of bytes read from back stream so far. + + function Read_Total_Out (Stream : in Stream_Type) return Count; + pragma Inline (Read_Total_Out); + -- Return total number of bytes read so far. + + function Write_Total_In (Stream : in Stream_Type) return Count; + pragma Inline (Write_Total_In); + -- Return total number of bytes written so far. + + function Write_Total_Out (Stream : in Stream_Type) return Count; + pragma Inline (Write_Total_Out); + -- Return total number of bytes written to the back stream. + + procedure Create + (Stream : out Stream_Type; + Mode : in Stream_Mode; + Back : in Stream_Access; + Back_Compressed : in Boolean; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Header : in Header_Type := Default; + Read_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + Write_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size); + -- Create the Comression/Decompression stream. + -- If mode is In_Stream then Write operation is disabled. + -- If mode is Out_Stream then Read operation is disabled. + + -- If Back_Compressed is true then + -- Data written to the Stream is compressing to the Back stream + -- and data read from the Stream is decompressed data from the Back stream. + + -- If Back_Compressed is false then + -- Data written to the Stream is decompressing to the Back stream + -- and data read from the Stream is compressed data from the Back stream. + + -- !!! When the Need_Header is False ZLib-Ada is using undocumented + -- ZLib 1.1.4 functionality to do not create/wait for ZLib headers. + + function Is_Open (Stream : Stream_Type) return Boolean; + + procedure Close (Stream : in out Stream_Type); + +private + + use Ada.Streams; + + type Buffer_Access is access all Stream_Element_Array; + + type Stream_Type + is new Root_Stream_Type with + record + Mode : Stream_Mode; + + Buffer : Buffer_Access; + Rest_First : Stream_Element_Offset; + Rest_Last : Stream_Element_Offset; + -- Buffer for Read operation. + -- We need to have this buffer in the record + -- becouse not all read data from back stream + -- could be processed during the read operation. + + Buffer_Size : Stream_Element_Offset; + -- Buffer size for write operation. + -- We do not need to have this buffer + -- in the record becouse all data could be + -- processed in the write operation. + + Back : Stream_Access; + Reader : Filter_Type; + Writer : Filter_Type; + end record; + +end ZLib.Streams; diff --git a/zlib/zlib/contrib/ada/zlib-thin.adb b/zlib/zlib/contrib/ada/zlib-thin.adb new file mode 100644 index 00000000..0ca4a712 --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib-thin.adb @@ -0,0 +1,141 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-thin.adb,v 1.8 2003/12/14 18:27:31 vagul Exp $ + +package body ZLib.Thin is + + ZLIB_VERSION : constant Chars_Ptr := zlibVersion; + + Z_Stream_Size : constant Int := Z_Stream'Size / System.Storage_Unit; + + -------------- + -- Avail_In -- + -------------- + + function Avail_In (Strm : in Z_Stream) return UInt is + begin + return Strm.Avail_In; + end Avail_In; + + --------------- + -- Avail_Out -- + --------------- + + function Avail_Out (Strm : in Z_Stream) return UInt is + begin + return Strm.Avail_Out; + end Avail_Out; + + ------------------ + -- Deflate_Init -- + ------------------ + + function Deflate_Init + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int) + return Int is + begin + return deflateInit2 + (strm, + level, + method, + windowBits, + memLevel, + strategy, + ZLIB_VERSION, + Z_Stream_Size); + end Deflate_Init; + + ------------------ + -- Inflate_Init -- + ------------------ + + function Inflate_Init (strm : Z_Streamp; windowBits : Int) return Int is + begin + return inflateInit2 (strm, windowBits, ZLIB_VERSION, Z_Stream_Size); + end Inflate_Init; + + ------------------------ + -- Last_Error_Message -- + ------------------------ + + function Last_Error_Message (Strm : in Z_Stream) return String is + use Interfaces.C.Strings; + begin + if Strm.msg = Null_Ptr then + return ""; + else + return Value (Strm.msg); + end if; + end Last_Error_Message; + + ------------ + -- Set_In -- + ------------ + + procedure Set_In + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt) is + begin + Strm.Next_In := Buffer; + Strm.Avail_In := Size; + end Set_In; + + ------------------ + -- Set_Mem_Func -- + ------------------ + + procedure Set_Mem_Func + (Strm : in out Z_Stream; + Opaque : in Voidp; + Alloc : in alloc_func; + Free : in free_func) is + begin + Strm.opaque := Opaque; + Strm.zalloc := Alloc; + Strm.zfree := Free; + end Set_Mem_Func; + + ------------- + -- Set_Out -- + ------------- + + procedure Set_Out + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt) is + begin + Strm.Next_Out := Buffer; + Strm.Avail_Out := Size; + end Set_Out; + + -------------- + -- Total_In -- + -------------- + + function Total_In (Strm : in Z_Stream) return ULong is + begin + return Strm.Total_In; + end Total_In; + + --------------- + -- Total_Out -- + --------------- + + function Total_Out (Strm : in Z_Stream) return ULong is + begin + return Strm.Total_Out; + end Total_Out; + +end ZLib.Thin; diff --git a/zlib/zlib/contrib/ada/zlib-thin.ads b/zlib/zlib/contrib/ada/zlib-thin.ads new file mode 100644 index 00000000..d4407eb8 --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib-thin.ads @@ -0,0 +1,450 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-thin.ads,v 1.11 2004/07/23 06:33:11 vagul Exp $ + +with Interfaces.C.Strings; + +with System; + +private package ZLib.Thin is + + -- From zconf.h + + MAX_MEM_LEVEL : constant := 9; -- zconf.h:105 + -- zconf.h:105 + MAX_WBITS : constant := 15; -- zconf.h:115 + -- 32K LZ77 window + -- zconf.h:115 + SEEK_SET : constant := 8#0000#; -- zconf.h:244 + -- Seek from beginning of file. + -- zconf.h:244 + SEEK_CUR : constant := 1; -- zconf.h:245 + -- Seek from current position. + -- zconf.h:245 + SEEK_END : constant := 2; -- zconf.h:246 + -- Set file pointer to EOF plus "offset" + -- zconf.h:246 + + type Byte is new Interfaces.C.unsigned_char; -- 8 bits + -- zconf.h:214 + type UInt is new Interfaces.C.unsigned; -- 16 bits or more + -- zconf.h:216 + type Int is new Interfaces.C.int; + + type ULong is new Interfaces.C.unsigned_long; -- 32 bits or more + -- zconf.h:217 + subtype Chars_Ptr is Interfaces.C.Strings.chars_ptr; + + type ULong_Access is access ULong; + type Int_Access is access Int; + + subtype Voidp is System.Address; -- zconf.h:232 + + subtype Byte_Access is Voidp; + + Nul : constant Voidp := System.Null_Address; + -- end from zconf + + Z_NO_FLUSH : constant := 8#0000#; -- zlib.h:125 + -- zlib.h:125 + Z_PARTIAL_FLUSH : constant := 1; -- zlib.h:126 + -- will be removed, use + -- Z_SYNC_FLUSH instead + -- zlib.h:126 + Z_SYNC_FLUSH : constant := 2; -- zlib.h:127 + -- zlib.h:127 + Z_FULL_FLUSH : constant := 3; -- zlib.h:128 + -- zlib.h:128 + Z_FINISH : constant := 4; -- zlib.h:129 + -- zlib.h:129 + Z_OK : constant := 8#0000#; -- zlib.h:132 + -- zlib.h:132 + Z_STREAM_END : constant := 1; -- zlib.h:133 + -- zlib.h:133 + Z_NEED_DICT : constant := 2; -- zlib.h:134 + -- zlib.h:134 + Z_ERRNO : constant := -1; -- zlib.h:135 + -- zlib.h:135 + Z_STREAM_ERROR : constant := -2; -- zlib.h:136 + -- zlib.h:136 + Z_DATA_ERROR : constant := -3; -- zlib.h:137 + -- zlib.h:137 + Z_MEM_ERROR : constant := -4; -- zlib.h:138 + -- zlib.h:138 + Z_BUF_ERROR : constant := -5; -- zlib.h:139 + -- zlib.h:139 + Z_VERSION_ERROR : constant := -6; -- zlib.h:140 + -- zlib.h:140 + Z_NO_COMPRESSION : constant := 8#0000#; -- zlib.h:145 + -- zlib.h:145 + Z_BEST_SPEED : constant := 1; -- zlib.h:146 + -- zlib.h:146 + Z_BEST_COMPRESSION : constant := 9; -- zlib.h:147 + -- zlib.h:147 + Z_DEFAULT_COMPRESSION : constant := -1; -- zlib.h:148 + -- zlib.h:148 + Z_FILTERED : constant := 1; -- zlib.h:151 + -- zlib.h:151 + Z_HUFFMAN_ONLY : constant := 2; -- zlib.h:152 + -- zlib.h:152 + Z_DEFAULT_STRATEGY : constant := 8#0000#; -- zlib.h:153 + -- zlib.h:153 + Z_BINARY : constant := 8#0000#; -- zlib.h:156 + -- zlib.h:156 + Z_ASCII : constant := 1; -- zlib.h:157 + -- zlib.h:157 + Z_UNKNOWN : constant := 2; -- zlib.h:158 + -- zlib.h:158 + Z_DEFLATED : constant := 8; -- zlib.h:161 + -- zlib.h:161 + Z_NULL : constant := 8#0000#; -- zlib.h:164 + -- for initializing zalloc, zfree, opaque + -- zlib.h:164 + type gzFile is new Voidp; -- zlib.h:646 + + type Z_Stream is private; + + type Z_Streamp is access all Z_Stream; -- zlib.h:89 + + type alloc_func is access function + (Opaque : Voidp; + Items : UInt; + Size : UInt) + return Voidp; -- zlib.h:63 + + type free_func is access procedure (opaque : Voidp; address : Voidp); + + function zlibVersion return Chars_Ptr; + + function Deflate (strm : Z_Streamp; flush : Int) return Int; + + function DeflateEnd (strm : Z_Streamp) return Int; + + function Inflate (strm : Z_Streamp; flush : Int) return Int; + + function InflateEnd (strm : Z_Streamp) return Int; + + function deflateSetDictionary + (strm : Z_Streamp; + dictionary : Byte_Access; + dictLength : UInt) + return Int; + + function deflateCopy (dest : Z_Streamp; source : Z_Streamp) return Int; + -- zlib.h:478 + + function deflateReset (strm : Z_Streamp) return Int; -- zlib.h:495 + + function deflateParams + (strm : Z_Streamp; + level : Int; + strategy : Int) + return Int; -- zlib.h:506 + + function inflateSetDictionary + (strm : Z_Streamp; + dictionary : Byte_Access; + dictLength : UInt) + return Int; -- zlib.h:548 + + function inflateSync (strm : Z_Streamp) return Int; -- zlib.h:565 + + function inflateReset (strm : Z_Streamp) return Int; -- zlib.h:580 + + function compress + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong) + return Int; -- zlib.h:601 + + function compress2 + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong; + level : Int) + return Int; -- zlib.h:615 + + function uncompress + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong) + return Int; + + function gzopen (path : Chars_Ptr; mode : Chars_Ptr) return gzFile; + + function gzdopen (fd : Int; mode : Chars_Ptr) return gzFile; + + function gzsetparams + (file : gzFile; + level : Int; + strategy : Int) + return Int; + + function gzread + (file : gzFile; + buf : Voidp; + len : UInt) + return Int; + + function gzwrite + (file : in gzFile; + buf : in Voidp; + len : in UInt) + return Int; + + function gzprintf (file : in gzFile; format : in Chars_Ptr) return Int; + + function gzputs (file : in gzFile; s : in Chars_Ptr) return Int; + + function gzgets + (file : gzFile; + buf : Chars_Ptr; + len : Int) + return Chars_Ptr; + + function gzputc (file : gzFile; char : Int) return Int; + + function gzgetc (file : gzFile) return Int; + + function gzflush (file : gzFile; flush : Int) return Int; + + function gzseek + (file : gzFile; + offset : Int; + whence : Int) + return Int; + + function gzrewind (file : gzFile) return Int; + + function gztell (file : gzFile) return Int; + + function gzeof (file : gzFile) return Int; + + function gzclose (file : gzFile) return Int; + + function gzerror (file : gzFile; errnum : Int_Access) return Chars_Ptr; + + function adler32 + (adler : ULong; + buf : Byte_Access; + len : UInt) + return ULong; + + function crc32 + (crc : ULong; + buf : Byte_Access; + len : UInt) + return ULong; + + function deflateInit + (strm : Z_Streamp; + level : Int; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function deflateInit2 + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function Deflate_Init + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int) + return Int; + pragma Inline (Deflate_Init); + + function inflateInit + (strm : Z_Streamp; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function inflateInit2 + (strm : in Z_Streamp; + windowBits : in Int; + version : in Chars_Ptr; + stream_size : in Int) + return Int; + + function inflateBackInit + (strm : in Z_Streamp; + windowBits : in Int; + window : in Byte_Access; + version : in Chars_Ptr; + stream_size : in Int) + return Int; + -- Size of window have to be 2**windowBits. + + function Inflate_Init (strm : Z_Streamp; windowBits : Int) return Int; + pragma Inline (Inflate_Init); + + function zError (err : Int) return Chars_Ptr; + + function inflateSyncPoint (z : Z_Streamp) return Int; + + function get_crc_table return ULong_Access; + + -- Interface to the available fields of the z_stream structure. + -- The application must update next_in and avail_in when avail_in has + -- dropped to zero. It must update next_out and avail_out when avail_out + -- has dropped to zero. The application must initialize zalloc, zfree and + -- opaque before calling the init function. + + procedure Set_In + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt); + pragma Inline (Set_In); + + procedure Set_Out + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt); + pragma Inline (Set_Out); + + procedure Set_Mem_Func + (Strm : in out Z_Stream; + Opaque : in Voidp; + Alloc : in alloc_func; + Free : in free_func); + pragma Inline (Set_Mem_Func); + + function Last_Error_Message (Strm : in Z_Stream) return String; + pragma Inline (Last_Error_Message); + + function Avail_Out (Strm : in Z_Stream) return UInt; + pragma Inline (Avail_Out); + + function Avail_In (Strm : in Z_Stream) return UInt; + pragma Inline (Avail_In); + + function Total_In (Strm : in Z_Stream) return ULong; + pragma Inline (Total_In); + + function Total_Out (Strm : in Z_Stream) return ULong; + pragma Inline (Total_Out); + + function inflateCopy + (dest : in Z_Streamp; + Source : in Z_Streamp) + return Int; + + function compressBound (Source_Len : in ULong) return ULong; + + function deflateBound + (Strm : in Z_Streamp; + Source_Len : in ULong) + return ULong; + + function gzungetc (C : in Int; File : in gzFile) return Int; + + function zlibCompileFlags return ULong; + +private + + type Z_Stream is record -- zlib.h:68 + Next_In : Voidp := Nul; -- next input byte + Avail_In : UInt := 0; -- number of bytes available at next_in + Total_In : ULong := 0; -- total nb of input bytes read so far + Next_Out : Voidp := Nul; -- next output byte should be put there + Avail_Out : UInt := 0; -- remaining free space at next_out + Total_Out : ULong := 0; -- total nb of bytes output so far + msg : Chars_Ptr; -- last error message, NULL if no error + state : Voidp; -- not visible by applications + zalloc : alloc_func := null; -- used to allocate the internal state + zfree : free_func := null; -- used to free the internal state + opaque : Voidp; -- private data object passed to + -- zalloc and zfree + data_type : Int; -- best guess about the data type: + -- ascii or binary + adler : ULong; -- adler32 value of the uncompressed + -- data + reserved : ULong; -- reserved for future use + end record; + + pragma Convention (C, Z_Stream); + + pragma Import (C, zlibVersion, "zlibVersion"); + pragma Import (C, Deflate, "deflate"); + pragma Import (C, DeflateEnd, "deflateEnd"); + pragma Import (C, Inflate, "inflate"); + pragma Import (C, InflateEnd, "inflateEnd"); + pragma Import (C, deflateSetDictionary, "deflateSetDictionary"); + pragma Import (C, deflateCopy, "deflateCopy"); + pragma Import (C, deflateReset, "deflateReset"); + pragma Import (C, deflateParams, "deflateParams"); + pragma Import (C, inflateSetDictionary, "inflateSetDictionary"); + pragma Import (C, inflateSync, "inflateSync"); + pragma Import (C, inflateReset, "inflateReset"); + pragma Import (C, compress, "compress"); + pragma Import (C, compress2, "compress2"); + pragma Import (C, uncompress, "uncompress"); + pragma Import (C, gzopen, "gzopen"); + pragma Import (C, gzdopen, "gzdopen"); + pragma Import (C, gzsetparams, "gzsetparams"); + pragma Import (C, gzread, "gzread"); + pragma Import (C, gzwrite, "gzwrite"); + pragma Import (C, gzprintf, "gzprintf"); + pragma Import (C, gzputs, "gzputs"); + pragma Import (C, gzgets, "gzgets"); + pragma Import (C, gzputc, "gzputc"); + pragma Import (C, gzgetc, "gzgetc"); + pragma Import (C, gzflush, "gzflush"); + pragma Import (C, gzseek, "gzseek"); + pragma Import (C, gzrewind, "gzrewind"); + pragma Import (C, gztell, "gztell"); + pragma Import (C, gzeof, "gzeof"); + pragma Import (C, gzclose, "gzclose"); + pragma Import (C, gzerror, "gzerror"); + pragma Import (C, adler32, "adler32"); + pragma Import (C, crc32, "crc32"); + pragma Import (C, deflateInit, "deflateInit_"); + pragma Import (C, inflateInit, "inflateInit_"); + pragma Import (C, deflateInit2, "deflateInit2_"); + pragma Import (C, inflateInit2, "inflateInit2_"); + pragma Import (C, zError, "zError"); + pragma Import (C, inflateSyncPoint, "inflateSyncPoint"); + pragma Import (C, get_crc_table, "get_crc_table"); + + -- since zlib 1.2.0: + + pragma Import (C, inflateCopy, "inflateCopy"); + pragma Import (C, compressBound, "compressBound"); + pragma Import (C, deflateBound, "deflateBound"); + pragma Import (C, gzungetc, "gzungetc"); + pragma Import (C, zlibCompileFlags, "zlibCompileFlags"); + + pragma Import (C, inflateBackInit, "inflateBackInit_"); + + -- I stopped binding the inflateBack routines, becouse realize that + -- it does not support zlib and gzip headers for now, and have no + -- symmetric deflateBack routines. + -- ZLib-Ada is symmetric regarding deflate/inflate data transformation + -- and has a similar generic callback interface for the + -- deflate/inflate transformation based on the regular Deflate/Inflate + -- routines. + + -- pragma Import (C, inflateBack, "inflateBack"); + -- pragma Import (C, inflateBackEnd, "inflateBackEnd"); + +end ZLib.Thin; diff --git a/zlib/zlib/contrib/ada/zlib.adb b/zlib/zlib/contrib/ada/zlib.adb new file mode 100644 index 00000000..8b6fd686 --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib.adb @@ -0,0 +1,701 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib.adb,v 1.31 2004/09/06 06:53:19 vagul Exp $ + +with Ada.Exceptions; +with Ada.Unchecked_Conversion; +with Ada.Unchecked_Deallocation; + +with Interfaces.C.Strings; + +with ZLib.Thin; + +package body ZLib is + + use type Thin.Int; + + type Z_Stream is new Thin.Z_Stream; + + type Return_Code_Enum is + (OK, + STREAM_END, + NEED_DICT, + ERRNO, + STREAM_ERROR, + DATA_ERROR, + MEM_ERROR, + BUF_ERROR, + VERSION_ERROR); + + type Flate_Step_Function is access + function (Strm : in Thin.Z_Streamp; Flush : in Thin.Int) return Thin.Int; + pragma Convention (C, Flate_Step_Function); + + type Flate_End_Function is access + function (Ctrm : in Thin.Z_Streamp) return Thin.Int; + pragma Convention (C, Flate_End_Function); + + type Flate_Type is record + Step : Flate_Step_Function; + Done : Flate_End_Function; + end record; + + subtype Footer_Array is Stream_Element_Array (1 .. 8); + + Simple_GZip_Header : constant Stream_Element_Array (1 .. 10) + := (16#1f#, 16#8b#, -- Magic header + 16#08#, -- Z_DEFLATED + 16#00#, -- Flags + 16#00#, 16#00#, 16#00#, 16#00#, -- Time + 16#00#, -- XFlags + 16#03# -- OS code + ); + -- The simplest gzip header is not for informational, but just for + -- gzip format compatibility. + -- Note that some code below is using assumption + -- Simple_GZip_Header'Last > Footer_Array'Last, so do not make + -- Simple_GZip_Header'Last <= Footer_Array'Last. + + Return_Code : constant array (Thin.Int range <>) of Return_Code_Enum + := (0 => OK, + 1 => STREAM_END, + 2 => NEED_DICT, + -1 => ERRNO, + -2 => STREAM_ERROR, + -3 => DATA_ERROR, + -4 => MEM_ERROR, + -5 => BUF_ERROR, + -6 => VERSION_ERROR); + + Flate : constant array (Boolean) of Flate_Type + := (True => (Step => Thin.Deflate'Access, + Done => Thin.DeflateEnd'Access), + False => (Step => Thin.Inflate'Access, + Done => Thin.InflateEnd'Access)); + + Flush_Finish : constant array (Boolean) of Flush_Mode + := (True => Finish, False => No_Flush); + + procedure Raise_Error (Stream : in Z_Stream); + pragma Inline (Raise_Error); + + procedure Raise_Error (Message : in String); + pragma Inline (Raise_Error); + + procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int); + + procedure Free is new Ada.Unchecked_Deallocation + (Z_Stream, Z_Stream_Access); + + function To_Thin_Access is new Ada.Unchecked_Conversion + (Z_Stream_Access, Thin.Z_Streamp); + + procedure Translate_GZip + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- Separate translate routine for make gzip header. + + procedure Translate_Auto + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- translate routine without additional headers. + + ----------------- + -- Check_Error -- + ----------------- + + procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int) is + use type Thin.Int; + begin + if Code /= Thin.Z_OK then + Raise_Error + (Return_Code_Enum'Image (Return_Code (Code)) + & ": " & Last_Error_Message (Stream)); + end if; + end Check_Error; + + ----------- + -- Close -- + ----------- + + procedure Close + (Filter : in out Filter_Type; + Ignore_Error : in Boolean := False) + is + Code : Thin.Int; + begin + if not Ignore_Error and then not Is_Open (Filter) then + raise Status_Error; + end if; + + Code := Flate (Filter.Compression).Done (To_Thin_Access (Filter.Strm)); + + if Ignore_Error or else Code = Thin.Z_OK then + Free (Filter.Strm); + else + declare + Error_Message : constant String + := Last_Error_Message (Filter.Strm.all); + begin + Free (Filter.Strm); + Ada.Exceptions.Raise_Exception + (ZLib_Error'Identity, + Return_Code_Enum'Image (Return_Code (Code)) + & ": " & Error_Message); + end; + end if; + end Close; + + ----------- + -- CRC32 -- + ----------- + + function CRC32 + (CRC : in Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) + return Unsigned_32 + is + use Thin; + begin + return Unsigned_32 (crc32 (ULong (CRC), + Data'Address, + Data'Length)); + end CRC32; + + procedure CRC32 + (CRC : in out Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) is + begin + CRC := CRC32 (CRC, Data); + end CRC32; + + ------------------ + -- Deflate_Init -- + ------------------ + + procedure Deflate_Init + (Filter : in out Filter_Type; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Method : in Compression_Method := Deflated; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Memory_Level : in Memory_Level_Type := Default_Memory_Level; + Header : in Header_Type := Default) + is + use type Thin.Int; + Win_Bits : Thin.Int := Thin.Int (Window_Bits); + begin + if Is_Open (Filter) then + raise Status_Error; + end if; + + -- We allow ZLib to make header only in case of default header type. + -- Otherwise we would either do header by ourselfs, or do not do + -- header at all. + + if Header = None or else Header = GZip then + Win_Bits := -Win_Bits; + end if; + + -- For the GZip CRC calculation and make headers. + + if Header = GZip then + Filter.CRC := 0; + Filter.Offset := Simple_GZip_Header'First; + else + Filter.Offset := Simple_GZip_Header'Last + 1; + end if; + + Filter.Strm := new Z_Stream; + Filter.Compression := True; + Filter.Stream_End := False; + Filter.Header := Header; + + if Thin.Deflate_Init + (To_Thin_Access (Filter.Strm), + Level => Thin.Int (Level), + method => Thin.Int (Method), + windowBits => Win_Bits, + memLevel => Thin.Int (Memory_Level), + strategy => Thin.Int (Strategy)) /= Thin.Z_OK + then + Raise_Error (Filter.Strm.all); + end if; + end Deflate_Init; + + ----------- + -- Flush -- + ----------- + + procedure Flush + (Filter : in out Filter_Type; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + No_Data : Stream_Element_Array := (1 .. 0 => 0); + Last : Stream_Element_Offset; + begin + Translate (Filter, No_Data, Last, Out_Data, Out_Last, Flush); + end Flush; + + ----------------------- + -- Generic_Translate -- + ----------------------- + + procedure Generic_Translate + (Filter : in out ZLib.Filter_Type; + In_Buffer_Size : in Integer := Default_Buffer_Size; + Out_Buffer_Size : in Integer := Default_Buffer_Size) + is + In_Buffer : Stream_Element_Array + (1 .. Stream_Element_Offset (In_Buffer_Size)); + Out_Buffer : Stream_Element_Array + (1 .. Stream_Element_Offset (Out_Buffer_Size)); + Last : Stream_Element_Offset; + In_Last : Stream_Element_Offset; + In_First : Stream_Element_Offset; + Out_Last : Stream_Element_Offset; + begin + Main : loop + Data_In (In_Buffer, Last); + + In_First := In_Buffer'First; + + loop + Translate + (Filter => Filter, + In_Data => In_Buffer (In_First .. Last), + In_Last => In_Last, + Out_Data => Out_Buffer, + Out_Last => Out_Last, + Flush => Flush_Finish (Last < In_Buffer'First)); + + if Out_Buffer'First <= Out_Last then + Data_Out (Out_Buffer (Out_Buffer'First .. Out_Last)); + end if; + + exit Main when Stream_End (Filter); + + -- The end of in buffer. + + exit when In_Last = Last; + + In_First := In_Last + 1; + end loop; + end loop Main; + + end Generic_Translate; + + ------------------ + -- Inflate_Init -- + ------------------ + + procedure Inflate_Init + (Filter : in out Filter_Type; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Header : in Header_Type := Default) + is + use type Thin.Int; + Win_Bits : Thin.Int := Thin.Int (Window_Bits); + + procedure Check_Version; + -- Check the latest header types compatibility. + + procedure Check_Version is + begin + if Version <= "1.1.4" then + Raise_Error + ("Inflate header type " & Header_Type'Image (Header) + & " incompatible with ZLib version " & Version); + end if; + end Check_Version; + + begin + if Is_Open (Filter) then + raise Status_Error; + end if; + + case Header is + when None => + Check_Version; + + -- Inflate data without headers determined + -- by negative Win_Bits. + + Win_Bits := -Win_Bits; + when GZip => + Check_Version; + + -- Inflate gzip data defined by flag 16. + + Win_Bits := Win_Bits + 16; + when Auto => + Check_Version; + + -- Inflate with automatic detection + -- of gzip or native header defined by flag 32. + + Win_Bits := Win_Bits + 32; + when Default => null; + end case; + + Filter.Strm := new Z_Stream; + Filter.Compression := False; + Filter.Stream_End := False; + Filter.Header := Header; + + if Thin.Inflate_Init + (To_Thin_Access (Filter.Strm), Win_Bits) /= Thin.Z_OK + then + Raise_Error (Filter.Strm.all); + end if; + end Inflate_Init; + + ------------- + -- Is_Open -- + ------------- + + function Is_Open (Filter : in Filter_Type) return Boolean is + begin + return Filter.Strm /= null; + end Is_Open; + + ----------------- + -- Raise_Error -- + ----------------- + + procedure Raise_Error (Message : in String) is + begin + Ada.Exceptions.Raise_Exception (ZLib_Error'Identity, Message); + end Raise_Error; + + procedure Raise_Error (Stream : in Z_Stream) is + begin + Raise_Error (Last_Error_Message (Stream)); + end Raise_Error; + + ---------- + -- Read -- + ---------- + + procedure Read + (Filter : in out Filter_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode := No_Flush) + is + In_Last : Stream_Element_Offset; + Item_First : Ada.Streams.Stream_Element_Offset := Item'First; + V_Flush : Flush_Mode := Flush; + + begin + pragma Assert (Rest_First in Buffer'First .. Buffer'Last + 1); + pragma Assert (Rest_Last in Buffer'First - 1 .. Buffer'Last); + + loop + if Rest_Last = Buffer'First - 1 then + V_Flush := Finish; + + elsif Rest_First > Rest_Last then + Read (Buffer, Rest_Last); + Rest_First := Buffer'First; + + if Rest_Last < Buffer'First then + V_Flush := Finish; + end if; + end if; + + Translate + (Filter => Filter, + In_Data => Buffer (Rest_First .. Rest_Last), + In_Last => In_Last, + Out_Data => Item (Item_First .. Item'Last), + Out_Last => Last, + Flush => V_Flush); + + Rest_First := In_Last + 1; + + exit when Stream_End (Filter) + or else Last = Item'Last + or else (Last >= Item'First and then Allow_Read_Some); + + Item_First := Last + 1; + end loop; + end Read; + + ---------------- + -- Stream_End -- + ---------------- + + function Stream_End (Filter : in Filter_Type) return Boolean is + begin + if Filter.Header = GZip and Filter.Compression then + return Filter.Stream_End + and then Filter.Offset = Footer_Array'Last + 1; + else + return Filter.Stream_End; + end if; + end Stream_End; + + -------------- + -- Total_In -- + -------------- + + function Total_In (Filter : in Filter_Type) return Count is + begin + return Count (Thin.Total_In (To_Thin_Access (Filter.Strm).all)); + end Total_In; + + --------------- + -- Total_Out -- + --------------- + + function Total_Out (Filter : in Filter_Type) return Count is + begin + return Count (Thin.Total_Out (To_Thin_Access (Filter.Strm).all)); + end Total_Out; + + --------------- + -- Translate -- + --------------- + + procedure Translate + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) is + begin + if Filter.Header = GZip and then Filter.Compression then + Translate_GZip + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data, + Out_Last => Out_Last, + Flush => Flush); + else + Translate_Auto + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data, + Out_Last => Out_Last, + Flush => Flush); + end if; + end Translate; + + -------------------- + -- Translate_Auto -- + -------------------- + + procedure Translate_Auto + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + use type Thin.Int; + Code : Thin.Int; + + begin + if not Is_Open (Filter) then + raise Status_Error; + end if; + + if Out_Data'Length = 0 and then In_Data'Length = 0 then + raise Constraint_Error; + end if; + + Set_Out (Filter.Strm.all, Out_Data'Address, Out_Data'Length); + Set_In (Filter.Strm.all, In_Data'Address, In_Data'Length); + + Code := Flate (Filter.Compression).Step + (To_Thin_Access (Filter.Strm), + Thin.Int (Flush)); + + if Code = Thin.Z_STREAM_END then + Filter.Stream_End := True; + else + Check_Error (Filter.Strm.all, Code); + end if; + + In_Last := In_Data'Last + - Stream_Element_Offset (Avail_In (Filter.Strm.all)); + Out_Last := Out_Data'Last + - Stream_Element_Offset (Avail_Out (Filter.Strm.all)); + end Translate_Auto; + + -------------------- + -- Translate_GZip -- + -------------------- + + procedure Translate_GZip + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + Out_First : Stream_Element_Offset; + + procedure Add_Data (Data : in Stream_Element_Array); + -- Add data to stream from the Filter.Offset till necessary, + -- used for add gzip headr/footer. + + procedure Put_32 + (Item : in out Stream_Element_Array; + Data : in Unsigned_32); + pragma Inline (Put_32); + + -------------- + -- Add_Data -- + -------------- + + procedure Add_Data (Data : in Stream_Element_Array) is + Data_First : Stream_Element_Offset renames Filter.Offset; + Data_Last : Stream_Element_Offset; + Data_Len : Stream_Element_Offset; -- -1 + Out_Len : Stream_Element_Offset; -- -1 + begin + Out_First := Out_Last + 1; + + if Data_First > Data'Last then + return; + end if; + + Data_Len := Data'Last - Data_First; + Out_Len := Out_Data'Last - Out_First; + + if Data_Len <= Out_Len then + Out_Last := Out_First + Data_Len; + Data_Last := Data'Last; + else + Out_Last := Out_Data'Last; + Data_Last := Data_First + Out_Len; + end if; + + Out_Data (Out_First .. Out_Last) := Data (Data_First .. Data_Last); + + Data_First := Data_Last + 1; + Out_First := Out_Last + 1; + end Add_Data; + + ------------ + -- Put_32 -- + ------------ + + procedure Put_32 + (Item : in out Stream_Element_Array; + Data : in Unsigned_32) + is + D : Unsigned_32 := Data; + begin + for J in Item'First .. Item'First + 3 loop + Item (J) := Stream_Element (D and 16#FF#); + D := Shift_Right (D, 8); + end loop; + end Put_32; + + begin + Out_Last := Out_Data'First - 1; + + if not Filter.Stream_End then + Add_Data (Simple_GZip_Header); + + Translate_Auto + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data (Out_First .. Out_Data'Last), + Out_Last => Out_Last, + Flush => Flush); + + CRC32 (Filter.CRC, In_Data (In_Data'First .. In_Last)); + end if; + + if Filter.Stream_End and then Out_Last <= Out_Data'Last then + -- This detection method would work only when + -- Simple_GZip_Header'Last > Footer_Array'Last + + if Filter.Offset = Simple_GZip_Header'Last + 1 then + Filter.Offset := Footer_Array'First; + end if; + + declare + Footer : Footer_Array; + begin + Put_32 (Footer, Filter.CRC); + Put_32 (Footer (Footer'First + 4 .. Footer'Last), + Unsigned_32 (Total_In (Filter))); + Add_Data (Footer); + end; + end if; + end Translate_GZip; + + ------------- + -- Version -- + ------------- + + function Version return String is + begin + return Interfaces.C.Strings.Value (Thin.zlibVersion); + end Version; + + ----------- + -- Write -- + ----------- + + procedure Write + (Filter : in out Filter_Type; + Item : in Ada.Streams.Stream_Element_Array; + Flush : in Flush_Mode := No_Flush) + is + Buffer : Stream_Element_Array (1 .. Buffer_Size); + In_Last : Stream_Element_Offset; + Out_Last : Stream_Element_Offset; + In_First : Stream_Element_Offset := Item'First; + begin + if Item'Length = 0 and Flush = No_Flush then + return; + end if; + + loop + Translate + (Filter => Filter, + In_Data => Item (In_First .. Item'Last), + In_Last => In_Last, + Out_Data => Buffer, + Out_Last => Out_Last, + Flush => Flush); + + if Out_Last >= Buffer'First then + Write (Buffer (1 .. Out_Last)); + end if; + + exit when In_Last = Item'Last or Stream_End (Filter); + + In_First := In_Last + 1; + end loop; + end Write; + +end ZLib; diff --git a/zlib/zlib/contrib/ada/zlib.ads b/zlib/zlib/contrib/ada/zlib.ads new file mode 100644 index 00000000..79ffc409 --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib.ads @@ -0,0 +1,328 @@ +------------------------------------------------------------------------------ +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- This library is free software; you can redistribute it and/or modify -- +-- it under the terms of the GNU General Public License as published by -- +-- the Free Software Foundation; either version 2 of the License, or (at -- +-- your option) any later version. -- +-- -- +-- This library is distributed in the hope that it will be useful, but -- +-- WITHOUT ANY WARRANTY; without even the implied warranty of -- +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -- +-- General Public License for more details. -- +-- -- +-- You should have received a copy of the GNU General Public License -- +-- along with this library; if not, write to the Free Software Foundation, -- +-- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -- +-- -- +-- As a special exception, if other files instantiate generics from this -- +-- unit, or you link this unit with other files to produce an executable, -- +-- this unit does not by itself cause the resulting executable to be -- +-- covered by the GNU General Public License. This exception does not -- +-- however invalidate any other reasons why the executable file might be -- +-- covered by the GNU Public License. -- +------------------------------------------------------------------------------ + +-- $Id: zlib.ads,v 1.26 2004/09/06 06:53:19 vagul Exp $ + +with Ada.Streams; + +with Interfaces; + +package ZLib is + + ZLib_Error : exception; + Status_Error : exception; + + type Compression_Level is new Integer range -1 .. 9; + + type Flush_Mode is private; + + type Compression_Method is private; + + type Window_Bits_Type is new Integer range 8 .. 15; + + type Memory_Level_Type is new Integer range 1 .. 9; + + type Unsigned_32 is new Interfaces.Unsigned_32; + + type Strategy_Type is private; + + type Header_Type is (None, Auto, Default, GZip); + -- Header type usage have a some limitation for inflate. + -- See comment for Inflate_Init. + + subtype Count is Ada.Streams.Stream_Element_Count; + + Default_Memory_Level : constant Memory_Level_Type := 8; + Default_Window_Bits : constant Window_Bits_Type := 15; + + ---------------------------------- + -- Compression method constants -- + ---------------------------------- + + Deflated : constant Compression_Method; + -- Only one method allowed in this ZLib version + + --------------------------------- + -- Compression level constants -- + --------------------------------- + + No_Compression : constant Compression_Level := 0; + Best_Speed : constant Compression_Level := 1; + Best_Compression : constant Compression_Level := 9; + Default_Compression : constant Compression_Level := -1; + + -------------------------- + -- Flush mode constants -- + -------------------------- + + No_Flush : constant Flush_Mode; + -- Regular way for compression, no flush + + Partial_Flush : constant Flush_Mode; + -- Will be removed, use Z_SYNC_FLUSH instead + + Sync_Flush : constant Flush_Mode; + -- All pending output is flushed to the output buffer and the output + -- is aligned on a byte boundary, so that the decompressor can get all + -- input data available so far. (In particular avail_in is zero after the + -- call if enough output space has been provided before the call.) + -- Flushing may degrade compression for some compression algorithms and so + -- it should be used only when necessary. + + Block_Flush : constant Flush_Mode; + -- Z_BLOCK requests that inflate() stop + -- if and when it get to the next deflate block boundary. When decoding the + -- zlib or gzip format, this will cause inflate() to return immediately + -- after the header and before the first block. When doing a raw inflate, + -- inflate() will go ahead and process the first block, and will return + -- when it gets to the end of that block, or when it runs out of data. + + Full_Flush : constant Flush_Mode; + -- All output is flushed as with SYNC_FLUSH, and the compression state + -- is reset so that decompression can restart from this point if previous + -- compressed data has been damaged or if random access is desired. Using + -- Full_Flush too often can seriously degrade the compression. + + Finish : constant Flush_Mode; + -- Just for tell the compressor that input data is complete. + + ------------------------------------ + -- Compression strategy constants -- + ------------------------------------ + + -- RLE stategy could be used only in version 1.2.0 and later. + + Filtered : constant Strategy_Type; + Huffman_Only : constant Strategy_Type; + RLE : constant Strategy_Type; + Default_Strategy : constant Strategy_Type; + + Default_Buffer_Size : constant := 4096; + + type Filter_Type is tagged limited private; + -- The filter is for compression and for decompression. + -- The usage of the type is depend of its initialization. + + function Version return String; + pragma Inline (Version); + -- Return string representation of the ZLib version. + + procedure Deflate_Init + (Filter : in out Filter_Type; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Method : in Compression_Method := Deflated; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Memory_Level : in Memory_Level_Type := Default_Memory_Level; + Header : in Header_Type := Default); + -- Compressor initialization. + -- When Header parameter is Auto or Default, then default zlib header + -- would be provided for compressed data. + -- When Header is GZip, then gzip header would be set instead of + -- default header. + -- When Header is None, no header would be set for compressed data. + + procedure Inflate_Init + (Filter : in out Filter_Type; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Header : in Header_Type := Default); + -- Decompressor initialization. + -- Default header type mean that ZLib default header is expecting in the + -- input compressed stream. + -- Header type None mean that no header is expecting in the input stream. + -- GZip header type mean that GZip header is expecting in the + -- input compressed stream. + -- Auto header type mean that header type (GZip or Native) would be + -- detected automatically in the input stream. + -- Note that header types parameter values None, GZip and Auto are + -- supported for inflate routine only in ZLib versions 1.2.0.2 and later. + -- Deflate_Init is supporting all header types. + + function Is_Open (Filter : in Filter_Type) return Boolean; + pragma Inline (Is_Open); + -- Is the filter opened for compression or decompression. + + procedure Close + (Filter : in out Filter_Type; + Ignore_Error : in Boolean := False); + -- Closing the compression or decompressor. + -- If stream is closing before the complete and Ignore_Error is False, + -- The exception would be raised. + + generic + with procedure Data_In + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + with procedure Data_Out + (Item : in Ada.Streams.Stream_Element_Array); + procedure Generic_Translate + (Filter : in out Filter_Type; + In_Buffer_Size : in Integer := Default_Buffer_Size; + Out_Buffer_Size : in Integer := Default_Buffer_Size); + -- Compress/decompress data fetch from Data_In routine and pass the result + -- to the Data_Out routine. User should provide Data_In and Data_Out + -- for compression/decompression data flow. + -- Compression or decompression depend on Filter initialization. + + function Total_In (Filter : in Filter_Type) return Count; + pragma Inline (Total_In); + -- Returns total number of input bytes read so far + + function Total_Out (Filter : in Filter_Type) return Count; + pragma Inline (Total_Out); + -- Returns total number of bytes output so far + + function CRC32 + (CRC : in Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) + return Unsigned_32; + pragma Inline (CRC32); + -- Compute CRC32, it could be necessary for make gzip format + + procedure CRC32 + (CRC : in out Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array); + pragma Inline (CRC32); + -- Compute CRC32, it could be necessary for make gzip format + + ------------------------------------------------- + -- Below is more complex low level routines. -- + ------------------------------------------------- + + procedure Translate + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- Compress/decompress the In_Data buffer and place the result into + -- Out_Data. In_Last is the index of last element from In_Data accepted by + -- the Filter. Out_Last is the last element of the received data from + -- Filter. To tell the filter that incoming data are complete put the + -- Flush parameter to Finish. + + function Stream_End (Filter : in Filter_Type) return Boolean; + pragma Inline (Stream_End); + -- Return the true when the stream is complete. + + procedure Flush + (Filter : in out Filter_Type; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + pragma Inline (Flush); + -- Flushing the data from the compressor. + + generic + with procedure Write + (Item : in Ada.Streams.Stream_Element_Array); + -- User should provide this routine for accept + -- compressed/decompressed data. + + Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + -- Buffer size for Write user routine. + + procedure Write + (Filter : in out Filter_Type; + Item : in Ada.Streams.Stream_Element_Array; + Flush : in Flush_Mode := No_Flush); + -- Compress/Decompress data from Item to the generic parameter procedure + -- Write. Output buffer size could be set in Buffer_Size generic parameter. + + generic + with procedure Read + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + -- User should provide data for compression/decompression + -- thru this routine. + + Buffer : in out Ada.Streams.Stream_Element_Array; + -- Buffer for keep remaining data from the previous + -- back read. + + Rest_First, Rest_Last : in out Ada.Streams.Stream_Element_Offset; + -- Rest_First have to be initialized to Buffer'Last + 1 + -- Rest_Last have to be initialized to Buffer'Last + -- before usage. + + Allow_Read_Some : in Boolean := False; + -- Is it allowed to return Last < Item'Last before end of data. + + procedure Read + (Filter : in out Filter_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode := No_Flush); + -- Compress/Decompress data from generic parameter procedure Read to the + -- Item. User should provide Buffer and initialized Rest_First, Rest_Last + -- indicators. If Allow_Read_Some is True, Read routines could return + -- Last < Item'Last only at end of stream. + +private + + use Ada.Streams; + + pragma Assert (Ada.Streams.Stream_Element'Size = 8); + pragma Assert (Ada.Streams.Stream_Element'Modulus = 2**8); + + type Flush_Mode is new Integer range 0 .. 5; + + type Compression_Method is new Integer range 8 .. 8; + + type Strategy_Type is new Integer range 0 .. 3; + + No_Flush : constant Flush_Mode := 0; + Partial_Flush : constant Flush_Mode := 1; + Sync_Flush : constant Flush_Mode := 2; + Full_Flush : constant Flush_Mode := 3; + Finish : constant Flush_Mode := 4; + Block_Flush : constant Flush_Mode := 5; + + Filtered : constant Strategy_Type := 1; + Huffman_Only : constant Strategy_Type := 2; + RLE : constant Strategy_Type := 3; + Default_Strategy : constant Strategy_Type := 0; + + Deflated : constant Compression_Method := 8; + + type Z_Stream; + + type Z_Stream_Access is access all Z_Stream; + + type Filter_Type is tagged limited record + Strm : Z_Stream_Access; + Compression : Boolean; + Stream_End : Boolean; + Header : Header_Type; + CRC : Unsigned_32; + Offset : Stream_Element_Offset; + -- Offset for gzip header/footer output. + end record; + +end ZLib; diff --git a/zlib/zlib/contrib/ada/zlib.gpr b/zlib/zlib/contrib/ada/zlib.gpr new file mode 100644 index 00000000..296b22aa --- /dev/null +++ b/zlib/zlib/contrib/ada/zlib.gpr @@ -0,0 +1,20 @@ +project Zlib is + + for Languages use ("Ada"); + for Source_Dirs use ("."); + for Object_Dir use "."; + for Main use ("test.adb", "mtest.adb", "read.adb", "buffer_demo"); + + package Compiler is + for Default_Switches ("ada") use ("-gnatwcfilopru", "-gnatVcdfimorst", "-gnatyabcefhiklmnoprst"); + end Compiler; + + package Linker is + for Default_Switches ("ada") use ("-lz"); + end Linker; + + package Builder is + for Default_Switches ("ada") use ("-s", "-gnatQ"); + end Builder; + +end Zlib; diff --git a/zlib/zlib/contrib/amd64/amd64-match.S b/zlib/zlib/contrib/amd64/amd64-match.S new file mode 100644 index 00000000..81d4a1c9 --- /dev/null +++ b/zlib/zlib/contrib/amd64/amd64-match.S @@ -0,0 +1,452 @@ +/* + * match.S -- optimized version of longest_match() + * based on the similar work by Gilles Vollant, and Brian Raiter, written 1998 + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the BSD License. Use by owners of Che Guevarra + * parafernalia is prohibited, where possible, and highly discouraged + * elsewhere. + */ + +#ifndef NO_UNDERLINE +# define match_init _match_init +# define longest_match _longest_match +#endif + +#define scanend ebx +#define scanendw bx +#define chainlenwmask edx /* high word: current chain len low word: s->wmask */ +#define curmatch rsi +#define curmatchd esi +#define windowbestlen r8 +#define scanalign r9 +#define scanalignd r9d +#define window r10 +#define bestlen r11 +#define bestlend r11d +#define scanstart r12d +#define scanstartw r12w +#define scan r13 +#define nicematch r14d +#define limit r15 +#define limitd r15d +#define prev rcx + +/* + * The 258 is a "magic number, not a parameter -- changing it + * breaks the hell loose + */ +#define MAX_MATCH (258) +#define MIN_MATCH (3) +#define MIN_LOOKAHEAD (MAX_MATCH + MIN_MATCH + 1) +#define MAX_MATCH_8 ((MAX_MATCH + 7) & ~7) + +/* stack frame offsets */ +#define LocalVarsSize (112) +#define _chainlenwmask ( 8-LocalVarsSize)(%rsp) +#define _windowbestlen (16-LocalVarsSize)(%rsp) +#define save_r14 (24-LocalVarsSize)(%rsp) +#define save_rsi (32-LocalVarsSize)(%rsp) +#define save_rbx (40-LocalVarsSize)(%rsp) +#define save_r12 (56-LocalVarsSize)(%rsp) +#define save_r13 (64-LocalVarsSize)(%rsp) +#define save_r15 (80-LocalVarsSize)(%rsp) + + +.globl match_init, longest_match + +/* + * On AMD64 the first argument of a function (in our case -- the pointer to + * deflate_state structure) is passed in %rdi, hence our offsets below are + * all off of that. + */ + +/* you can check the structure offset by running + +#include +#include +#include "deflate.h" + +void print_depl() +{ +deflate_state ds; +deflate_state *s=&ds; +printf("size pointer=%u\n",(int)sizeof(void*)); + +printf("#define dsWSize (%3u)(%%rdi)\n",(int)(((char*)&(s->w_size))-((char*)s))); +printf("#define dsWMask (%3u)(%%rdi)\n",(int)(((char*)&(s->w_mask))-((char*)s))); +printf("#define dsWindow (%3u)(%%rdi)\n",(int)(((char*)&(s->window))-((char*)s))); +printf("#define dsPrev (%3u)(%%rdi)\n",(int)(((char*)&(s->prev))-((char*)s))); +printf("#define dsMatchLen (%3u)(%%rdi)\n",(int)(((char*)&(s->match_length))-((char*)s))); +printf("#define dsPrevMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->prev_match))-((char*)s))); +printf("#define dsStrStart (%3u)(%%rdi)\n",(int)(((char*)&(s->strstart))-((char*)s))); +printf("#define dsMatchStart (%3u)(%%rdi)\n",(int)(((char*)&(s->match_start))-((char*)s))); +printf("#define dsLookahead (%3u)(%%rdi)\n",(int)(((char*)&(s->lookahead))-((char*)s))); +printf("#define dsPrevLen (%3u)(%%rdi)\n",(int)(((char*)&(s->prev_length))-((char*)s))); +printf("#define dsMaxChainLen (%3u)(%%rdi)\n",(int)(((char*)&(s->max_chain_length))-((char*)s))); +printf("#define dsGoodMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->good_match))-((char*)s))); +printf("#define dsNiceMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->nice_match))-((char*)s))); +} + +*/ + + +/* + to compile for XCode 3.2 on MacOSX x86_64 + - run "gcc -g -c -DXCODE_MAC_X64_STRUCTURE amd64-match.S" + */ + + +#ifndef CURRENT_LINX_XCODE_MAC_X64_STRUCTURE +#define dsWSize ( 68)(%rdi) +#define dsWMask ( 76)(%rdi) +#define dsWindow ( 80)(%rdi) +#define dsPrev ( 96)(%rdi) +#define dsMatchLen (144)(%rdi) +#define dsPrevMatch (148)(%rdi) +#define dsStrStart (156)(%rdi) +#define dsMatchStart (160)(%rdi) +#define dsLookahead (164)(%rdi) +#define dsPrevLen (168)(%rdi) +#define dsMaxChainLen (172)(%rdi) +#define dsGoodMatch (188)(%rdi) +#define dsNiceMatch (192)(%rdi) + +#else + +#ifndef STRUCT_OFFSET +# define STRUCT_OFFSET (0) +#endif + + +#define dsWSize ( 56 + STRUCT_OFFSET)(%rdi) +#define dsWMask ( 64 + STRUCT_OFFSET)(%rdi) +#define dsWindow ( 72 + STRUCT_OFFSET)(%rdi) +#define dsPrev ( 88 + STRUCT_OFFSET)(%rdi) +#define dsMatchLen (136 + STRUCT_OFFSET)(%rdi) +#define dsPrevMatch (140 + STRUCT_OFFSET)(%rdi) +#define dsStrStart (148 + STRUCT_OFFSET)(%rdi) +#define dsMatchStart (152 + STRUCT_OFFSET)(%rdi) +#define dsLookahead (156 + STRUCT_OFFSET)(%rdi) +#define dsPrevLen (160 + STRUCT_OFFSET)(%rdi) +#define dsMaxChainLen (164 + STRUCT_OFFSET)(%rdi) +#define dsGoodMatch (180 + STRUCT_OFFSET)(%rdi) +#define dsNiceMatch (184 + STRUCT_OFFSET)(%rdi) + +#endif + + + + +.text + +/* uInt longest_match(deflate_state *deflatestate, IPos curmatch) */ + +longest_match: +/* + * Retrieve the function arguments. %curmatch will hold cur_match + * throughout the entire function (passed via rsi on amd64). + * rdi will hold the pointer to the deflate_state (first arg on amd64) + */ + mov %rsi, save_rsi + mov %rbx, save_rbx + mov %r12, save_r12 + mov %r13, save_r13 + mov %r14, save_r14 + mov %r15, save_r15 + +/* uInt wmask = s->w_mask; */ +/* unsigned chain_length = s->max_chain_length; */ +/* if (s->prev_length >= s->good_match) { */ +/* chain_length >>= 2; */ +/* } */ + + movl dsPrevLen, %eax + movl dsGoodMatch, %ebx + cmpl %ebx, %eax + movl dsWMask, %eax + movl dsMaxChainLen, %chainlenwmask + jl LastMatchGood + shrl $2, %chainlenwmask +LastMatchGood: + +/* chainlen is decremented once beforehand so that the function can */ +/* use the sign flag instead of the zero flag for the exit test. */ +/* It is then shifted into the high word, to make room for the wmask */ +/* value, which it will always accompany. */ + + decl %chainlenwmask + shll $16, %chainlenwmask + orl %eax, %chainlenwmask + +/* if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; */ + + movl dsNiceMatch, %eax + movl dsLookahead, %ebx + cmpl %eax, %ebx + jl LookaheadLess + movl %eax, %ebx +LookaheadLess: movl %ebx, %nicematch + +/* register Bytef *scan = s->window + s->strstart; */ + + mov dsWindow, %window + movl dsStrStart, %limitd + lea (%limit, %window), %scan + +/* Determine how many bytes the scan ptr is off from being */ +/* dword-aligned. */ + + mov %scan, %scanalign + negl %scanalignd + andl $3, %scanalignd + +/* IPos limit = s->strstart > (IPos)MAX_DIST(s) ? */ +/* s->strstart - (IPos)MAX_DIST(s) : NIL; */ + + movl dsWSize, %eax + subl $MIN_LOOKAHEAD, %eax + xorl %ecx, %ecx + subl %eax, %limitd + cmovng %ecx, %limitd + +/* int best_len = s->prev_length; */ + + movl dsPrevLen, %bestlend + +/* Store the sum of s->window + best_len in %windowbestlen locally, and in memory. */ + + lea (%window, %bestlen), %windowbestlen + mov %windowbestlen, _windowbestlen + +/* register ush scan_start = *(ushf*)scan; */ +/* register ush scan_end = *(ushf*)(scan+best_len-1); */ +/* Posf *prev = s->prev; */ + + movzwl (%scan), %scanstart + movzwl -1(%scan, %bestlen), %scanend + mov dsPrev, %prev + +/* Jump into the main loop. */ + + movl %chainlenwmask, _chainlenwmask + jmp LoopEntry + +.balign 16 + +/* do { + * match = s->window + cur_match; + * if (*(ushf*)(match+best_len-1) != scan_end || + * *(ushf*)match != scan_start) continue; + * [...] + * } while ((cur_match = prev[cur_match & wmask]) > limit + * && --chain_length != 0); + * + * Here is the inner loop of the function. The function will spend the + * majority of its time in this loop, and majority of that time will + * be spent in the first ten instructions. + */ +LookupLoop: + andl %chainlenwmask, %curmatchd + movzwl (%prev, %curmatch, 2), %curmatchd + cmpl %limitd, %curmatchd + jbe LeaveNow + subl $0x00010000, %chainlenwmask + js LeaveNow +LoopEntry: cmpw -1(%windowbestlen, %curmatch), %scanendw + jne LookupLoop + cmpw %scanstartw, (%window, %curmatch) + jne LookupLoop + +/* Store the current value of chainlen. */ + movl %chainlenwmask, _chainlenwmask + +/* %scan is the string under scrutiny, and %prev to the string we */ +/* are hoping to match it up with. In actuality, %esi and %edi are */ +/* both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and %edx is */ +/* initialized to -(MAX_MATCH_8 - scanalign). */ + + mov $(-MAX_MATCH_8), %rdx + lea (%curmatch, %window), %windowbestlen + lea MAX_MATCH_8(%windowbestlen, %scanalign), %windowbestlen + lea MAX_MATCH_8(%scan, %scanalign), %prev + +/* the prefetching below makes very little difference... */ + prefetcht1 (%windowbestlen, %rdx) + prefetcht1 (%prev, %rdx) + +/* + * Test the strings for equality, 8 bytes at a time. At the end, + * adjust %rdx so that it is offset to the exact byte that mismatched. + * + * It should be confessed that this loop usually does not represent + * much of the total running time. Replacing it with a more + * straightforward "rep cmpsb" would not drastically degrade + * performance -- unrolling it, for example, makes no difference. + */ + +#undef USE_SSE /* works, but is 6-7% slower, than non-SSE... */ + +LoopCmps: +#ifdef USE_SSE + /* Preload the SSE registers */ + movdqu (%windowbestlen, %rdx), %xmm1 + movdqu (%prev, %rdx), %xmm2 + pcmpeqb %xmm2, %xmm1 + movdqu 16(%windowbestlen, %rdx), %xmm3 + movdqu 16(%prev, %rdx), %xmm4 + pcmpeqb %xmm4, %xmm3 + movdqu 32(%windowbestlen, %rdx), %xmm5 + movdqu 32(%prev, %rdx), %xmm6 + pcmpeqb %xmm6, %xmm5 + movdqu 48(%windowbestlen, %rdx), %xmm7 + movdqu 48(%prev, %rdx), %xmm8 + pcmpeqb %xmm8, %xmm7 + + /* Check the comparisions' results */ + pmovmskb %xmm1, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + /* this is the only iteration of the loop with a possibility of having + incremented rdx by 0x108 (each loop iteration add 16*4 = 0x40 + and (0x40*4)+8=0x108 */ + add $8, %rdx + jz LenMaximum + add $8, %rdx + + + pmovmskb %xmm3, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + + add $16, %rdx + + + pmovmskb %xmm5, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + add $16, %rdx + + + pmovmskb %xmm7, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + add $16, %rdx + + jmp LoopCmps +LeaveLoopCmps: add %rax, %rdx +#else + mov (%windowbestlen, %rdx), %rax + xor (%prev, %rdx), %rax + jnz LeaveLoopCmps + + mov 8(%windowbestlen, %rdx), %rax + xor 8(%prev, %rdx), %rax + jnz LeaveLoopCmps8 + + mov 16(%windowbestlen, %rdx), %rax + xor 16(%prev, %rdx), %rax + jnz LeaveLoopCmps16 + + add $24, %rdx + jnz LoopCmps + jmp LenMaximum +# if 0 +/* + * This three-liner is tantalizingly simple, but bsf is a slow instruction, + * and the complicated alternative down below is quite a bit faster. Sad... + */ + +LeaveLoopCmps: bsf %rax, %rax /* find the first non-zero bit */ + shrl $3, %eax /* divide by 8 to get the byte */ + add %rax, %rdx +# else +LeaveLoopCmps16: + add $8, %rdx +LeaveLoopCmps8: + add $8, %rdx +LeaveLoopCmps: testl $0xFFFFFFFF, %eax /* Check the first 4 bytes */ + jnz Check16 + add $4, %rdx + shr $32, %rax +Check16: testw $0xFFFF, %ax + jnz LenLower + add $2, %rdx + shrl $16, %eax +LenLower: subb $1, %al + adc $0, %rdx +# endif +#endif + +/* Calculate the length of the match. If it is longer than MAX_MATCH, */ +/* then automatically accept it as the best possible match and leave. */ + + lea (%prev, %rdx), %rax + sub %scan, %rax + cmpl $MAX_MATCH, %eax + jge LenMaximum + +/* If the length of the match is not longer than the best match we */ +/* have so far, then forget it and return to the lookup loop. */ + + cmpl %bestlend, %eax + jg LongerMatch + mov _windowbestlen, %windowbestlen + mov dsPrev, %prev + movl _chainlenwmask, %edx + jmp LookupLoop + +/* s->match_start = cur_match; */ +/* best_len = len; */ +/* if (len >= nice_match) break; */ +/* scan_end = *(ushf*)(scan+best_len-1); */ + +LongerMatch: + movl %eax, %bestlend + movl %curmatchd, dsMatchStart + cmpl %nicematch, %eax + jge LeaveNow + + lea (%window, %bestlen), %windowbestlen + mov %windowbestlen, _windowbestlen + + movzwl -1(%scan, %rax), %scanend + mov dsPrev, %prev + movl _chainlenwmask, %chainlenwmask + jmp LookupLoop + +/* Accept the current string, with the maximum possible length. */ + +LenMaximum: + movl $MAX_MATCH, %bestlend + movl %curmatchd, dsMatchStart + +/* if ((uInt)best_len <= s->lookahead) return (uInt)best_len; */ +/* return s->lookahead; */ + +LeaveNow: + movl dsLookahead, %eax + cmpl %eax, %bestlend + cmovngl %bestlend, %eax +LookaheadRet: + +/* Restore the registers and return from whence we came. */ + + mov save_rsi, %rsi + mov save_rbx, %rbx + mov save_r12, %r12 + mov save_r13, %r13 + mov save_r14, %r14 + mov save_r15, %r15 + + ret + +match_init: ret diff --git a/zlib/zlib/contrib/asm686/README.686 b/zlib/zlib/contrib/asm686/README.686 new file mode 100644 index 00000000..a0bf3bea --- /dev/null +++ b/zlib/zlib/contrib/asm686/README.686 @@ -0,0 +1,51 @@ +This is a patched version of zlib, modified to use +Pentium-Pro-optimized assembly code in the deflation algorithm. The +files changed/added by this patch are: + +README.686 +match.S + +The speedup that this patch provides varies, depending on whether the +compiler used to build the original version of zlib falls afoul of the +PPro's speed traps. My own tests show a speedup of around 10-20% at +the default compression level, and 20-30% using -9, against a version +compiled using gcc 2.7.2.3. Your mileage may vary. + +Note that this code has been tailored for the PPro/PII in particular, +and will not perform particuarly well on a Pentium. + +If you are using an assembler other than GNU as, you will have to +translate match.S to use your assembler's syntax. (Have fun.) + +Brian Raiter +breadbox@muppetlabs.com +April, 1998 + + +Added for zlib 1.1.3: + +The patches come from +http://www.muppetlabs.com/~breadbox/software/assembly.html + +To compile zlib with this asm file, copy match.S to the zlib directory +then do: + +CFLAGS="-O3 -DASMV" ./configure +make OBJA=match.o + + +Update: + +I've been ignoring these assembly routines for years, believing that +gcc's generated code had caught up with it sometime around gcc 2.95 +and the major rearchitecting of the Pentium 4. However, I recently +learned that, despite what I believed, this code still has some life +in it. On the Pentium 4 and AMD64 chips, it continues to run about 8% +faster than the code produced by gcc 4.1. + +In acknowledgement of its continuing usefulness, I've altered the +license to match that of the rest of zlib. Share and Enjoy! + +Brian Raiter +breadbox@muppetlabs.com +April, 2007 diff --git a/zlib/zlib/contrib/asm686/match.S b/zlib/zlib/contrib/asm686/match.S new file mode 100644 index 00000000..fa421092 --- /dev/null +++ b/zlib/zlib/contrib/asm686/match.S @@ -0,0 +1,357 @@ +/* match.S -- x86 assembly version of the zlib longest_match() function. + * Optimized for the Intel 686 chips (PPro and later). + * + * Copyright (C) 1998, 2007 Brian Raiter + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef NO_UNDERLINE +#define match_init _match_init +#define longest_match _longest_match +#endif + +#define MAX_MATCH (258) +#define MIN_MATCH (3) +#define MIN_LOOKAHEAD (MAX_MATCH + MIN_MATCH + 1) +#define MAX_MATCH_8 ((MAX_MATCH + 7) & ~7) + +/* stack frame offsets */ + +#define chainlenwmask 0 /* high word: current chain len */ + /* low word: s->wmask */ +#define window 4 /* local copy of s->window */ +#define windowbestlen 8 /* s->window + bestlen */ +#define scanstart 16 /* first two bytes of string */ +#define scanend 12 /* last two bytes of string */ +#define scanalign 20 /* dword-misalignment of string */ +#define nicematch 24 /* a good enough match size */ +#define bestlen 28 /* size of best match so far */ +#define scan 32 /* ptr to string wanting match */ + +#define LocalVarsSize (36) +/* saved ebx 36 */ +/* saved edi 40 */ +/* saved esi 44 */ +/* saved ebp 48 */ +/* return address 52 */ +#define deflatestate 56 /* the function arguments */ +#define curmatch 60 + +/* All the +zlib1222add offsets are due to the addition of fields + * in zlib in the deflate_state structure since the asm code was first written + * (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). + * (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). + * if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + */ + +#define zlib1222add (8) + +#define dsWSize (36+zlib1222add) +#define dsWMask (44+zlib1222add) +#define dsWindow (48+zlib1222add) +#define dsPrev (56+zlib1222add) +#define dsMatchLen (88+zlib1222add) +#define dsPrevMatch (92+zlib1222add) +#define dsStrStart (100+zlib1222add) +#define dsMatchStart (104+zlib1222add) +#define dsLookahead (108+zlib1222add) +#define dsPrevLen (112+zlib1222add) +#define dsMaxChainLen (116+zlib1222add) +#define dsGoodMatch (132+zlib1222add) +#define dsNiceMatch (136+zlib1222add) + + +.file "match.S" + +.globl match_init, longest_match + +.text + +/* uInt longest_match(deflate_state *deflatestate, IPos curmatch) */ +.cfi_sections .debug_frame + +longest_match: + +.cfi_startproc +/* Save registers that the compiler may be using, and adjust %esp to */ +/* make room for our stack frame. */ + + pushl %ebp + .cfi_def_cfa_offset 8 + .cfi_offset ebp, -8 + pushl %edi + .cfi_def_cfa_offset 12 + pushl %esi + .cfi_def_cfa_offset 16 + pushl %ebx + .cfi_def_cfa_offset 20 + subl $LocalVarsSize, %esp + .cfi_def_cfa_offset LocalVarsSize+20 + +/* Retrieve the function arguments. %ecx will hold cur_match */ +/* throughout the entire function. %edx will hold the pointer to the */ +/* deflate_state structure during the function's setup (before */ +/* entering the main loop). */ + + movl deflatestate(%esp), %edx + movl curmatch(%esp), %ecx + +/* uInt wmask = s->w_mask; */ +/* unsigned chain_length = s->max_chain_length; */ +/* if (s->prev_length >= s->good_match) { */ +/* chain_length >>= 2; */ +/* } */ + + movl dsPrevLen(%edx), %eax + movl dsGoodMatch(%edx), %ebx + cmpl %ebx, %eax + movl dsWMask(%edx), %eax + movl dsMaxChainLen(%edx), %ebx + jl LastMatchGood + shrl $2, %ebx +LastMatchGood: + +/* chainlen is decremented once beforehand so that the function can */ +/* use the sign flag instead of the zero flag for the exit test. */ +/* It is then shifted into the high word, to make room for the wmask */ +/* value, which it will always accompany. */ + + decl %ebx + shll $16, %ebx + orl %eax, %ebx + movl %ebx, chainlenwmask(%esp) + +/* if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; */ + + movl dsNiceMatch(%edx), %eax + movl dsLookahead(%edx), %ebx + cmpl %eax, %ebx + jl LookaheadLess + movl %eax, %ebx +LookaheadLess: movl %ebx, nicematch(%esp) + +/* register Bytef *scan = s->window + s->strstart; */ + + movl dsWindow(%edx), %esi + movl %esi, window(%esp) + movl dsStrStart(%edx), %ebp + lea (%esi,%ebp), %edi + movl %edi, scan(%esp) + +/* Determine how many bytes the scan ptr is off from being */ +/* dword-aligned. */ + + movl %edi, %eax + negl %eax + andl $3, %eax + movl %eax, scanalign(%esp) + +/* IPos limit = s->strstart > (IPos)MAX_DIST(s) ? */ +/* s->strstart - (IPos)MAX_DIST(s) : NIL; */ + + movl dsWSize(%edx), %eax + subl $MIN_LOOKAHEAD, %eax + subl %eax, %ebp + jg LimitPositive + xorl %ebp, %ebp +LimitPositive: + +/* int best_len = s->prev_length; */ + + movl dsPrevLen(%edx), %eax + movl %eax, bestlen(%esp) + +/* Store the sum of s->window + best_len in %esi locally, and in %esi. */ + + addl %eax, %esi + movl %esi, windowbestlen(%esp) + +/* register ush scan_start = *(ushf*)scan; */ +/* register ush scan_end = *(ushf*)(scan+best_len-1); */ +/* Posf *prev = s->prev; */ + + movzwl (%edi), %ebx + movl %ebx, scanstart(%esp) + movzwl -1(%edi,%eax), %ebx + movl %ebx, scanend(%esp) + movl dsPrev(%edx), %edi + +/* Jump into the main loop. */ + + movl chainlenwmask(%esp), %edx + jmp LoopEntry + +.balign 16 + +/* do { + * match = s->window + cur_match; + * if (*(ushf*)(match+best_len-1) != scan_end || + * *(ushf*)match != scan_start) continue; + * [...] + * } while ((cur_match = prev[cur_match & wmask]) > limit + * && --chain_length != 0); + * + * Here is the inner loop of the function. The function will spend the + * majority of its time in this loop, and majority of that time will + * be spent in the first ten instructions. + * + * Within this loop: + * %ebx = scanend + * %ecx = curmatch + * %edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) + * %esi = windowbestlen - i.e., (window + bestlen) + * %edi = prev + * %ebp = limit + */ +LookupLoop: + andl %edx, %ecx + movzwl (%edi,%ecx,2), %ecx + cmpl %ebp, %ecx + jbe LeaveNow + subl $0x00010000, %edx + js LeaveNow +LoopEntry: movzwl -1(%esi,%ecx), %eax + cmpl %ebx, %eax + jnz LookupLoop + movl window(%esp), %eax + movzwl (%eax,%ecx), %eax + cmpl scanstart(%esp), %eax + jnz LookupLoop + +/* Store the current value of chainlen. */ + + movl %edx, chainlenwmask(%esp) + +/* Point %edi to the string under scrutiny, and %esi to the string we */ +/* are hoping to match it up with. In actuality, %esi and %edi are */ +/* both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and %edx is */ +/* initialized to -(MAX_MATCH_8 - scanalign). */ + + movl window(%esp), %esi + movl scan(%esp), %edi + addl %ecx, %esi + movl scanalign(%esp), %eax + movl $(-MAX_MATCH_8), %edx + lea MAX_MATCH_8(%edi,%eax), %edi + lea MAX_MATCH_8(%esi,%eax), %esi + +/* Test the strings for equality, 8 bytes at a time. At the end, + * adjust %edx so that it is offset to the exact byte that mismatched. + * + * We already know at this point that the first three bytes of the + * strings match each other, and they can be safely passed over before + * starting the compare loop. So what this code does is skip over 0-3 + * bytes, as much as necessary in order to dword-align the %edi + * pointer. (%esi will still be misaligned three times out of four.) + * + * It should be confessed that this loop usually does not represent + * much of the total running time. Replacing it with a more + * straightforward "rep cmpsb" would not drastically degrade + * performance. + */ +LoopCmps: + movl (%esi,%edx), %eax + xorl (%edi,%edx), %eax + jnz LeaveLoopCmps + movl 4(%esi,%edx), %eax + xorl 4(%edi,%edx), %eax + jnz LeaveLoopCmps4 + addl $8, %edx + jnz LoopCmps + jmp LenMaximum +LeaveLoopCmps4: addl $4, %edx +LeaveLoopCmps: testl $0x0000FFFF, %eax + jnz LenLower + addl $2, %edx + shrl $16, %eax +LenLower: subb $1, %al + adcl $0, %edx + +/* Calculate the length of the match. If it is longer than MAX_MATCH, */ +/* then automatically accept it as the best possible match and leave. */ + + lea (%edi,%edx), %eax + movl scan(%esp), %edi + subl %edi, %eax + cmpl $MAX_MATCH, %eax + jge LenMaximum + +/* If the length of the match is not longer than the best match we */ +/* have so far, then forget it and return to the lookup loop. */ + + movl deflatestate(%esp), %edx + movl bestlen(%esp), %ebx + cmpl %ebx, %eax + jg LongerMatch + movl windowbestlen(%esp), %esi + movl dsPrev(%edx), %edi + movl scanend(%esp), %ebx + movl chainlenwmask(%esp), %edx + jmp LookupLoop + +/* s->match_start = cur_match; */ +/* best_len = len; */ +/* if (len >= nice_match) break; */ +/* scan_end = *(ushf*)(scan+best_len-1); */ + +LongerMatch: movl nicematch(%esp), %ebx + movl %eax, bestlen(%esp) + movl %ecx, dsMatchStart(%edx) + cmpl %ebx, %eax + jge LeaveNow + movl window(%esp), %esi + addl %eax, %esi + movl %esi, windowbestlen(%esp) + movzwl -1(%edi,%eax), %ebx + movl dsPrev(%edx), %edi + movl %ebx, scanend(%esp) + movl chainlenwmask(%esp), %edx + jmp LookupLoop + +/* Accept the current string, with the maximum possible length. */ + +LenMaximum: movl deflatestate(%esp), %edx + movl $MAX_MATCH, bestlen(%esp) + movl %ecx, dsMatchStart(%edx) + +/* if ((uInt)best_len <= s->lookahead) return (uInt)best_len; */ +/* return s->lookahead; */ + +LeaveNow: + movl deflatestate(%esp), %edx + movl bestlen(%esp), %ebx + movl dsLookahead(%edx), %eax + cmpl %eax, %ebx + jg LookaheadRet + movl %ebx, %eax +LookaheadRet: + +/* Restore the stack and return from whence we came. */ + + addl $LocalVarsSize, %esp + .cfi_def_cfa_offset 20 + popl %ebx + .cfi_def_cfa_offset 16 + popl %esi + .cfi_def_cfa_offset 12 + popl %edi + .cfi_def_cfa_offset 8 + popl %ebp + .cfi_def_cfa_offset 4 +.cfi_endproc +match_init: ret diff --git a/zlib/zlib/contrib/blast/Makefile b/zlib/zlib/contrib/blast/Makefile new file mode 100644 index 00000000..9be80baf --- /dev/null +++ b/zlib/zlib/contrib/blast/Makefile @@ -0,0 +1,8 @@ +blast: blast.c blast.h + cc -DTEST -o blast blast.c + +test: blast + blast < test.pk | cmp - test.txt + +clean: + rm -f blast blast.o diff --git a/zlib/zlib/contrib/blast/README b/zlib/zlib/contrib/blast/README new file mode 100644 index 00000000..e3a60b3f --- /dev/null +++ b/zlib/zlib/contrib/blast/README @@ -0,0 +1,4 @@ +Read blast.h for purpose and usage. + +Mark Adler +madler@alumni.caltech.edu diff --git a/zlib/zlib/contrib/blast/blast.c b/zlib/zlib/contrib/blast/blast.c new file mode 100644 index 00000000..69ef0fe0 --- /dev/null +++ b/zlib/zlib/contrib/blast/blast.c @@ -0,0 +1,446 @@ +/* blast.c + * Copyright (C) 2003, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in blast.h + * version 1.2, 24 Oct 2012 + * + * blast.c decompresses data compressed by the PKWare Compression Library. + * This function provides functionality similar to the explode() function of + * the PKWare library, hence the name "blast". + * + * This decompressor is based on the excellent format description provided by + * Ben Rudiak-Gould in comp.compression on August 13, 2001. Interestingly, the + * example Ben provided in the post is incorrect. The distance 110001 should + * instead be 111000. When corrected, the example byte stream becomes: + * + * 00 04 82 24 25 8f 80 7f + * + * which decompresses to "AIAIAIAIAIAIA" (without the quotes). + */ + +/* + * Change history: + * + * 1.0 12 Feb 2003 - First version + * 1.1 16 Feb 2003 - Fixed distance check for > 4 GB uncompressed data + * 1.2 24 Oct 2012 - Add note about using binary mode in stdio + * - Fix comparisons of differently signed integers + */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "blast.h" /* prototype for blast() */ + +#define local static /* for local function definitions */ +#define MAXBITS 13 /* maximum code length */ +#define MAXWIN 4096 /* maximum window size */ + +/* input and output state */ +struct state { + /* input state */ + blast_in infun; /* input function provided by user */ + void *inhow; /* opaque information passed to infun() */ + unsigned char *in; /* next input location */ + unsigned left; /* available input at in */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; + + /* output state */ + blast_out outfun; /* output function provided by user */ + void *outhow; /* opaque information passed to outfun() */ + unsigned next; /* index of next write location in out[] */ + int first; /* true to check distances (for first 4K) */ + unsigned char out[MAXWIN]; /* output buffer and sliding window */ +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + int val; /* bit accumulator */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->left == 0) { + s->left = s->infun(s->inhow, &(s->in)); + if (s->left == 0) longjmp(s->env, 1); /* out of input */ + } + val |= (int)(*(s->in)++) << s->bitcnt; /* load eight bits */ + s->left--; + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = val >> need; + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return val & ((1 << need) - 1); +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -9 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. + * + * - The first code for the shortest length is all ones. Subsequent codes of + * the same length are simply integer decrements of the previous code. When + * moving up a length, a one bit is appended to the code. For a complete + * code, the last code of the longest length will be all zeros. To support + * this ordering, the bits pulled during decoding are inverted to apply the + * more "natural" ordering starting with all zeros and incrementing. + */ +local int decode(struct state *s, struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= (bitbuf & 1) ^ 1; /* invert code */ + bitbuf >>= 1; + count = *next++; + if (code < first + count) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) break; + if (s->left == 0) { + s->left = s->infun(s->inhow, &(s->in)); + if (s->left == 0) longjmp(s->env, 1); /* out of input */ + } + bitbuf = *(s->in)++; + s->left--; + if (left > 8) left = 8; + } + return -9; /* ran out of codes */ +} + +/* + * Given a list of repeated code lengths rep[0..n-1], where each byte is a + * count (high four bits + 1) and a code length (low four bits), generate the + * list of code lengths. This compaction reduces the size of the object code. + * Then given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + */ +local int construct(struct huffman *h, const unsigned char *rep, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + short length[256]; /* code lengths */ + + /* convert compact repeat counts into symbol bit length list */ + symbol = 0; + do { + len = *rep++; + left = (len >> 4) + 1; + len &= 15; + do { + length[symbol++] = len; + } while (--left); + } while (--n); + n = symbol; + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode PKWare Compression Library stream. + * + * Format notes: + * + * - First byte is 0 if literals are uncoded or 1 if they are coded. Second + * byte is 4, 5, or 6 for the number of extra bits in the distance code. + * This is the base-2 logarithm of the dictionary size minus six. + * + * - Compressed data is a combination of literals and length/distance pairs + * terminated by an end code. Literals are either Huffman coded or + * uncoded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - A bit preceding a literal or length/distance pair indicates which comes + * next, 0 for literals, 1 for length/distance. + * + * - If literals are uncoded, then the next eight bits are the literal, in the + * normal bit order in th stream, i.e. no bit-reversal is needed. Similarly, + * no bit reversal is needed for either the length extra bits or the distance + * extra bits. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 518 + * simply copies the last byte 518 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. + */ +local int decomp(struct state *s) +{ + int lit; /* true if literals are coded */ + int dict; /* log2(dictionary size) - 6 */ + int symbol; /* decoded symbol, extra bits for distance */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + int copy; /* copy counter */ + unsigned char *from, *to; /* copy pointers */ + static int virgin = 1; /* build tables once */ + static short litcnt[MAXBITS+1], litsym[256]; /* litcode memory */ + static short lencnt[MAXBITS+1], lensym[16]; /* lencode memory */ + static short distcnt[MAXBITS+1], distsym[64]; /* distcode memory */ + static struct huffman litcode = {litcnt, litsym}; /* length code */ + static struct huffman lencode = {lencnt, lensym}; /* length code */ + static struct huffman distcode = {distcnt, distsym};/* distance code */ + /* bit lengths of literal codes */ + static const unsigned char litlen[] = { + 11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8, + 9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5, + 7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12, + 8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27, + 44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45, + 44, 173}; + /* bit lengths of length codes 0..15 */ + static const unsigned char lenlen[] = {2, 35, 36, 53, 38, 23}; + /* bit lengths of distance codes 0..63 */ + static const unsigned char distlen[] = {2, 20, 53, 230, 247, 151, 248}; + static const short base[16] = { /* base for length codes */ + 3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264}; + static const char extra[16] = { /* extra bits for length codes */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}; + + /* set up decoding tables (once--might not be thread-safe) */ + if (virgin) { + construct(&litcode, litlen, sizeof(litlen)); + construct(&lencode, lenlen, sizeof(lenlen)); + construct(&distcode, distlen, sizeof(distlen)); + virgin = 0; + } + + /* read header */ + lit = bits(s, 8); + if (lit > 1) return -1; + dict = bits(s, 8); + if (dict < 4 || dict > 6) return -2; + + /* decode literals and length/distance pairs */ + do { + if (bits(s, 1)) { + /* get length */ + symbol = decode(s, &lencode); + len = base[symbol] + bits(s, extra[symbol]); + if (len == 519) break; /* end code */ + + /* get distance */ + symbol = len == 2 ? 2 : dict; + dist = decode(s, &distcode) << symbol; + dist += bits(s, symbol); + dist++; + if (s->first && dist > s->next) + return -3; /* distance too far back */ + + /* copy length bytes from distance bytes back */ + do { + to = s->out + s->next; + from = to - dist; + copy = MAXWIN; + if (s->next < dist) { + from += copy; + copy = dist; + } + copy -= s->next; + if (copy > len) copy = len; + len -= copy; + s->next += copy; + do { + *to++ = *from++; + } while (--copy); + if (s->next == MAXWIN) { + if (s->outfun(s->outhow, s->out, s->next)) return 1; + s->next = 0; + s->first = 0; + } + } while (len != 0); + } + else { + /* get literal and write it */ + symbol = lit ? decode(s, &litcode) : bits(s, 8); + s->out[s->next++] = symbol; + if (s->next == MAXWIN) { + if (s->outfun(s->outhow, s->out, s->next)) return 1; + s->next = 0; + s->first = 0; + } + } + } while (1); + return 0; +} + +/* See comments in blast.h */ +int blast(blast_in infun, void *inhow, blast_out outfun, void *outhow) +{ + struct state s; /* input/output state */ + int err; /* return value */ + + /* initialize input state */ + s.infun = infun; + s.inhow = inhow; + s.left = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* initialize output state */ + s.outfun = outfun; + s.outhow = outhow; + s.next = 0; + s.first = 1; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp(), */ + err = 2; /* then skip decomp(), return error */ + else + err = decomp(&s); /* decompress */ + + /* write any leftover output and update the error code if needed */ + if (err != 1 && s.next && s.outfun(s.outhow, s.out, s.next) && err == 0) + err = 1; + return err; +} + +#ifdef TEST +/* Example of how to use blast() */ +#include +#include + +#define CHUNK 16384 + +local unsigned inf(void *how, unsigned char **buf) +{ + static unsigned char hold[CHUNK]; + + *buf = hold; + return fread(hold, 1, CHUNK, (FILE *)how); +} + +local int outf(void *how, unsigned char *buf, unsigned len) +{ + return fwrite(buf, 1, len, (FILE *)how) != len; +} + +/* Decompress a PKWare Compression Library stream from stdin to stdout */ +int main(void) +{ + int ret, n; + + /* decompress to stdout */ + ret = blast(inf, stdin, outf, stdout); + if (ret != 0) fprintf(stderr, "blast error: %d\n", ret); + + /* see if there are any leftover bytes */ + n = 0; + while (getchar() != EOF) n++; + if (n) fprintf(stderr, "blast warning: %d unused bytes of input\n", n); + + /* return blast() error code */ + return ret; +} +#endif diff --git a/zlib/zlib/contrib/blast/blast.h b/zlib/zlib/contrib/blast/blast.h new file mode 100644 index 00000000..658cfd32 --- /dev/null +++ b/zlib/zlib/contrib/blast/blast.h @@ -0,0 +1,75 @@ +/* blast.h -- interface for blast.c + Copyright (C) 2003, 2012 Mark Adler + version 1.2, 24 Oct 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + + +/* + * blast() decompresses the PKWare Data Compression Library (DCL) compressed + * format. It provides the same functionality as the explode() function in + * that library. (Note: PKWare overused the "implode" verb, and the format + * used by their library implode() function is completely different and + * incompatible with the implode compression method supported by PKZIP.) + * + * The binary mode for stdio functions should be used to assure that the + * compressed data is not corrupted when read or written. For example: + * fopen(..., "rb") and fopen(..., "wb"). + */ + + +typedef unsigned (*blast_in)(void *how, unsigned char **buf); +typedef int (*blast_out)(void *how, unsigned char *buf, unsigned len); +/* Definitions for input/output functions passed to blast(). See below for + * what the provided functions need to do. + */ + + +int blast(blast_in infun, void *inhow, blast_out outfun, void *outhow); +/* Decompress input to output using the provided infun() and outfun() calls. + * On success, the return value of blast() is zero. If there is an error in + * the source data, i.e. it is not in the proper format, then a negative value + * is returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. + * + * The input function is invoked: len = infun(how, &buf), where buf is set by + * infun() to point to the input buffer, and infun() returns the number of + * available bytes there. If infun() returns zero, then blast() returns with + * an input error. (blast() only asks for input if it needs it.) inhow is for + * use by the application to pass an input descriptor to infun(), if desired. + * + * The output function is invoked: err = outfun(how, buf, len), where the bytes + * to be written are buf[0..len-1]. If err is not zero, then blast() returns + * with an output error. outfun() is always called with len <= 4096. outhow + * is for use by the application to pass an output descriptor to outfun(), if + * desired. + * + * The return codes are: + * + * 2: ran out of input before completing decompression + * 1: output error before completing decompression + * 0: successful decompression + * -1: literal flag not zero or one + * -2: dictionary size not in 4..6 + * -3: distance is too far back + * + * At the bottom of blast.c is an example program that uses blast() that can be + * compiled to produce a command-line decompression filter by defining TEST. + */ diff --git a/zlib/zlib/contrib/blast/test.pk b/zlib/zlib/contrib/blast/test.pk new file mode 100644 index 00000000..be10b2bb Binary files /dev/null and b/zlib/zlib/contrib/blast/test.pk differ diff --git a/zlib/zlib/contrib/blast/test.txt b/zlib/zlib/contrib/blast/test.txt new file mode 100644 index 00000000..bfdf1c5d --- /dev/null +++ b/zlib/zlib/contrib/blast/test.txt @@ -0,0 +1 @@ +AIAIAIAIAIAIA \ No newline at end of file diff --git a/zlib/zlib/contrib/delphi/ZLib.pas b/zlib/zlib/contrib/delphi/ZLib.pas new file mode 100644 index 00000000..a579974f --- /dev/null +++ b/zlib/zlib/contrib/delphi/ZLib.pas @@ -0,0 +1,557 @@ +{*******************************************************} +{ } +{ Borland Delphi Supplemental Components } +{ ZLIB Data Compression Interface Unit } +{ } +{ Copyright (c) 1997,99 Borland Corporation } +{ } +{*******************************************************} + +{ Updated for zlib 1.2.x by Cosmin Truta } + +unit ZLib; + +interface + +uses SysUtils, Classes; + +type + TAlloc = function (AppData: Pointer; Items, Size: Integer): Pointer; cdecl; + TFree = procedure (AppData, Block: Pointer); cdecl; + + // Internal structure. Ignore. + TZStreamRec = packed record + next_in: PChar; // next input byte + avail_in: Integer; // number of bytes available at next_in + total_in: Longint; // total nb of input bytes read so far + + next_out: PChar; // next output byte should be put here + avail_out: Integer; // remaining free space at next_out + total_out: Longint; // total nb of bytes output so far + + msg: PChar; // last error message, NULL if no error + internal: Pointer; // not visible by applications + + zalloc: TAlloc; // used to allocate the internal state + zfree: TFree; // used to free the internal state + AppData: Pointer; // private data object passed to zalloc and zfree + + data_type: Integer; // best guess about the data type: ascii or binary + adler: Longint; // adler32 value of the uncompressed data + reserved: Longint; // reserved for future use + end; + + // Abstract ancestor class + TCustomZlibStream = class(TStream) + private + FStrm: TStream; + FStrmPos: Integer; + FOnProgress: TNotifyEvent; + FZRec: TZStreamRec; + FBuffer: array [Word] of Char; + protected + procedure Progress(Sender: TObject); dynamic; + property OnProgress: TNotifyEvent read FOnProgress write FOnProgress; + constructor Create(Strm: TStream); + end; + +{ TCompressionStream compresses data on the fly as data is written to it, and + stores the compressed data to another stream. + + TCompressionStream is write-only and strictly sequential. Reading from the + stream will raise an exception. Using Seek to move the stream pointer + will raise an exception. + + Output data is cached internally, written to the output stream only when + the internal output buffer is full. All pending output data is flushed + when the stream is destroyed. + + The Position property returns the number of uncompressed bytes of + data that have been written to the stream so far. + + CompressionRate returns the on-the-fly percentage by which the original + data has been compressed: (1 - (CompressedBytes / UncompressedBytes)) * 100 + If raw data size = 100 and compressed data size = 25, the CompressionRate + is 75% + + The OnProgress event is called each time the output buffer is filled and + written to the output stream. This is useful for updating a progress + indicator when you are writing a large chunk of data to the compression + stream in a single call.} + + + TCompressionLevel = (clNone, clFastest, clDefault, clMax); + + TCompressionStream = class(TCustomZlibStream) + private + function GetCompressionRate: Single; + public + constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream); + destructor Destroy; override; + function Read(var Buffer; Count: Longint): Longint; override; + function Write(const Buffer; Count: Longint): Longint; override; + function Seek(Offset: Longint; Origin: Word): Longint; override; + property CompressionRate: Single read GetCompressionRate; + property OnProgress; + end; + +{ TDecompressionStream decompresses data on the fly as data is read from it. + + Compressed data comes from a separate source stream. TDecompressionStream + is read-only and unidirectional; you can seek forward in the stream, but not + backwards. The special case of setting the stream position to zero is + allowed. Seeking forward decompresses data until the requested position in + the uncompressed data has been reached. Seeking backwards, seeking relative + to the end of the stream, requesting the size of the stream, and writing to + the stream will raise an exception. + + The Position property returns the number of bytes of uncompressed data that + have been read from the stream so far. + + The OnProgress event is called each time the internal input buffer of + compressed data is exhausted and the next block is read from the input stream. + This is useful for updating a progress indicator when you are reading a + large chunk of data from the decompression stream in a single call.} + + TDecompressionStream = class(TCustomZlibStream) + public + constructor Create(Source: TStream); + destructor Destroy; override; + function Read(var Buffer; Count: Longint): Longint; override; + function Write(const Buffer; Count: Longint): Longint; override; + function Seek(Offset: Longint; Origin: Word): Longint; override; + property OnProgress; + end; + + + +{ CompressBuf compresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + Out: OutBuf = ptr to newly allocated buffer containing decompressed data + OutBytes = number of bytes in OutBuf } +procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; + out OutBuf: Pointer; out OutBytes: Integer); + + +{ DecompressBuf decompresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + OutEstimate = zero, or est. size of the decompressed data + Out: OutBuf = ptr to newly allocated buffer containing decompressed data + OutBytes = number of bytes in OutBuf } +procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; + OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); + +{ DecompressToUserBuf decompresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + Out: OutBuf = ptr to user-allocated buffer to contain decompressed data + BufSize = number of bytes in OutBuf } +procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; + const OutBuf: Pointer; BufSize: Integer); + +const + zlib_version = '1.2.8'; + +type + EZlibError = class(Exception); + ECompressionError = class(EZlibError); + EDecompressionError = class(EZlibError); + +implementation + +uses ZLibConst; + +const + Z_NO_FLUSH = 0; + Z_PARTIAL_FLUSH = 1; + Z_SYNC_FLUSH = 2; + Z_FULL_FLUSH = 3; + Z_FINISH = 4; + + Z_OK = 0; + Z_STREAM_END = 1; + Z_NEED_DICT = 2; + Z_ERRNO = (-1); + Z_STREAM_ERROR = (-2); + Z_DATA_ERROR = (-3); + Z_MEM_ERROR = (-4); + Z_BUF_ERROR = (-5); + Z_VERSION_ERROR = (-6); + + Z_NO_COMPRESSION = 0; + Z_BEST_SPEED = 1; + Z_BEST_COMPRESSION = 9; + Z_DEFAULT_COMPRESSION = (-1); + + Z_FILTERED = 1; + Z_HUFFMAN_ONLY = 2; + Z_RLE = 3; + Z_DEFAULT_STRATEGY = 0; + + Z_BINARY = 0; + Z_ASCII = 1; + Z_UNKNOWN = 2; + + Z_DEFLATED = 8; + + +{$L adler32.obj} +{$L compress.obj} +{$L crc32.obj} +{$L deflate.obj} +{$L infback.obj} +{$L inffast.obj} +{$L inflate.obj} +{$L inftrees.obj} +{$L trees.obj} +{$L uncompr.obj} +{$L zutil.obj} + +procedure adler32; external; +procedure compressBound; external; +procedure crc32; external; +procedure deflateInit2_; external; +procedure deflateParams; external; + +function _malloc(Size: Integer): Pointer; cdecl; +begin + Result := AllocMem(Size); +end; + +procedure _free(Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl; +begin + FillChar(P^, count, B); +end; + +procedure _memcpy(dest, source: Pointer; count: Integer); cdecl; +begin + Move(source^, dest^, count); +end; + + + +// deflate compresses data +function deflateInit_(var strm: TZStreamRec; level: Integer; version: PChar; + recsize: Integer): Integer; external; +function deflate(var strm: TZStreamRec; flush: Integer): Integer; external; +function deflateEnd(var strm: TZStreamRec): Integer; external; + +// inflate decompresses data +function inflateInit_(var strm: TZStreamRec; version: PChar; + recsize: Integer): Integer; external; +function inflate(var strm: TZStreamRec; flush: Integer): Integer; external; +function inflateEnd(var strm: TZStreamRec): Integer; external; +function inflateReset(var strm: TZStreamRec): Integer; external; + + +function zlibAllocMem(AppData: Pointer; Items, Size: Integer): Pointer; cdecl; +begin +// GetMem(Result, Items*Size); + Result := AllocMem(Items * Size); +end; + +procedure zlibFreeMem(AppData, Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +{function zlibCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise EZlibError.Create('error'); //!! +end;} + +function CCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise ECompressionError.Create('error'); //!! +end; + +function DCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise EDecompressionError.Create('error'); //!! +end; + +procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; + out OutBuf: Pointer; out OutBytes: Integer); +var + strm: TZStreamRec; + P: Pointer; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255; + GetMem(OutBuf, OutBytes); + try + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := OutBytes; + CCheck(deflateInit_(strm, Z_BEST_COMPRESSION, zlib_version, sizeof(strm))); + try + while CCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do + begin + P := OutBuf; + Inc(OutBytes, 256); + ReallocMem(OutBuf, OutBytes); + strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); + strm.avail_out := 256; + end; + finally + CCheck(deflateEnd(strm)); + end; + ReallocMem(OutBuf, strm.total_out); + OutBytes := strm.total_out; + except + FreeMem(OutBuf); + raise + end; +end; + + +procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; + OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); +var + strm: TZStreamRec; + P: Pointer; + BufInc: Integer; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + BufInc := (InBytes + 255) and not 255; + if OutEstimate = 0 then + OutBytes := BufInc + else + OutBytes := OutEstimate; + GetMem(OutBuf, OutBytes); + try + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := OutBytes; + DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); + try + while DCheck(inflate(strm, Z_NO_FLUSH)) <> Z_STREAM_END do + begin + P := OutBuf; + Inc(OutBytes, BufInc); + ReallocMem(OutBuf, OutBytes); + strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); + strm.avail_out := BufInc; + end; + finally + DCheck(inflateEnd(strm)); + end; + ReallocMem(OutBuf, strm.total_out); + OutBytes := strm.total_out; + except + FreeMem(OutBuf); + raise + end; +end; + +procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; + const OutBuf: Pointer; BufSize: Integer); +var + strm: TZStreamRec; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := BufSize; + DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); + try + if DCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END then + raise EZlibError.CreateRes(@sTargetBufferTooSmall); + finally + DCheck(inflateEnd(strm)); + end; +end; + +// TCustomZlibStream + +constructor TCustomZLibStream.Create(Strm: TStream); +begin + inherited Create; + FStrm := Strm; + FStrmPos := Strm.Position; + FZRec.zalloc := zlibAllocMem; + FZRec.zfree := zlibFreeMem; +end; + +procedure TCustomZLibStream.Progress(Sender: TObject); +begin + if Assigned(FOnProgress) then FOnProgress(Sender); +end; + + +// TCompressionStream + +constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel; + Dest: TStream); +const + Levels: array [TCompressionLevel] of ShortInt = + (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION); +begin + inherited Create(Dest); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + CCheck(deflateInit_(FZRec, Levels[CompressionLevel], zlib_version, sizeof(FZRec))); +end; + +destructor TCompressionStream.Destroy; +begin + FZRec.next_in := nil; + FZRec.avail_in := 0; + try + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END) + and (FZRec.avail_out = 0) do + begin + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + end; + if FZRec.avail_out < sizeof(FBuffer) then + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer) - FZRec.avail_out); + finally + deflateEnd(FZRec); + end; + inherited Destroy; +end; + +function TCompressionStream.Read(var Buffer; Count: Longint): Longint; +begin + raise ECompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TCompressionStream.Write(const Buffer; Count: Longint): Longint; +begin + FZRec.next_in := @Buffer; + FZRec.avail_in := Count; + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (FZRec.avail_in > 0) do + begin + CCheck(deflate(FZRec, 0)); + if FZRec.avail_out = 0 then + begin + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + FStrmPos := FStrm.Position; + Progress(Self); + end; + end; + Result := Count; +end; + +function TCompressionStream.Seek(Offset: Longint; Origin: Word): Longint; +begin + if (Offset = 0) and (Origin = soFromCurrent) then + Result := FZRec.total_in + else + raise ECompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TCompressionStream.GetCompressionRate: Single; +begin + if FZRec.total_in = 0 then + Result := 0 + else + Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0; +end; + + +// TDecompressionStream + +constructor TDecompressionStream.Create(Source: TStream); +begin + inherited Create(Source); + FZRec.next_in := FBuffer; + FZRec.avail_in := 0; + DCheck(inflateInit_(FZRec, zlib_version, sizeof(FZRec))); +end; + +destructor TDecompressionStream.Destroy; +begin + FStrm.Seek(-FZRec.avail_in, 1); + inflateEnd(FZRec); + inherited Destroy; +end; + +function TDecompressionStream.Read(var Buffer; Count: Longint): Longint; +begin + FZRec.next_out := @Buffer; + FZRec.avail_out := Count; + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (FZRec.avail_out > 0) do + begin + if FZRec.avail_in = 0 then + begin + FZRec.avail_in := FStrm.Read(FBuffer, sizeof(FBuffer)); + if FZRec.avail_in = 0 then + begin + Result := Count - FZRec.avail_out; + Exit; + end; + FZRec.next_in := FBuffer; + FStrmPos := FStrm.Position; + Progress(Self); + end; + CCheck(inflate(FZRec, 0)); + end; + Result := Count; +end; + +function TDecompressionStream.Write(const Buffer; Count: Longint): Longint; +begin + raise EDecompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TDecompressionStream.Seek(Offset: Longint; Origin: Word): Longint; +var + I: Integer; + Buf: array [0..4095] of Char; +begin + if (Offset = 0) and (Origin = soFromBeginning) then + begin + DCheck(inflateReset(FZRec)); + FZRec.next_in := FBuffer; + FZRec.avail_in := 0; + FStrm.Position := 0; + FStrmPos := 0; + end + else if ( (Offset >= 0) and (Origin = soFromCurrent)) or + ( ((Offset - FZRec.total_out) > 0) and (Origin = soFromBeginning)) then + begin + if Origin = soFromBeginning then Dec(Offset, FZRec.total_out); + if Offset > 0 then + begin + for I := 1 to Offset div sizeof(Buf) do + ReadBuffer(Buf, sizeof(Buf)); + ReadBuffer(Buf, Offset mod sizeof(Buf)); + end; + end + else + raise EDecompressionError.CreateRes(@sInvalidStreamOp); + Result := FZRec.total_out; +end; + + +end. diff --git a/zlib/zlib/contrib/delphi/ZLibConst.pas b/zlib/zlib/contrib/delphi/ZLibConst.pas new file mode 100644 index 00000000..cdfe1367 --- /dev/null +++ b/zlib/zlib/contrib/delphi/ZLibConst.pas @@ -0,0 +1,11 @@ +unit ZLibConst; + +interface + +resourcestring + sTargetBufferTooSmall = 'ZLib error: target buffer may be too small'; + sInvalidStreamOp = 'Invalid stream operation'; + +implementation + +end. diff --git a/zlib/zlib/contrib/delphi/readme.txt b/zlib/zlib/contrib/delphi/readme.txt new file mode 100644 index 00000000..2dc9a8bb --- /dev/null +++ b/zlib/zlib/contrib/delphi/readme.txt @@ -0,0 +1,76 @@ + +Overview +======== + +This directory contains an update to the ZLib interface unit, +distributed by Borland as a Delphi supplemental component. + +The original ZLib unit is Copyright (c) 1997,99 Borland Corp., +and is based on zlib version 1.0.4. There are a series of bugs +and security problems associated with that old zlib version, and +we recommend the users to update their ZLib unit. + + +Summary of modifications +======================== + +- Improved makefile, adapted to zlib version 1.2.1. + +- Some field types from TZStreamRec are changed from Integer to + Longint, for consistency with the zlib.h header, and for 64-bit + readiness. + +- The zlib_version constant is updated. + +- The new Z_RLE strategy has its corresponding symbolic constant. + +- The allocation and deallocation functions and function types + (TAlloc, TFree, zlibAllocMem and zlibFreeMem) are now cdecl, + and _malloc and _free are added as C RTL stubs. As a result, + the original C sources of zlib can be compiled out of the box, + and linked to the ZLib unit. + + +Suggestions for improvements +============================ + +Currently, the ZLib unit provides only a limited wrapper around +the zlib library, and much of the original zlib functionality is +missing. Handling compressed file formats like ZIP/GZIP or PNG +cannot be implemented without having this functionality. +Applications that handle these formats are either using their own, +duplicated code, or not using the ZLib unit at all. + +Here are a few suggestions: + +- Checksum class wrappers around adler32() and crc32(), similar + to the Java classes that implement the java.util.zip.Checksum + interface. + +- The ability to read and write raw deflate streams, without the + zlib stream header and trailer. Raw deflate streams are used + in the ZIP file format. + +- The ability to read and write gzip streams, used in the GZIP + file format, and normally produced by the gzip program. + +- The ability to select a different compression strategy, useful + to PNG and MNG image compression, and to multimedia compression + in general. Besides the compression level + + TCompressionLevel = (clNone, clFastest, clDefault, clMax); + + which, in fact, could have used the 'z' prefix and avoided + TColor-like symbols + + TCompressionLevel = (zcNone, zcFastest, zcDefault, zcMax); + + there could be a compression strategy + + TCompressionStrategy = (zsDefault, zsFiltered, zsHuffmanOnly, zsRle); + +- ZIP and GZIP stream handling via TStreams. + + +-- +Cosmin Truta diff --git a/zlib/zlib/contrib/delphi/zlibd32.mak b/zlib/zlib/contrib/delphi/zlibd32.mak new file mode 100644 index 00000000..9bb00b7c --- /dev/null +++ b/zlib/zlib/contrib/delphi/zlibd32.mak @@ -0,0 +1,99 @@ +# Makefile for zlib +# For use with Delphi and C++ Builder under Win32 +# Updated for zlib 1.2.x by Cosmin Truta + +# ------------ Borland C++ ------------ + +# This project uses the Delphi (fastcall/register) calling convention: +LOC = -DZEXPORT=__fastcall -DZEXPORTVA=__cdecl + +CC = bcc32 +LD = bcc32 +AR = tlib +# do not use "-pr" in CFLAGS +CFLAGS = -a -d -k- -O2 $(LOC) +LDFLAGS = + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del *.obj + -del *.exe + -del *.lib + -del *.tds + -del zlib.bak + -del foo.gz + diff --git a/zlib/zlib/contrib/dotzlib/DotZLib.build b/zlib/zlib/contrib/dotzlib/DotZLib.build new file mode 100644 index 00000000..7f90d6bc --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib.build @@ -0,0 +1,33 @@ + + + A .Net wrapper library around ZLib1.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/dotzlib/DotZLib.chm b/zlib/zlib/contrib/dotzlib/DotZLib.chm new file mode 100644 index 00000000..f214a444 Binary files /dev/null and b/zlib/zlib/contrib/dotzlib/DotZLib.chm differ diff --git a/zlib/zlib/contrib/dotzlib/DotZLib.sln b/zlib/zlib/contrib/dotzlib/DotZLib.sln new file mode 100644 index 00000000..ac45ca04 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotZLib", "DotZLib\DotZLib.csproj", "{BB1EE0B1-1808-46CB-B786-949D91117FC5}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Debug.ActiveCfg = Debug|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Debug.Build.0 = Debug|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Release.ActiveCfg = Release|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Release.Build.0 = Release|.NET + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs b/zlib/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs new file mode 100644 index 00000000..724c5347 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("DotZLib")] +[assembly: AssemblyDescription(".Net bindings for ZLib compression dll 1.2.x")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Henrik Ravn")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("(c) 2004 by Henrik Ravn")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs b/zlib/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs new file mode 100644 index 00000000..b110dae6 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs @@ -0,0 +1,202 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Runtime.InteropServices; +using System.Text; + + +namespace DotZLib +{ + #region ChecksumGeneratorBase + /// + /// Implements the common functionality needed for all s + /// + /// + public abstract class ChecksumGeneratorBase : ChecksumGenerator + { + /// + /// The value of the current checksum + /// + protected uint _current; + + /// + /// Initializes a new instance of the checksum generator base - the current checksum is + /// set to zero + /// + public ChecksumGeneratorBase() + { + _current = 0; + } + + /// + /// Initializes a new instance of the checksum generator basewith a specified value + /// + /// The value to set the current checksum to + public ChecksumGeneratorBase(uint initialValue) + { + _current = initialValue; + } + + /// + /// Resets the current checksum to zero + /// + public void Reset() { _current = 0; } + + /// + /// Gets the current checksum value + /// + public uint Value { get { return _current; } } + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + /// All the other Update methods are implmeneted in terms of this one. + /// This is therefore the only method a derived class has to implement + public abstract void Update(byte[] data, int offset, int count); + + /// + /// Updates the current checksum with an array of bytes. + /// + /// The data to update the checksum with + public void Update(byte[] data) + { + Update(data, 0, data.Length); + } + + /// + /// Updates the current checksum with the data from a string + /// + /// The string to update the checksum with + /// The characters in the string are converted by the UTF-8 encoding + public void Update(string data) + { + Update(Encoding.UTF8.GetBytes(data)); + } + + /// + /// Updates the current checksum with the data from a string, using a specific encoding + /// + /// The string to update the checksum with + /// The encoding to use + public void Update(string data, Encoding encoding) + { + Update(encoding.GetBytes(data)); + } + + } + #endregion + + #region CRC32 + /// + /// Implements a CRC32 checksum generator + /// + public sealed class CRC32Checksum : ChecksumGeneratorBase + { + #region DLL imports + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint crc32(uint crc, int data, uint length); + + #endregion + + /// + /// Initializes a new instance of the CRC32 checksum generator + /// + public CRC32Checksum() : base() {} + + /// + /// Initializes a new instance of the CRC32 checksum generator with a specified value + /// + /// The value to set the current checksum to + public CRC32Checksum(uint initialValue) : base(initialValue) {} + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + public override void Update(byte[] data, int offset, int count) + { + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + GCHandle hData = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + _current = crc32(_current, hData.AddrOfPinnedObject().ToInt32()+offset, (uint)count); + } + finally + { + hData.Free(); + } + } + + } + #endregion + + #region Adler + /// + /// Implements a checksum generator that computes the Adler checksum on data + /// + public sealed class AdlerChecksum : ChecksumGeneratorBase + { + #region DLL imports + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint adler32(uint adler, int data, uint length); + + #endregion + + /// + /// Initializes a new instance of the Adler checksum generator + /// + public AdlerChecksum() : base() {} + + /// + /// Initializes a new instance of the Adler checksum generator with a specified value + /// + /// The value to set the current checksum to + public AdlerChecksum(uint initialValue) : base(initialValue) {} + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + public override void Update(byte[] data, int offset, int count) + { + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + GCHandle hData = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + _current = adler32(_current, hData.AddrOfPinnedObject().ToInt32()+offset, (uint)count); + } + finally + { + hData.Free(); + } + } + + } + #endregion + +} \ No newline at end of file diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs b/zlib/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs new file mode 100644 index 00000000..9c8d6019 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs @@ -0,0 +1,83 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; + +namespace DotZLib +{ + + /// + /// This class implements a circular buffer + /// + internal class CircularBuffer + { + #region Private data + private int _capacity; + private int _head; + private int _tail; + private int _size; + private byte[] _buffer; + #endregion + + public CircularBuffer(int capacity) + { + Debug.Assert( capacity > 0 ); + _buffer = new byte[capacity]; + _capacity = capacity; + _head = 0; + _tail = 0; + _size = 0; + } + + public int Size { get { return _size; } } + + public int Put(byte[] source, int offset, int count) + { + Debug.Assert( count > 0 ); + int trueCount = Math.Min(count, _capacity - Size); + for (int i = 0; i < trueCount; ++i) + _buffer[(_tail+i) % _capacity] = source[offset+i]; + _tail += trueCount; + _tail %= _capacity; + _size += trueCount; + return trueCount; + } + + public bool Put(byte b) + { + if (Size == _capacity) // no room + return false; + _buffer[_tail++] = b; + _tail %= _capacity; + ++_size; + return true; + } + + public int Get(byte[] destination, int offset, int count) + { + int trueCount = Math.Min(count,Size); + for (int i = 0; i < trueCount; ++i) + destination[offset + i] = _buffer[(_head+i) % _capacity]; + _head += trueCount; + _head %= _capacity; + _size -= trueCount; + return trueCount; + } + + public int Get() + { + if (Size == 0) + return -1; + + int result = (int)_buffer[_head++ % _capacity]; + --_size; + return result; + } + + } +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/CodecBase.cs b/zlib/zlib/contrib/dotzlib/DotZLib/CodecBase.cs new file mode 100644 index 00000000..b0eb78a0 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/CodecBase.cs @@ -0,0 +1,198 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + /// + /// Implements the common functionality needed for all s + /// + public abstract class CodecBase : Codec, IDisposable + { + + #region Data members + + /// + /// Instance of the internal zlib buffer structure that is + /// passed to all functions in the zlib dll + /// + internal ZStream _ztream = new ZStream(); + + /// + /// True if the object instance has been disposed, false otherwise + /// + protected bool _isDisposed = false; + + /// + /// The size of the internal buffers + /// + protected const int kBufferSize = 16384; + + private byte[] _outBuffer = new byte[kBufferSize]; + private byte[] _inBuffer = new byte[kBufferSize]; + + private GCHandle _hInput; + private GCHandle _hOutput; + + private uint _checksum = 0; + + #endregion + + /// + /// Initializes a new instance of the CodeBase class. + /// + public CodecBase() + { + try + { + _hInput = GCHandle.Alloc(_inBuffer, GCHandleType.Pinned); + _hOutput = GCHandle.Alloc(_outBuffer, GCHandleType.Pinned); + } + catch (Exception) + { + CleanUp(false); + throw; + } + } + + + #region Codec Members + + /// + /// Occurs when more processed data are available. + /// + public event DataAvailableHandler DataAvailable; + + /// + /// Fires the event + /// + protected void OnDataAvailable() + { + if (_ztream.total_out > 0) + { + if (DataAvailable != null) + DataAvailable( _outBuffer, 0, (int)_ztream.total_out); + resetOutput(); + } + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// Adding data may, or may not, raise the DataAvailable event + public void Add(byte[] data) + { + Add(data,0,data.Length); + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + /// This must be implemented by a derived class + public abstract void Add(byte[] data, int offset, int count); + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + /// This must be implemented by a derived class + public abstract void Finish(); + + /// + /// Gets the checksum of the data that has been added so far + /// + public uint Checksum { get { return _checksum; } } + + #endregion + + #region Destructor & IDisposable stuff + + /// + /// Destroys this instance + /// + ~CodecBase() + { + CleanUp(false); + } + + /// + /// Releases any unmanaged resources and calls the method of the derived class + /// + public void Dispose() + { + CleanUp(true); + } + + /// + /// Performs any codec specific cleanup + /// + /// This must be implemented by a derived class + protected abstract void CleanUp(); + + // performs the release of the handles and calls the dereived CleanUp() + private void CleanUp(bool isDisposing) + { + if (!_isDisposed) + { + CleanUp(); + if (_hInput.IsAllocated) + _hInput.Free(); + if (_hOutput.IsAllocated) + _hOutput.Free(); + + _isDisposed = true; + } + } + + + #endregion + + #region Helper methods + + /// + /// Copies a number of bytes to the internal codec buffer - ready for proccesing + /// + /// The byte array that contains the data to copy + /// The index of the first byte to copy + /// The number of bytes to copy from data + protected void copyInput(byte[] data, int startIndex, int count) + { + Array.Copy(data, startIndex, _inBuffer,0, count); + _ztream.next_in = _hInput.AddrOfPinnedObject(); + _ztream.total_in = 0; + _ztream.avail_in = (uint)count; + + } + + /// + /// Resets the internal output buffers to a known state - ready for processing + /// + protected void resetOutput() + { + _ztream.total_out = 0; + _ztream.avail_out = kBufferSize; + _ztream.next_out = _hOutput.AddrOfPinnedObject(); + } + + /// + /// Updates the running checksum property + /// + /// The new checksum value + protected void setChecksum(uint newSum) + { + _checksum = newSum; + } + #endregion + + } +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/Deflater.cs b/zlib/zlib/contrib/dotzlib/DotZLib/Deflater.cs new file mode 100644 index 00000000..9039f41f --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/Deflater.cs @@ -0,0 +1,106 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + + /// + /// Implements a data compressor, using the deflate algorithm in the ZLib dll + /// + public sealed class Deflater : CodecBase + { + #region Dll imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern int deflateInit_(ref ZStream sz, int level, string vs, int size); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflate(ref ZStream sz, int flush); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflateReset(ref ZStream sz); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflateEnd(ref ZStream sz); + #endregion + + /// + /// Constructs an new instance of the Deflater + /// + /// The compression level to use for this Deflater + public Deflater(CompressLevel level) : base() + { + int retval = deflateInit_(ref _ztream, (int)level, Info.Version, Marshal.SizeOf(_ztream)); + if (retval != 0) + throw new ZLibException(retval, "Could not initialize deflater"); + + resetOutput(); + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + public override void Add(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + + int total = count; + int inputIndex = offset; + int err = 0; + + while (err >= 0 && inputIndex < total) + { + copyInput(data, inputIndex, Math.Min(total - inputIndex, kBufferSize)); + while (err >= 0 && _ztream.avail_in > 0) + { + err = deflate(ref _ztream, (int)FlushTypes.None); + if (err == 0) + while (_ztream.avail_out == 0) + { + OnDataAvailable(); + err = deflate(ref _ztream, (int)FlushTypes.None); + } + inputIndex += (int)_ztream.total_in; + } + } + setChecksum( _ztream.adler ); + } + + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + public override void Finish() + { + int err; + do + { + err = deflate(ref _ztream, (int)FlushTypes.Finish); + OnDataAvailable(); + } + while (err == 0); + setChecksum( _ztream.adler ); + deflateReset(ref _ztream); + resetOutput(); + } + + /// + /// Closes the internal zlib deflate stream + /// + protected override void CleanUp() { deflateEnd(ref _ztream); } + + } +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.cs b/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.cs new file mode 100644 index 00000000..90c7c3b3 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.cs @@ -0,0 +1,288 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + + +namespace DotZLib +{ + + #region Internal types + + /// + /// Defines constants for the various flush types used with zlib + /// + internal enum FlushTypes + { + None, Partial, Sync, Full, Finish, Block + } + + #region ZStream structure + // internal mapping of the zlib zstream structure for marshalling + [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0, CharSet=CharSet.Ansi)] + internal struct ZStream + { + public IntPtr next_in; + public uint avail_in; + public uint total_in; + + public IntPtr next_out; + public uint avail_out; + public uint total_out; + + [MarshalAs(UnmanagedType.LPStr)] + string msg; + uint state; + + uint zalloc; + uint zfree; + uint opaque; + + int data_type; + public uint adler; + uint reserved; + } + + #endregion + + #endregion + + #region Public enums + /// + /// Defines constants for the available compression levels in zlib + /// + public enum CompressLevel : int + { + /// + /// The default compression level with a reasonable compromise between compression and speed + /// + Default = -1, + /// + /// No compression at all. The data are passed straight through. + /// + None = 0, + /// + /// The maximum compression rate available. + /// + Best = 9, + /// + /// The fastest available compression level. + /// + Fastest = 1 + } + #endregion + + #region Exception classes + /// + /// The exception that is thrown when an error occurs on the zlib dll + /// + public class ZLibException : ApplicationException + { + /// + /// Initializes a new instance of the class with a specified + /// error message and error code + /// + /// The zlib error code that caused the exception + /// A message that (hopefully) describes the error + public ZLibException(int errorCode, string msg) : base(String.Format("ZLib error {0} {1}", errorCode, msg)) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error code + /// + /// The zlib error code that caused the exception + public ZLibException(int errorCode) : base(String.Format("ZLib error {0}", errorCode)) + { + } + } + #endregion + + #region Interfaces + + /// + /// Declares methods and properties that enables a running checksum to be calculated + /// + public interface ChecksumGenerator + { + /// + /// Gets the current value of the checksum + /// + uint Value { get; } + + /// + /// Clears the current checksum to 0 + /// + void Reset(); + + /// + /// Updates the current checksum with an array of bytes + /// + /// The data to update the checksum with + void Update(byte[] data); + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + void Update(byte[] data, int offset, int count); + + /// + /// Updates the current checksum with the data from a string + /// + /// The string to update the checksum with + /// The characters in the string are converted by the UTF-8 encoding + void Update(string data); + + /// + /// Updates the current checksum with the data from a string, using a specific encoding + /// + /// The string to update the checksum with + /// The encoding to use + void Update(string data, Encoding encoding); + } + + + /// + /// Represents the method that will be called from a codec when new data + /// are available. + /// + /// The byte array containing the processed data + /// The index of the first processed byte in data + /// The number of processed bytes available + /// On return from this method, the data may be overwritten, so grab it while you can. + /// You cannot assume that startIndex will be zero. + /// + public delegate void DataAvailableHandler(byte[] data, int startIndex, int count); + + /// + /// Declares methods and events for implementing compressors/decompressors + /// + public interface Codec + { + /// + /// Occurs when more processed data are available. + /// + event DataAvailableHandler DataAvailable; + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// Adding data may, or may not, raise the DataAvailable event + void Add(byte[] data); + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + void Add(byte[] data, int offset, int count); + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + void Finish(); + + /// + /// Gets the checksum of the data that has been added so far + /// + uint Checksum { get; } + + + } + + #endregion + + #region Classes + /// + /// Encapsulates general information about the ZLib library + /// + public class Info + { + #region DLL imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint zlibCompileFlags(); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern string zlibVersion(); + #endregion + + #region Private stuff + private uint _flags; + + // helper function that unpacks a bitsize mask + private static int bitSize(uint bits) + { + switch (bits) + { + case 0: return 16; + case 1: return 32; + case 2: return 64; + } + return -1; + } + #endregion + + /// + /// Constructs an instance of the Info class. + /// + public Info() + { + _flags = zlibCompileFlags(); + } + + /// + /// True if the library is compiled with debug info + /// + public bool HasDebugInfo { get { return 0 != (_flags & 0x100); } } + + /// + /// True if the library is compiled with assembly optimizations + /// + public bool UsesAssemblyCode { get { return 0 != (_flags & 0x200); } } + + /// + /// Gets the size of the unsigned int that was compiled into Zlib + /// + public int SizeOfUInt { get { return bitSize(_flags & 3); } } + + /// + /// Gets the size of the unsigned long that was compiled into Zlib + /// + public int SizeOfULong { get { return bitSize((_flags >> 2) & 3); } } + + /// + /// Gets the size of the pointers that were compiled into Zlib + /// + public int SizeOfPointer { get { return bitSize((_flags >> 4) & 3); } } + + /// + /// Gets the size of the z_off_t type that was compiled into Zlib + /// + public int SizeOfOffset { get { return bitSize((_flags >> 6) & 3); } } + + /// + /// Gets the version of ZLib as a string, e.g. "1.2.1" + /// + public static string Version { get { return zlibVersion(); } } + } + + #endregion + +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj b/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj new file mode 100644 index 00000000..71eeb859 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/GZipStream.cs b/zlib/zlib/contrib/dotzlib/DotZLib/GZipStream.cs new file mode 100644 index 00000000..f0eada1d --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/GZipStream.cs @@ -0,0 +1,301 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + /// + /// Implements a compressed , in GZip (.gz) format. + /// + public class GZipStream : Stream, IDisposable + { + #region Dll Imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern IntPtr gzopen(string name, string mode); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzclose(IntPtr gzFile); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzwrite(IntPtr gzFile, int data, int length); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzread(IntPtr gzFile, int data, int length); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzgetc(IntPtr gzFile); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzputc(IntPtr gzFile, int c); + + #endregion + + #region Private data + private IntPtr _gzFile; + private bool _isDisposed = false; + private bool _isWriting; + #endregion + + #region Constructors + /// + /// Creates a new file as a writeable GZipStream + /// + /// The name of the compressed file to create + /// The compression level to use when adding data + /// If an error occurred in the internal zlib function + public GZipStream(string fileName, CompressLevel level) + { + _isWriting = true; + _gzFile = gzopen(fileName, String.Format("wb{0}", (int)level)); + if (_gzFile == IntPtr.Zero) + throw new ZLibException(-1, "Could not open " + fileName); + } + + /// + /// Opens an existing file as a readable GZipStream + /// + /// The name of the file to open + /// If an error occurred in the internal zlib function + public GZipStream(string fileName) + { + _isWriting = false; + _gzFile = gzopen(fileName, "rb"); + if (_gzFile == IntPtr.Zero) + throw new ZLibException(-1, "Could not open " + fileName); + + } + #endregion + + #region Access properties + /// + /// Returns true of this stream can be read from, false otherwise + /// + public override bool CanRead + { + get + { + return !_isWriting; + } + } + + + /// + /// Returns false. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Returns true if this tsream is writeable, false otherwise + /// + public override bool CanWrite + { + get + { + return _isWriting; + } + } + #endregion + + #region Destructor & IDispose stuff + + /// + /// Destroys this instance + /// + ~GZipStream() + { + cleanUp(false); + } + + /// + /// Closes the external file handle + /// + public void Dispose() + { + cleanUp(true); + } + + // Does the actual closing of the file handle. + private void cleanUp(bool isDisposing) + { + if (!_isDisposed) + { + gzclose(_gzFile); + _isDisposed = true; + } + } + #endregion + + #region Basic reading and writing + /// + /// Attempts to read a number of bytes from the stream. + /// + /// The destination data buffer + /// The index of the first destination byte in buffer + /// The number of bytes requested + /// The number of bytes read + /// If buffer is null + /// If count or offset are negative + /// If offset + count is > buffer.Length + /// If this stream is not readable. + /// If this stream has been disposed. + public override int Read(byte[] buffer, int offset, int count) + { + if (!CanRead) throw new NotSupportedException(); + if (buffer == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > buffer.Length) throw new ArgumentException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); + int result; + try + { + result = gzread(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count); + if (result < 0) + throw new IOException(); + } + finally + { + h.Free(); + } + return result; + } + + /// + /// Attempts to read a single byte from the stream. + /// + /// The byte that was read, or -1 in case of error or End-Of-File + public override int ReadByte() + { + if (!CanRead) throw new NotSupportedException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + return gzgetc(_gzFile); + } + + /// + /// Writes a number of bytes to the stream + /// + /// + /// + /// + /// If buffer is null + /// If count or offset are negative + /// If offset + count is > buffer.Length + /// If this stream is not writeable. + /// If this stream has been disposed. + public override void Write(byte[] buffer, int offset, int count) + { + if (!CanWrite) throw new NotSupportedException(); + if (buffer == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > buffer.Length) throw new ArgumentException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + int result = gzwrite(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count); + if (result < 0) + throw new IOException(); + } + finally + { + h.Free(); + } + } + + /// + /// Writes a single byte to the stream + /// + /// The byte to add to the stream. + /// If this stream is not writeable. + /// If this stream has been disposed. + public override void WriteByte(byte value) + { + if (!CanWrite) throw new NotSupportedException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + int result = gzputc(_gzFile, (int)value); + if (result < 0) + throw new IOException(); + } + #endregion + + #region Position & length stuff + /// + /// Not supported. + /// + /// + /// Always thrown + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Not suppported. + /// + /// + /// + /// + /// Always thrown + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Flushes the GZipStream. + /// + /// In this implementation, this method does nothing. This is because excessive + /// flushing may degrade the achievable compression rates. + public override void Flush() + { + // left empty on purpose + } + + /// + /// Gets/sets the current position in the GZipStream. Not suppported. + /// + /// In this implementation this property is not supported + /// Always thrown + public override long Position + { + get + { + throw new NotSupportedException(); + } + set + { + throw new NotSupportedException(); + } + } + + /// + /// Gets the size of the stream. Not suppported. + /// + /// In this implementation this property is not supported + /// Always thrown + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + #endregion + } +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/Inflater.cs b/zlib/zlib/contrib/dotzlib/DotZLib/Inflater.cs new file mode 100644 index 00000000..d295f268 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/Inflater.cs @@ -0,0 +1,105 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + + /// + /// Implements a data decompressor, using the inflate algorithm in the ZLib dll + /// + public class Inflater : CodecBase + { + #region Dll imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern int inflateInit_(ref ZStream sz, string vs, int size); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflate(ref ZStream sz, int flush); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflateReset(ref ZStream sz); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflateEnd(ref ZStream sz); + #endregion + + /// + /// Constructs an new instance of the Inflater + /// + public Inflater() : base() + { + int retval = inflateInit_(ref _ztream, Info.Version, Marshal.SizeOf(_ztream)); + if (retval != 0) + throw new ZLibException(retval, "Could not initialize inflater"); + + resetOutput(); + } + + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + public override void Add(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + + int total = count; + int inputIndex = offset; + int err = 0; + + while (err >= 0 && inputIndex < total) + { + copyInput(data, inputIndex, Math.Min(total - inputIndex, kBufferSize)); + err = inflate(ref _ztream, (int)FlushTypes.None); + if (err == 0) + while (_ztream.avail_out == 0) + { + OnDataAvailable(); + err = inflate(ref _ztream, (int)FlushTypes.None); + } + + inputIndex += (int)_ztream.total_in; + } + setChecksum( _ztream.adler ); + } + + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + public override void Finish() + { + int err; + do + { + err = inflate(ref _ztream, (int)FlushTypes.Finish); + OnDataAvailable(); + } + while (err == 0); + setChecksum( _ztream.adler ); + inflateReset(ref _ztream); + resetOutput(); + } + + /// + /// Closes the internal zlib inflate stream + /// + protected override void CleanUp() { inflateEnd(ref _ztream); } + + + } +} diff --git a/zlib/zlib/contrib/dotzlib/DotZLib/UnitTests.cs b/zlib/zlib/contrib/dotzlib/DotZLib/UnitTests.cs new file mode 100644 index 00000000..15394614 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/DotZLib/UnitTests.cs @@ -0,0 +1,274 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Collections; +using System.IO; + +// uncomment the define below to include unit tests +//#define nunit +#if nunit +using NUnit.Framework; + +// Unit tests for the DotZLib class library +// ---------------------------------------- +// +// Use this with NUnit 2 from http://www.nunit.org +// + +namespace DotZLibTests +{ + using DotZLib; + + // helper methods + internal class Utils + { + public static bool byteArrEqual( byte[] lhs, byte[] rhs ) + { + if (lhs.Length != rhs.Length) + return false; + for (int i = lhs.Length-1; i >= 0; --i) + if (lhs[i] != rhs[i]) + return false; + return true; + } + + } + + + [TestFixture] + public class CircBufferTests + { + #region Circular buffer tests + [Test] + public void SinglePutGet() + { + CircularBuffer buf = new CircularBuffer(10); + Assert.AreEqual( 0, buf.Size ); + Assert.AreEqual( -1, buf.Get() ); + + Assert.IsTrue(buf.Put( 1 )); + Assert.AreEqual( 1, buf.Size ); + Assert.AreEqual( 1, buf.Get() ); + Assert.AreEqual( 0, buf.Size ); + Assert.AreEqual( -1, buf.Get() ); + } + + [Test] + public void BlockPutGet() + { + CircularBuffer buf = new CircularBuffer(10); + byte[] arr = {1,2,3,4,5,6,7,8,9,10}; + Assert.AreEqual( 10, buf.Put(arr,0,10) ); + Assert.AreEqual( 10, buf.Size ); + Assert.IsFalse( buf.Put(11) ); + Assert.AreEqual( 1, buf.Get() ); + Assert.IsTrue( buf.Put(11) ); + + byte[] arr2 = (byte[])arr.Clone(); + Assert.AreEqual( 9, buf.Get(arr2,1,9) ); + Assert.IsTrue( Utils.byteArrEqual(arr,arr2) ); + } + + #endregion + } + + [TestFixture] + public class ChecksumTests + { + #region CRC32 Tests + [Test] + public void CRC32_Null() + { + CRC32Checksum crc32 = new CRC32Checksum(); + Assert.AreEqual( 0, crc32.Value ); + + crc32 = new CRC32Checksum(1); + Assert.AreEqual( 1, crc32.Value ); + + crc32 = new CRC32Checksum(556); + Assert.AreEqual( 556, crc32.Value ); + } + + [Test] + public void CRC32_Data() + { + CRC32Checksum crc32 = new CRC32Checksum(); + byte[] data = { 1,2,3,4,5,6,7 }; + crc32.Update(data); + Assert.AreEqual( 0x70e46888, crc32.Value ); + + crc32 = new CRC32Checksum(); + crc32.Update("penguin"); + Assert.AreEqual( 0x0e5c1a120, crc32.Value ); + + crc32 = new CRC32Checksum(1); + crc32.Update("penguin"); + Assert.AreEqual(0x43b6aa94, crc32.Value); + + } + #endregion + + #region Adler tests + + [Test] + public void Adler_Null() + { + AdlerChecksum adler = new AdlerChecksum(); + Assert.AreEqual(0, adler.Value); + + adler = new AdlerChecksum(1); + Assert.AreEqual( 1, adler.Value ); + + adler = new AdlerChecksum(556); + Assert.AreEqual( 556, adler.Value ); + } + + [Test] + public void Adler_Data() + { + AdlerChecksum adler = new AdlerChecksum(1); + byte[] data = { 1,2,3,4,5,6,7 }; + adler.Update(data); + Assert.AreEqual( 0x5b001d, adler.Value ); + + adler = new AdlerChecksum(); + adler.Update("penguin"); + Assert.AreEqual(0x0bcf02f6, adler.Value ); + + adler = new AdlerChecksum(1); + adler.Update("penguin"); + Assert.AreEqual(0x0bd602f7, adler.Value); + + } + #endregion + } + + [TestFixture] + public class InfoTests + { + #region Info tests + [Test] + public void Info_Version() + { + Info info = new Info(); + Assert.AreEqual("1.2.8", Info.Version); + Assert.AreEqual(32, info.SizeOfUInt); + Assert.AreEqual(32, info.SizeOfULong); + Assert.AreEqual(32, info.SizeOfPointer); + Assert.AreEqual(32, info.SizeOfOffset); + } + #endregion + } + + [TestFixture] + public class DeflateInflateTests + { + #region Deflate tests + [Test] + public void Deflate_Init() + { + using (Deflater def = new Deflater(CompressLevel.Default)) + { + } + } + + private ArrayList compressedData = new ArrayList(); + private uint adler1; + + private ArrayList uncompressedData = new ArrayList(); + private uint adler2; + + public void CDataAvail(byte[] data, int startIndex, int count) + { + for (int i = 0; i < count; ++i) + compressedData.Add(data[i+startIndex]); + } + + [Test] + public void Deflate_Compress() + { + compressedData.Clear(); + + byte[] testData = new byte[35000]; + for (int i = 0; i < testData.Length; ++i) + testData[i] = 5; + + using (Deflater def = new Deflater((CompressLevel)5)) + { + def.DataAvailable += new DataAvailableHandler(CDataAvail); + def.Add(testData); + def.Finish(); + adler1 = def.Checksum; + } + } + #endregion + + #region Inflate tests + [Test] + public void Inflate_Init() + { + using (Inflater inf = new Inflater()) + { + } + } + + private void DDataAvail(byte[] data, int startIndex, int count) + { + for (int i = 0; i < count; ++i) + uncompressedData.Add(data[i+startIndex]); + } + + [Test] + public void Inflate_Expand() + { + uncompressedData.Clear(); + + using (Inflater inf = new Inflater()) + { + inf.DataAvailable += new DataAvailableHandler(DDataAvail); + inf.Add((byte[])compressedData.ToArray(typeof(byte))); + inf.Finish(); + adler2 = inf.Checksum; + } + Assert.AreEqual( adler1, adler2 ); + } + #endregion + } + + [TestFixture] + public class GZipStreamTests + { + #region GZipStream test + [Test] + public void GZipStream_WriteRead() + { + using (GZipStream gzOut = new GZipStream("gzstream.gz", CompressLevel.Best)) + { + BinaryWriter writer = new BinaryWriter(gzOut); + writer.Write("hi there"); + writer.Write(Math.PI); + writer.Write(42); + } + + using (GZipStream gzIn = new GZipStream("gzstream.gz")) + { + BinaryReader reader = new BinaryReader(gzIn); + string s = reader.ReadString(); + Assert.AreEqual("hi there",s); + double d = reader.ReadDouble(); + Assert.AreEqual(Math.PI, d); + int i = reader.ReadInt32(); + Assert.AreEqual(42,i); + } + + } + #endregion + } +} + +#endif diff --git a/zlib/zlib/contrib/dotzlib/LICENSE_1_0.txt b/zlib/zlib/contrib/dotzlib/LICENSE_1_0.txt new file mode 100644 index 00000000..127a5bc3 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/zlib/zlib/contrib/dotzlib/readme.txt b/zlib/zlib/contrib/dotzlib/readme.txt new file mode 100644 index 00000000..4d8c2dd9 --- /dev/null +++ b/zlib/zlib/contrib/dotzlib/readme.txt @@ -0,0 +1,58 @@ +This directory contains a .Net wrapper class library for the ZLib1.dll + +The wrapper includes support for inflating/deflating memory buffers, +.Net streaming wrappers for the gz streams part of zlib, and wrappers +for the checksum parts of zlib. See DotZLib/UnitTests.cs for examples. + +Directory structure: +-------------------- + +LICENSE_1_0.txt - License file. +readme.txt - This file. +DotZLib.chm - Class library documentation +DotZLib.build - NAnt build file +DotZLib.sln - Microsoft Visual Studio 2003 solution file + +DotZLib\*.cs - Source files for the class library + +Unit tests: +----------- +The file DotZLib/UnitTests.cs contains unit tests for use with NUnit 2.1 or higher. +To include unit tests in the build, define nunit before building. + + +Build instructions: +------------------- + +1. Using Visual Studio.Net 2003: + Open DotZLib.sln in VS.Net and build from there. Output file (DotZLib.dll) + will be found ./DotZLib/bin/release or ./DotZLib/bin/debug, depending on + you are building the release or debug version of the library. Check + DotZLib/UnitTests.cs for instructions on how to include unit tests in the + build. + +2. Using NAnt: + Open a command prompt with access to the build environment and run nant + in the same directory as the DotZLib.build file. + You can define 2 properties on the nant command-line to control the build: + debug={true|false} to toggle between release/debug builds (default=true). + nunit={true|false} to include or esclude unit tests (default=true). + Also the target clean will remove binaries. + Output file (DotZLib.dll) will be found in either ./DotZLib/bin/release + or ./DotZLib/bin/debug, depending on whether you are building the release + or debug version of the library. + + Examples: + nant -D:debug=false -D:nunit=false + will build a release mode version of the library without unit tests. + nant + will build a debug version of the library with unit tests + nant clean + will remove all previously built files. + + +--------------------------------- +Copyright (c) Henrik Ravn 2004 + +Use, modification and distribution are subject to the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/zlib/zlib/contrib/gcc_gvmat64/gvmat64.S b/zlib/zlib/contrib/gcc_gvmat64/gvmat64.S new file mode 100644 index 00000000..dd858ddb --- /dev/null +++ b/zlib/zlib/contrib/gcc_gvmat64/gvmat64.S @@ -0,0 +1,574 @@ +/* +;uInt longest_match_x64( +; deflate_state *s, +; IPos cur_match); // current match + +; gvmat64.S -- Asm portion of the optimized longest_match for 32 bits x86_64 +; (AMD64 on Athlon 64, Opteron, Phenom +; and Intel EM64T on Pentium 4 with EM64T, Pentium D, Core 2 Duo, Core I5/I7) +; this file is translation from gvmat64.asm to GCC 4.x (for Linux, Mac XCode) +; Copyright (C) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; +; File written by Gilles Vollant, by converting to assembly the longest_match +; from Jean-loup Gailly in deflate.c of zLib and infoZip zip. +; and by taking inspiration on asm686 with masm, optimised assembly code +; from Brian Raiter, written 1998 +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software +; 3. This notice may not be removed or altered from any source distribution. +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; to compile this file for zLib, I use option: +; gcc -c -arch x86_64 gvmat64.S + + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; // current match / +; +; with XCode for Mac, I had strange error with some jump on intel syntax +; this is why BEFORE_JMP and AFTER_JMP are used + */ + + +#define BEFORE_JMP .att_syntax +#define AFTER_JMP .intel_syntax noprefix + +#ifndef NO_UNDERLINE +# define match_init _match_init +# define longest_match _longest_match +#endif + +.intel_syntax noprefix + +.globl match_init, longest_match +.text +longest_match: + + + +#define LocalVarsSize 96 +/* +; register used : rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12 +; free register : r14,r15 +; register can be saved : rsp +*/ + +#define chainlenwmask (rsp + 8 - LocalVarsSize) +#define nicematch (rsp + 16 - LocalVarsSize) + +#define save_rdi (rsp + 24 - LocalVarsSize) +#define save_rsi (rsp + 32 - LocalVarsSize) +#define save_rbx (rsp + 40 - LocalVarsSize) +#define save_rbp (rsp + 48 - LocalVarsSize) +#define save_r12 (rsp + 56 - LocalVarsSize) +#define save_r13 (rsp + 64 - LocalVarsSize) +#define save_r14 (rsp + 72 - LocalVarsSize) +#define save_r15 (rsp + 80 - LocalVarsSize) + + +/* +; all the +4 offsets are due to the addition of pending_buf_size (in zlib +; in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, remove the +4). +; Note : these value are good with a 8 bytes boundary pack structure +*/ + +#define MAX_MATCH 258 +#define MIN_MATCH 3 +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) + +/* +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). +*/ + + + +/* you can check the structure offset by running + +#include +#include +#include "deflate.h" + +void print_depl() +{ +deflate_state ds; +deflate_state *s=&ds; +printf("size pointer=%u\n",(int)sizeof(void*)); + +printf("#define dsWSize %u\n",(int)(((char*)&(s->w_size))-((char*)s))); +printf("#define dsWMask %u\n",(int)(((char*)&(s->w_mask))-((char*)s))); +printf("#define dsWindow %u\n",(int)(((char*)&(s->window))-((char*)s))); +printf("#define dsPrev %u\n",(int)(((char*)&(s->prev))-((char*)s))); +printf("#define dsMatchLen %u\n",(int)(((char*)&(s->match_length))-((char*)s))); +printf("#define dsPrevMatch %u\n",(int)(((char*)&(s->prev_match))-((char*)s))); +printf("#define dsStrStart %u\n",(int)(((char*)&(s->strstart))-((char*)s))); +printf("#define dsMatchStart %u\n",(int)(((char*)&(s->match_start))-((char*)s))); +printf("#define dsLookahead %u\n",(int)(((char*)&(s->lookahead))-((char*)s))); +printf("#define dsPrevLen %u\n",(int)(((char*)&(s->prev_length))-((char*)s))); +printf("#define dsMaxChainLen %u\n",(int)(((char*)&(s->max_chain_length))-((char*)s))); +printf("#define dsGoodMatch %u\n",(int)(((char*)&(s->good_match))-((char*)s))); +printf("#define dsNiceMatch %u\n",(int)(((char*)&(s->nice_match))-((char*)s))); +} +*/ + +#define dsWSize 68 +#define dsWMask 76 +#define dsWindow 80 +#define dsPrev 96 +#define dsMatchLen 144 +#define dsPrevMatch 148 +#define dsStrStart 156 +#define dsMatchStart 160 +#define dsLookahead 164 +#define dsPrevLen 168 +#define dsMaxChainLen 172 +#define dsGoodMatch 188 +#define dsNiceMatch 192 + +#define window_size [ rcx + dsWSize] +#define WMask [ rcx + dsWMask] +#define window_ad [ rcx + dsWindow] +#define prev_ad [ rcx + dsPrev] +#define strstart [ rcx + dsStrStart] +#define match_start [ rcx + dsMatchStart] +#define Lookahead [ rcx + dsLookahead] //; 0ffffffffh on infozip +#define prev_length [ rcx + dsPrevLen] +#define max_chain_length [ rcx + dsMaxChainLen] +#define good_match [ rcx + dsGoodMatch] +#define nice_match [ rcx + dsNiceMatch] + +/* +; windows: +; parameter 1 in rcx(deflate state s), param 2 in rdx (cur match) + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r9, r10, and r11, which are scratch. + +; +; gcc on macosx-linux: +; see http://www.x86-64.org/documentation/abi-0.99.pdf +; param 1 in rdi, param 2 in rsi +; rbx, rsp, rbp, r12 to r15 must be preserved + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + +;;; Retrieve the function arguments. r8d will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + +; ms: parameter 1 in rcx (deflate_state* s), param 2 in edx -> r8 (cur match) +; mac: param 1 in rdi, param 2 rsi +; this clear high 32 bits of r8, which can be garbage in both r8 and rdx +*/ + mov [save_rbx],rbx + mov [save_rbp],rbp + + + mov rcx,rdi + + mov r8d,esi + + + mov [save_r12],r12 + mov [save_r13],r13 + mov [save_r14],r14 + mov [save_r15],r15 + + +//;;; uInt wmask = s->w_mask; +//;;; unsigned chain_length = s->max_chain_length; +//;;; if (s->prev_length >= s->good_match) { +//;;; chain_length >>= 2; +//;;; } + + + mov edi, prev_length + mov esi, good_match + mov eax, WMask + mov ebx, max_chain_length + cmp edi, esi + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +//;;; chainlen is decremented once beforehand so that the function can +//;;; use the sign flag instead of the zero flag for the exit test. +//;;; It is then shifted into the high word, to make room for the wmask +//;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + +//;;; on zlib only +//;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + + + mov eax, nice_match + mov [chainlenwmask], ebx + mov r10d, Lookahead + cmp r10d, eax + cmovnl r10d, eax + mov [nicematch],r10d + + + +//;;; register Bytef *scan = s->window + s->strstart; + mov r10, window_ad + mov ebp, strstart + lea r13, [r10 + rbp] + +//;;; Determine how many bytes the scan ptr is off from being +//;;; dword-aligned. + + mov r9,r13 + neg r13 + and r13,3 + +//;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +//;;; s->strstart - (IPos)MAX_DIST(s) : NIL; + + + mov eax, window_size + sub eax, MIN_LOOKAHEAD + + + xor edi,edi + sub ebp, eax + + mov r11d, prev_length + + cmovng ebp,edi + +//;;; int best_len = s->prev_length; + + +//;;; Store the sum of s->window + best_len in esi locally, and in esi. + + lea rsi,[r10+r11] + +//;;; register ush scan_start = *(ushf*)scan; +//;;; register ush scan_end = *(ushf*)(scan+best_len-1); +//;;; Posf *prev = s->prev; + + movzx r12d,word ptr [r9] + movzx ebx, word ptr [r9 + r11 - 1] + + mov rdi, prev_ad + +//;;; Jump into the main loop. + + mov edx, [chainlenwmask] + + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + + + +LookupLoop1: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + + + + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry1: + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jz LookupLoopIsZero + AFTER_JMP + +LookupLoop2: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry2: + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jz LookupLoopIsZero + AFTER_JMP + +LookupLoop4: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry4: + + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jnz LookupLoop1 + jmp LookupLoopIsZero + AFTER_JMP +/* +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; r8d = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit +*/ +.balign 16 +LookupLoop: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry: + + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jnz LookupLoop1 + AFTER_JMP +LookupLoopIsZero: + cmp r12w, word ptr [r10 + r8] + BEFORE_JMP + jnz LookupLoop1 + AFTER_JMP + + +//;;; Store the current value of chainlen. + mov [chainlenwmask], edx +/* +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). +*/ + lea rsi,[r8+r10] + mov rdx, 0xfffffffffffffef8 //; -(MAX_MATCH_8) + lea rsi, [rsi + r13 + 0x0108] //;MAX_MATCH_8] + lea rdi, [r9 + r13 + 0x0108] //;MAX_MATCH_8] + + prefetcht1 [rsi+rdx] + prefetcht1 [rdi+rdx] + +/* +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust rdx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (rsi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. +*/ + +LoopCmps: + mov rax, [rsi + rdx] + xor rax, [rdi + rdx] + jnz LeaveLoopCmps + + mov rax, [rsi + rdx + 8] + xor rax, [rdi + rdx + 8] + jnz LeaveLoopCmps8 + + + mov rax, [rsi + rdx + 8+8] + xor rax, [rdi + rdx + 8+8] + jnz LeaveLoopCmps16 + + add rdx,8+8+8 + + BEFORE_JMP + jnz LoopCmps + jmp LenMaximum + AFTER_JMP + +LeaveLoopCmps16: add rdx,8 +LeaveLoopCmps8: add rdx,8 +LeaveLoopCmps: + + test eax, 0x0000FFFF + jnz LenLower + + test eax,0xffffffff + + jnz LenLower32 + + add rdx,4 + shr rax,32 + or ax,ax + BEFORE_JMP + jnz LenLower + AFTER_JMP + +LenLower32: + shr eax,16 + add rdx,2 + +LenLower: + sub al, 1 + adc rdx, 0 +//;;; Calculate the length of the match. If it is longer than MAX_MATCH, +//;;; then automatically accept it as the best possible match and leave. + + lea rax, [rdi + rdx] + sub rax, r9 + cmp eax, MAX_MATCH + BEFORE_JMP + jge LenMaximum + AFTER_JMP +/* +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. +;/////////////////////////////////// +*/ + cmp eax, r11d + jg LongerMatch + + lea rsi,[r10+r11] + + mov rdi, prev_ad + mov edx, [chainlenwmask] + BEFORE_JMP + jmp LookupLoop + AFTER_JMP +/* +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); +*/ +LongerMatch: + mov r11d, eax + mov match_start, r8d + cmp eax, [nicematch] + BEFORE_JMP + jge LeaveNow + AFTER_JMP + + lea rsi,[r10+rax] + + movzx ebx, word ptr [r9 + rax - 1] + mov rdi, prev_ad + mov edx, [chainlenwmask] + BEFORE_JMP + jmp LookupLoop + AFTER_JMP + +//;;; Accept the current string, with the maximum possible length. + +LenMaximum: + mov r11d,MAX_MATCH + mov match_start, r8d + +//;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +//;;; return s->lookahead; + +LeaveNow: + mov eax, Lookahead + cmp r11d, eax + cmovng eax, r11d + + + +//;;; Restore the stack and return from whence we came. + + +// mov rsi,[save_rsi] +// mov rdi,[save_rdi] + mov rbx,[save_rbx] + mov rbp,[save_rbp] + mov r12,[save_r12] + mov r13,[save_r13] + mov r14,[save_r14] + mov r15,[save_r15] + + + ret 0 +//; please don't remove this string ! +//; Your can freely use gvmat64 in any free or commercial app +//; but it is far better don't remove the string in the binary! + // db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998, converted to amd 64 by Gilles Vollant 2005",0dh,0ah,0 + + +match_init: + ret 0 + + diff --git a/zlib/zlib/contrib/infback9/README b/zlib/zlib/contrib/infback9/README new file mode 100644 index 00000000..e75ed132 --- /dev/null +++ b/zlib/zlib/contrib/infback9/README @@ -0,0 +1 @@ +See infback9.h for what this is and how to use it. diff --git a/zlib/zlib/contrib/infback9/infback9.c b/zlib/zlib/contrib/infback9/infback9.c new file mode 100644 index 00000000..05fb3e33 --- /dev/null +++ b/zlib/zlib/contrib/infback9/infback9.c @@ -0,0 +1,615 @@ +/* infback9.c -- inflate deflate64 data using a call-back interface + * Copyright (C) 1995-2008 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "infback9.h" +#include "inftree9.h" +#include "inflate9.h" + +#define WSIZE 65536UL + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + window is a user-supplied window and output buffer that is 64K bytes. + */ +int ZEXPORT inflateBack9Init_(strm, window, version, stream_size) +z_stream FAR *strm; +unsigned char FAR *window; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (voidpf)state; + state->window = window; + return Z_OK; +} + +/* + Build and output length and distance decoding tables for fixed code + decoding. + */ +#ifdef MAKEFIXED +#include + +void makefixed9(void) +{ + unsigned sym, bits, low, size; + code *next, *lenfix, *distfix; + struct inflate_state state; + code fixed[544]; + + /* literal/length table */ + sym = 0; + while (sym < 144) state.lens[sym++] = 8; + while (sym < 256) state.lens[sym++] = 9; + while (sym < 280) state.lens[sym++] = 7; + while (sym < 288) state.lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table9(LENS, state.lens, 288, &(next), &(bits), state.work); + + /* distance table */ + sym = 0; + while (sym < 32) state.lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table9(DISTS, state.lens, 32, &(next), &(bits), state.work); + + /* write tables */ + puts(" /* inffix9.h -- table for decoding deflate64 fixed codes"); + puts(" * Generated automatically by makefixed9()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", lenfix[low].op, lenfix[low].bits, + lenfix[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 5) == 0) printf("\n "); + printf("{%u,%u,%d}", distfix[low].op, distfix[low].bits, + distfix[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* Macros for inflateBack(): */ + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n <= 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = window; \ + left = WSIZE; \ + wrap = 1; \ + if (out(out_desc, put, (unsigned)left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack9(strm, in, in_desc, out, out_desc) +z_stream FAR *strm; +in_func in; +void FAR *in_desc; +out_func out; +void FAR *out_desc; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have; /* available input */ + unsigned long left; /* available output */ + inflate_mode mode; /* current inflate mode */ + int lastblock; /* true if processing last block */ + int wrap; /* true if the window has wrapped */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned extra; /* extra bits needed */ + unsigned long length; /* literal or length of data to copy */ + unsigned long offset; /* distance back to copy string from */ + unsigned long copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; +#include "inffix9.h" + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + mode = TYPE; + lastblock = 0; + wrap = 0; + window = state->window; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = window; + left = WSIZE; + lencode = Z_NULL; + distcode = Z_NULL; + + /* Inflate until end of block marked as last */ + for (;;) + switch (mode) { + case TYPE: + /* determine and dispatch block type */ + if (lastblock) { + BYTEBITS(); + mode = DONE; + break; + } + NEEDBITS(3); + lastblock = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + lastblock ? " (last)" : "")); + mode = STORED; + break; + case 1: /* fixed block */ + lencode = lenfix; + lenbits = 9; + distcode = distfix; + distbits = 5; + Tracev((stderr, "inflate: fixed codes block%s\n", + lastblock ? " (last)" : "")); + mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + lastblock ? " (last)" : "")); + mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + mode = BAD; + break; + } + length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %lu\n", + length)); + INITBITS(); + + /* copy stored block from input to output */ + while (length != 0) { + copy = length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); + if (state->nlen > 286) { + strm->msg = (char *)"too many length symbols"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + lencode = (code const FAR *)(state->next); + lenbits = 7; + ret = inflate_table9(CODES, state->lens, 19, &(state->next), + &(lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = lencode[BITS(lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + NEEDBITS(here.bits); + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftree9.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + lencode = (code const FAR *)(state->next); + lenbits = 9; + ret = inflate_table9(LENS, state->lens, state->nlen, + &(state->next), &(lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + mode = BAD; + break; + } + distcode = (code const FAR *)(state->next); + distbits = 6; + ret = inflate_table9(DISTS, state->lens + state->nlen, + state->ndist, &(state->next), &(distbits), + state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + mode = LEN; + + case LEN: + /* get a literal, length, or end-of-block code */ + for (;;) { + here = lencode[BITS(lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + length = (unsigned)here.val; + + /* process literal */ + if (here.op == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + ROOM(); + *put++ = (unsigned char)(length); + left--; + mode = LEN; + break; + } + + /* process end of block */ + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + mode = TYPE; + break; + } + + /* invalid code */ + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + extra = (unsigned)(here.op) & 31; + if (extra != 0) { + NEEDBITS(extra); + length += BITS(extra); + DROPBITS(extra); + } + Tracevv((stderr, "inflate: length %lu\n", length)); + + /* get distance code */ + for (;;) { + here = distcode[BITS(distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + mode = BAD; + break; + } + offset = (unsigned)here.val; + + /* get distance extra bits, if any */ + extra = (unsigned)(here.op) & 15; + if (extra != 0) { + NEEDBITS(extra); + offset += BITS(extra); + DROPBITS(extra); + } + if (offset > WSIZE - (wrap ? 0: left)) { + strm->msg = (char *)"invalid distance too far back"; + mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %lu\n", offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = WSIZE - offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - offset; + copy = left; + } + if (copy > length) copy = length; + length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < WSIZE) { + if (out(out_desc, window, (unsigned)(WSIZE - left))) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBack9End(strm) +z_stream FAR *strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/zlib/zlib/contrib/infback9/infback9.h b/zlib/zlib/contrib/infback9/infback9.h new file mode 100644 index 00000000..1073c0a3 --- /dev/null +++ b/zlib/zlib/contrib/infback9/infback9.h @@ -0,0 +1,37 @@ +/* infback9.h -- header for using inflateBack9 functions + * Copyright (C) 2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * This header file and associated patches provide a decoder for PKWare's + * undocumented deflate64 compression method (method 9). Use with infback9.c, + * inftree9.h, inftree9.c, and inffix9.h. These patches are not supported. + * This should be compiled with zlib, since it uses zutil.h and zutil.o. + * This code has not yet been tested on 16-bit architectures. See the + * comments in zlib.h for inflateBack() usage. These functions are used + * identically, except that there is no windowBits parameter, and a 64K + * window must be provided. Also if int's are 16 bits, then a zero for + * the third parameter of the "out" function actually means 65536UL. + * zlib.h must be included before this header file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +ZEXTERN int ZEXPORT inflateBack9 OF((z_stream FAR *strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +ZEXTERN int ZEXPORT inflateBack9End OF((z_stream FAR *strm)); +ZEXTERN int ZEXPORT inflateBack9Init_ OF((z_stream FAR *strm, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define inflateBack9Init(strm, window) \ + inflateBack9Init_((strm), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + +#ifdef __cplusplus +} +#endif diff --git a/zlib/zlib/contrib/infback9/inffix9.h b/zlib/zlib/contrib/infback9/inffix9.h new file mode 100644 index 00000000..ee5671d2 --- /dev/null +++ b/zlib/zlib/contrib/infback9/inffix9.h @@ -0,0 +1,107 @@ + /* inffix9.h -- table for decoding deflate64 fixed codes + * Generated automatically by makefixed9(). + */ + + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{132,8,115},{130,7,31},{0,8,112}, + {0,8,48},{0,9,192},{128,7,10},{0,8,96},{0,8,32},{0,9,160}, + {0,8,0},{0,8,128},{0,8,64},{0,9,224},{128,7,6},{0,8,88}, + {0,8,24},{0,9,144},{131,7,59},{0,8,120},{0,8,56},{0,9,208}, + {129,7,17},{0,8,104},{0,8,40},{0,9,176},{0,8,8},{0,8,136}, + {0,8,72},{0,9,240},{128,7,4},{0,8,84},{0,8,20},{133,8,227}, + {131,7,43},{0,8,116},{0,8,52},{0,9,200},{129,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232}, + {128,7,8},{0,8,92},{0,8,28},{0,9,152},{132,7,83},{0,8,124}, + {0,8,60},{0,9,216},{130,7,23},{0,8,108},{0,8,44},{0,9,184}, + {0,8,12},{0,8,140},{0,8,76},{0,9,248},{128,7,3},{0,8,82}, + {0,8,18},{133,8,163},{131,7,35},{0,8,114},{0,8,50},{0,9,196}, + {129,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},{0,8,130}, + {0,8,66},{0,9,228},{128,7,7},{0,8,90},{0,8,26},{0,9,148}, + {132,7,67},{0,8,122},{0,8,58},{0,9,212},{130,7,19},{0,8,106}, + {0,8,42},{0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244}, + {128,7,5},{0,8,86},{0,8,22},{65,8,0},{131,7,51},{0,8,118}, + {0,8,54},{0,9,204},{129,7,15},{0,8,102},{0,8,38},{0,9,172}, + {0,8,6},{0,8,134},{0,8,70},{0,9,236},{128,7,9},{0,8,94}, + {0,8,30},{0,9,156},{132,7,99},{0,8,126},{0,8,62},{0,9,220}, + {130,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{133,8,131}, + {130,7,31},{0,8,113},{0,8,49},{0,9,194},{128,7,10},{0,8,97}, + {0,8,33},{0,9,162},{0,8,1},{0,8,129},{0,8,65},{0,9,226}, + {128,7,6},{0,8,89},{0,8,25},{0,9,146},{131,7,59},{0,8,121}, + {0,8,57},{0,9,210},{129,7,17},{0,8,105},{0,8,41},{0,9,178}, + {0,8,9},{0,8,137},{0,8,73},{0,9,242},{128,7,4},{0,8,85}, + {0,8,21},{144,8,3},{131,7,43},{0,8,117},{0,8,53},{0,9,202}, + {129,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133}, + {0,8,69},{0,9,234},{128,7,8},{0,8,93},{0,8,29},{0,9,154}, + {132,7,83},{0,8,125},{0,8,61},{0,9,218},{130,7,23},{0,8,109}, + {0,8,45},{0,9,186},{0,8,13},{0,8,141},{0,8,77},{0,9,250}, + {128,7,3},{0,8,83},{0,8,19},{133,8,195},{131,7,35},{0,8,115}, + {0,8,51},{0,9,198},{129,7,11},{0,8,99},{0,8,35},{0,9,166}, + {0,8,3},{0,8,131},{0,8,67},{0,9,230},{128,7,7},{0,8,91}, + {0,8,27},{0,9,150},{132,7,67},{0,8,123},{0,8,59},{0,9,214}, + {130,7,19},{0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139}, + {0,8,75},{0,9,246},{128,7,5},{0,8,87},{0,8,23},{77,8,0}, + {131,7,51},{0,8,119},{0,8,55},{0,9,206},{129,7,15},{0,8,103}, + {0,8,39},{0,9,174},{0,8,7},{0,8,135},{0,8,71},{0,9,238}, + {128,7,9},{0,8,95},{0,8,31},{0,9,158},{132,7,99},{0,8,127}, + {0,8,63},{0,9,222},{130,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80}, + {0,8,16},{132,8,115},{130,7,31},{0,8,112},{0,8,48},{0,9,193}, + {128,7,10},{0,8,96},{0,8,32},{0,9,161},{0,8,0},{0,8,128}, + {0,8,64},{0,9,225},{128,7,6},{0,8,88},{0,8,24},{0,9,145}, + {131,7,59},{0,8,120},{0,8,56},{0,9,209},{129,7,17},{0,8,104}, + {0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},{0,9,241}, + {128,7,4},{0,8,84},{0,8,20},{133,8,227},{131,7,43},{0,8,116}, + {0,8,52},{0,9,201},{129,7,13},{0,8,100},{0,8,36},{0,9,169}, + {0,8,4},{0,8,132},{0,8,68},{0,9,233},{128,7,8},{0,8,92}, + {0,8,28},{0,9,153},{132,7,83},{0,8,124},{0,8,60},{0,9,217}, + {130,7,23},{0,8,108},{0,8,44},{0,9,185},{0,8,12},{0,8,140}, + {0,8,76},{0,9,249},{128,7,3},{0,8,82},{0,8,18},{133,8,163}, + {131,7,35},{0,8,114},{0,8,50},{0,9,197},{129,7,11},{0,8,98}, + {0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {128,7,7},{0,8,90},{0,8,26},{0,9,149},{132,7,67},{0,8,122}, + {0,8,58},{0,9,213},{130,7,19},{0,8,106},{0,8,42},{0,9,181}, + {0,8,10},{0,8,138},{0,8,74},{0,9,245},{128,7,5},{0,8,86}, + {0,8,22},{65,8,0},{131,7,51},{0,8,118},{0,8,54},{0,9,205}, + {129,7,15},{0,8,102},{0,8,38},{0,9,173},{0,8,6},{0,8,134}, + {0,8,70},{0,9,237},{128,7,9},{0,8,94},{0,8,30},{0,9,157}, + {132,7,99},{0,8,126},{0,8,62},{0,9,221},{130,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253}, + {96,7,0},{0,8,81},{0,8,17},{133,8,131},{130,7,31},{0,8,113}, + {0,8,49},{0,9,195},{128,7,10},{0,8,97},{0,8,33},{0,9,163}, + {0,8,1},{0,8,129},{0,8,65},{0,9,227},{128,7,6},{0,8,89}, + {0,8,25},{0,9,147},{131,7,59},{0,8,121},{0,8,57},{0,9,211}, + {129,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},{0,8,137}, + {0,8,73},{0,9,243},{128,7,4},{0,8,85},{0,8,21},{144,8,3}, + {131,7,43},{0,8,117},{0,8,53},{0,9,203},{129,7,13},{0,8,101}, + {0,8,37},{0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235}, + {128,7,8},{0,8,93},{0,8,29},{0,9,155},{132,7,83},{0,8,125}, + {0,8,61},{0,9,219},{130,7,23},{0,8,109},{0,8,45},{0,9,187}, + {0,8,13},{0,8,141},{0,8,77},{0,9,251},{128,7,3},{0,8,83}, + {0,8,19},{133,8,195},{131,7,35},{0,8,115},{0,8,51},{0,9,199}, + {129,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{128,7,7},{0,8,91},{0,8,27},{0,9,151}, + {132,7,67},{0,8,123},{0,8,59},{0,9,215},{130,7,19},{0,8,107}, + {0,8,43},{0,9,183},{0,8,11},{0,8,139},{0,8,75},{0,9,247}, + {128,7,5},{0,8,87},{0,8,23},{77,8,0},{131,7,51},{0,8,119}, + {0,8,55},{0,9,207},{129,7,15},{0,8,103},{0,8,39},{0,9,175}, + {0,8,7},{0,8,135},{0,8,71},{0,9,239},{128,7,9},{0,8,95}, + {0,8,31},{0,9,159},{132,7,99},{0,8,127},{0,8,63},{0,9,223}, + {130,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143}, + {0,8,79},{0,9,255} + }; + + static const code distfix[32] = { + {128,5,1},{135,5,257},{131,5,17},{139,5,4097},{129,5,5}, + {137,5,1025},{133,5,65},{141,5,16385},{128,5,3},{136,5,513}, + {132,5,33},{140,5,8193},{130,5,9},{138,5,2049},{134,5,129}, + {142,5,32769},{128,5,2},{135,5,385},{131,5,25},{139,5,6145}, + {129,5,7},{137,5,1537},{133,5,97},{141,5,24577},{128,5,4}, + {136,5,769},{132,5,49},{140,5,12289},{130,5,13},{138,5,3073}, + {134,5,193},{142,5,49153} + }; diff --git a/zlib/zlib/contrib/infback9/inflate9.h b/zlib/zlib/contrib/infback9/inflate9.h new file mode 100644 index 00000000..ee9a7939 --- /dev/null +++ b/zlib/zlib/contrib/infback9/inflate9.h @@ -0,0 +1,47 @@ +/* inflate9.h -- internal inflate state definition + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Possible inflate modes between inflate() calls */ +typedef enum { + TYPE, /* i: waiting for type bits, including last-flag bit */ + STORED, /* i: waiting for stored size (length and complement) */ + TABLE, /* i: waiting for dynamic block table lengths */ + LEN, /* i: waiting for length/lit code */ + DONE, /* finished check, done -- remain here until reset */ + BAD /* got a data error -- remain here until reset */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD mode -- not shown for clarity) + + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or DONE + STORED -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LEN or TYPE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + /* sliding window */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; diff --git a/zlib/zlib/contrib/infback9/inftree9.c b/zlib/zlib/contrib/infback9/inftree9.c new file mode 100644 index 00000000..4a73ad21 --- /dev/null +++ b/zlib/zlib/contrib/infback9/inftree9.c @@ -0,0 +1,324 @@ +/* inftree9.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftree9.h" + +#define MAXBITS 15 + +const char inflate9_copyright[] = + " inflate9 1.2.8 Copyright 1995-2013 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table9(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code this; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, + 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 3, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 130, 130, 130, 130, 131, 131, 131, 131, 132, 132, 132, 132, + 133, 133, 133, 133, 144, 72, 78}; + static const unsigned short dbase[32] = { /* Distance codes 0..31 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, + 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577, 32769, 49153}; + static const unsigned short dext[32] = { /* Distance codes 0..31 extra */ + 128, 128, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, + 133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138, 138, + 139, 139, 140, 140, 141, 141, 142, 142}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) return -1; /* no codes! */ + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftree9.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type == LENS && used >= ENOUGH_LENS) || + (type == DISTS && used >= ENOUGH_DISTS)) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + this.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + this.op = (unsigned char)0; + this.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + this.op = (unsigned char)(extra[work[sym]]); + this.val = base[work[sym]]; + } + else { + this.op = (unsigned char)(32 + 64); /* end of block */ + this.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + do { + fill -= incr; + next[(huff >> drop) + fill] = this; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += 1U << curr; + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if ((type == LENS && used >= ENOUGH_LENS) || + (type == DISTS && used >= ENOUGH_DISTS)) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)(len - drop); + this.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + curr = root; + this.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = this; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/zlib/zlib/contrib/infback9/inftree9.h b/zlib/zlib/contrib/infback9/inftree9.h new file mode 100644 index 00000000..5ab21f0c --- /dev/null +++ b/zlib/zlib/contrib/infback9/inftree9.h @@ -0,0 +1,61 @@ +/* inftree9.h -- header to use inftree9.c + * Copyright (C) 1995-2008 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 100eeeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of the dynamic table. The maximum number of code structures is + 1446, which is the sum of 852 for literal/length codes and 594 for distance + codes. These values were found by exhaustive searches using the program + examples/enough.c found in the zlib distribtution. The arguments to that + program are the number of symbols, the initial root table size, and the + maximum bit length of a code. "enough 286 9 15" for literal/length codes + returns returns 852, and "enough 32 6 15" for distance codes returns 594. + The initial root table size (9 or 6) is found in the fifth argument of the + inflate_table() calls in infback9.c. If the root table size is changed, + then these maximum sizes would be need to be recalculated and updated. */ +#define ENOUGH_LENS 852 +#define ENOUGH_DISTS 594 +#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS) + +/* Type of code to build for inflate_table9() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table9 OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); diff --git a/zlib/zlib/contrib/inflate86/inffas86.c b/zlib/zlib/contrib/inflate86/inffas86.c new file mode 100644 index 00000000..7292f67b --- /dev/null +++ b/zlib/zlib/contrib/inflate86/inffas86.c @@ -0,0 +1,1157 @@ +/* inffas86.c is a hand tuned assembler version of + * + * inffast.c -- fast decoding + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * Dec-29-2003 -- I added AMD64 inflate asm support. This version is also + * slightly quicker on x86 systems because, instead of using rep movsb to copy + * data, it uses rep movsw, which moves data in 2-byte chunks instead of single + * bytes. I've tested the AMD64 code on a Fedora Core 1 + the x86_64 updates + * from http://fedora.linux.duke.edu/fc1_x86_64 + * which is running on an Athlon 64 3000+ / Gigabyte GA-K8VT800M system with + * 1GB ram. The 64-bit version is about 4% faster than the 32-bit version, + * when decompressing mozilla-source-1.3.tar.gz. + * + * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from + * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at + * the moment. I have successfully compiled and tested this code with gcc2.96, + * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S + * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX + * enabled. I will attempt to merge the MMX code into this version. Newer + * versions of this and inffast.S can be found at + * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* Mark Adler's comments from inffast.c: */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + struct inffast_ar { +/* 64 32 x86 x86_64 */ +/* ar offset register */ +/* 0 0 */ void *esp; /* esp save */ +/* 8 4 */ void *ebp; /* ebp save */ +/* 16 8 */ unsigned char FAR *in; /* esi rsi local strm->next_in */ +/* 24 12 */ unsigned char FAR *last; /* r9 while in < last */ +/* 32 16 */ unsigned char FAR *out; /* edi rdi local strm->next_out */ +/* 40 20 */ unsigned char FAR *beg; /* inflate()'s init next_out */ +/* 48 24 */ unsigned char FAR *end; /* r10 while out < end */ +/* 56 28 */ unsigned char FAR *window;/* size of window, wsize!=0 */ +/* 64 32 */ code const FAR *lcode; /* ebp rbp local strm->lencode */ +/* 72 36 */ code const FAR *dcode; /* r11 local strm->distcode */ +/* 80 40 */ unsigned long hold; /* edx rdx local strm->hold */ +/* 88 44 */ unsigned bits; /* ebx rbx local strm->bits */ +/* 92 48 */ unsigned wsize; /* window size */ +/* 96 52 */ unsigned write; /* window write index */ +/*100 56 */ unsigned lmask; /* r12 mask for lcode */ +/*104 60 */ unsigned dmask; /* r13 mask for dcode */ +/*108 64 */ unsigned len; /* r14 match length */ +/*112 68 */ unsigned dist; /* r15 match distance */ +/*116 72 */ unsigned status; /* set when state chng*/ + } ar; + +#if defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 ) +#define PAD_AVAIL_IN 6 +#define PAD_AVAIL_OUT 258 +#else +#define PAD_AVAIL_IN 5 +#define PAD_AVAIL_OUT 257 +#endif + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + ar.in = strm->next_in; + ar.last = ar.in + (strm->avail_in - PAD_AVAIL_IN); + ar.out = strm->next_out; + ar.beg = ar.out - (start - strm->avail_out); + ar.end = ar.out + (strm->avail_out - PAD_AVAIL_OUT); + ar.wsize = state->wsize; + ar.write = state->wnext; + ar.window = state->window; + ar.hold = state->hold; + ar.bits = state->bits; + ar.lcode = state->lencode; + ar.dcode = state->distcode; + ar.lmask = (1U << state->lenbits) - 1; + ar.dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + /* align in on 1/2 hold size boundary */ + while (((unsigned long)(void *)ar.in & (sizeof(ar.hold) / 2 - 1)) != 0) { + ar.hold += (unsigned long)*ar.in++ << ar.bits; + ar.bits += 8; + } + +#if defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 ) + __asm__ __volatile__ ( +" leaq %0, %%rax\n" +" movq %%rbp, 8(%%rax)\n" /* save regs rbp and rsp */ +" movq %%rsp, (%%rax)\n" +" movq %%rax, %%rsp\n" /* make rsp point to &ar */ +" movq 16(%%rsp), %%rsi\n" /* rsi = in */ +" movq 32(%%rsp), %%rdi\n" /* rdi = out */ +" movq 24(%%rsp), %%r9\n" /* r9 = last */ +" movq 48(%%rsp), %%r10\n" /* r10 = end */ +" movq 64(%%rsp), %%rbp\n" /* rbp = lcode */ +" movq 72(%%rsp), %%r11\n" /* r11 = dcode */ +" movq 80(%%rsp), %%rdx\n" /* rdx = hold */ +" movl 88(%%rsp), %%ebx\n" /* ebx = bits */ +" movl 100(%%rsp), %%r12d\n" /* r12d = lmask */ +" movl 104(%%rsp), %%r13d\n" /* r13d = dmask */ + /* r14d = len */ + /* r15d = dist */ +" cld\n" +" cmpq %%rdi, %%r10\n" +" je .L_one_time\n" /* if only one decode left */ +" cmpq %%rsi, %%r9\n" +" je .L_one_time\n" +" jmp .L_do_loop\n" + +".L_one_time:\n" +" movq %%r12, %%r8\n" /* r8 = lmask */ +" cmpb $32, %%bl\n" +" ja .L_get_length_code_one_time\n" + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ +" jmp .L_get_length_code_one_time\n" + +".align 32,0x90\n" +".L_while_test:\n" +" cmpq %%rdi, %%r10\n" +" jbe .L_break_loop\n" +" cmpq %%rsi, %%r9\n" +" jbe .L_break_loop\n" + +".L_do_loop:\n" +" movq %%r12, %%r8\n" /* r8 = lmask */ +" cmpb $32, %%bl\n" +" ja .L_get_length_code\n" /* if (32 < bits) */ + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ + +".L_get_length_code:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%rbp,%%r8,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" movq %%r12, %%r8\n" /* r8 = lmask */ +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" + +".L_get_length_code_one_time:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%rbp,%%r8,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +".L_dolen:\n" +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_length_base:\n" +" movl %%eax, %%r14d\n" /* len = this */ +" shrl $16, %%r14d\n" /* len = this.val */ +" movb %%al, %%cl\n" + +" testb $16, %%al\n" +" jz .L_test_for_second_level_length\n" /* if ((op & 16) == 0) 8% */ +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_decode_distance\n" /* if (!op) */ + +".L_add_bits_to_len:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrq %%cl, %%rdx\n" +" addl %%eax, %%r14d\n" /* len += hold & mask[op] */ + +".L_decode_distance:\n" +" movq %%r13, %%r8\n" /* r8 = dmask */ +" cmpb $32, %%bl\n" +" ja .L_get_distance_code\n" /* if (32 < bits) */ + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ + +".L_get_distance_code:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%r11,%%r8,4), %%eax\n" /* eax = dcode[hold & dmask] */ + +".L_dodist:\n" +" movl %%eax, %%r15d\n" /* dist = this */ +" shrl $16, %%r15d\n" /* dist = this.val */ +" movb %%ah, %%cl\n" +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ +" movb %%al, %%cl\n" /* cl = this.op */ + +" testb $16, %%al\n" /* if ((op & 16) == 0) */ +" jz .L_test_for_second_level_dist\n" +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_check_dist_one\n" + +".L_add_bits_to_dist:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" /* (1 << op) - 1 */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrq %%cl, %%rdx\n" +" addl %%eax, %%r15d\n" /* dist += hold & ((1 << op) - 1) */ + +".L_check_window:\n" +" movq %%rsi, %%r8\n" /* save in so from can use it's reg */ +" movq %%rdi, %%rax\n" +" subq 40(%%rsp), %%rax\n" /* nbytes = out - beg */ + +" cmpl %%r15d, %%eax\n" +" jb .L_clip_window\n" /* if (dist > nbytes) 4.2% */ + +" movl %%r14d, %%ecx\n" /* ecx = len */ +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ + +" sarl %%ecx\n" +" jnc .L_copy_two\n" /* if len % 2 == 0 */ + +" rep movsw\n" +" movb (%%rsi), %%al\n" +" movb %%al, (%%rdi)\n" +" incq %%rdi\n" + +" movq %%r8, %%rsi\n" /* move in back to %rsi, toss from */ +" jmp .L_while_test\n" + +".L_copy_two:\n" +" rep movsw\n" +" movq %%r8, %%rsi\n" /* move in back to %rsi, toss from */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_check_dist_one:\n" +" cmpl $1, %%r15d\n" /* if dist 1, is a memset */ +" jne .L_check_window\n" +" cmpq %%rdi, 40(%%rsp)\n" /* if out == beg, outside window */ +" je .L_check_window\n" + +" movl %%r14d, %%ecx\n" /* ecx = len */ +" movb -1(%%rdi), %%al\n" +" movb %%al, %%ah\n" + +" sarl %%ecx\n" +" jnc .L_set_two\n" +" movb %%al, (%%rdi)\n" +" incq %%rdi\n" + +".L_set_two:\n" +" rep stosw\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_second_level_length:\n" +" testb $64, %%al\n" +" jnz .L_test_for_end_of_block\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%r14d, %%eax\n" /* eax += len */ +" movl (%%rbp,%%rax,4), %%eax\n" /* eax = lcode[val+(hold&mask[op])]*/ +" jmp .L_dolen\n" + +".align 32,0x90\n" +".L_test_for_second_level_dist:\n" +" testb $64, %%al\n" +" jnz .L_invalid_distance_code\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%r15d, %%eax\n" /* eax += dist */ +" movl (%%r11,%%rax,4), %%eax\n" /* eax = dcode[val+(hold&mask[op])]*/ +" jmp .L_dodist\n" + +".align 32,0x90\n" +".L_clip_window:\n" +" movl %%eax, %%ecx\n" /* ecx = nbytes */ +" movl 92(%%rsp), %%eax\n" /* eax = wsize, prepare for dist cmp */ +" negl %%ecx\n" /* nbytes = -nbytes */ + +" cmpl %%r15d, %%eax\n" +" jb .L_invalid_distance_too_far\n" /* if (dist > wsize) */ + +" addl %%r15d, %%ecx\n" /* nbytes = dist - nbytes */ +" cmpl $0, 96(%%rsp)\n" +" jne .L_wrap_around_window\n" /* if (write != 0) */ + +" movq 56(%%rsp), %%rsi\n" /* from = window */ +" subl %%ecx, %%eax\n" /* eax -= nbytes */ +" addq %%rax, %%rsi\n" /* from += wsize - nbytes */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%r14d\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* eax -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = &out[ -dist ] */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_wrap_around_window:\n" +" movl 96(%%rsp), %%eax\n" /* eax = write */ +" cmpl %%eax, %%ecx\n" +" jbe .L_contiguous_in_window\n" /* if (write >= nbytes) */ + +" movl 92(%%rsp), %%esi\n" /* from = wsize */ +" addq 56(%%rsp), %%rsi\n" /* from += window */ +" addq %%rax, %%rsi\n" /* from += write */ +" subq %%rcx, %%rsi\n" /* from -= nbytes */ +" subl %%eax, %%ecx\n" /* nbytes -= write */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq 56(%%rsp), %%rsi\n" /* from = window */ +" movl 96(%%rsp), %%ecx\n" /* nbytes = write */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_contiguous_in_window:\n" +" movq 56(%%rsp), %%rsi\n" /* rsi = window */ +" addq %%rax, %%rsi\n" +" subq %%rcx, %%rsi\n" /* from += write - nbytes */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ +" jmp .L_do_copy\n" /* if (nbytes >= len) */ + +".align 32,0x90\n" +".L_do_copy:\n" +" movl %%eax, %%ecx\n" /* ecx = len */ +" rep movsb\n" + +" movq %%r8, %%rsi\n" /* move in back to %esi, toss from */ +" jmp .L_while_test\n" + +".L_test_for_end_of_block:\n" +" testb $32, %%al\n" +" jz .L_invalid_literal_length_code\n" +" movl $1, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_literal_length_code:\n" +" movl $2, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_code:\n" +" movl $3, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_too_far:\n" +" movl $4, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_break_loop:\n" +" movl $0, 116(%%rsp)\n" + +".L_break_loop_with_status:\n" +/* put in, out, bits, and hold back into ar and pop esp */ +" movq %%rsi, 16(%%rsp)\n" /* in */ +" movq %%rdi, 32(%%rsp)\n" /* out */ +" movl %%ebx, 88(%%rsp)\n" /* bits */ +" movq %%rdx, 80(%%rsp)\n" /* hold */ +" movq (%%rsp), %%rax\n" /* restore rbp and rsp */ +" movq 8(%%rsp), %%rbp\n" +" movq %%rax, %%rsp\n" + : + : "m" (ar) + : "memory", "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" + ); +#elif ( defined( __GNUC__ ) || defined( __ICC ) ) && defined( __i386 ) + __asm__ __volatile__ ( +" leal %0, %%eax\n" +" movl %%esp, (%%eax)\n" /* save esp, ebp */ +" movl %%ebp, 4(%%eax)\n" +" movl %%eax, %%esp\n" +" movl 8(%%esp), %%esi\n" /* esi = in */ +" movl 16(%%esp), %%edi\n" /* edi = out */ +" movl 40(%%esp), %%edx\n" /* edx = hold */ +" movl 44(%%esp), %%ebx\n" /* ebx = bits */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ + +" cld\n" +" jmp .L_do_loop\n" + +".align 32,0x90\n" +".L_while_test:\n" +" cmpl %%edi, 24(%%esp)\n" /* out < end */ +" jbe .L_break_loop\n" +" cmpl %%esi, 12(%%esp)\n" /* in < last */ +" jbe .L_break_loop\n" + +".L_do_loop:\n" +" cmpb $15, %%bl\n" +" ja .L_get_length_code\n" /* if (15 < bits) */ + +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ + +".L_get_length_code:\n" +" movl 56(%%esp), %%eax\n" /* eax = lmask */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" movl (%%ebp,%%eax,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +".L_dolen:\n" +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrl %%cl, %%edx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_length_base:\n" +" movl %%eax, %%ecx\n" /* len = this */ +" shrl $16, %%ecx\n" /* len = this.val */ +" movl %%ecx, 64(%%esp)\n" /* save len */ +" movb %%al, %%cl\n" + +" testb $16, %%al\n" +" jz .L_test_for_second_level_length\n" /* if ((op & 16) == 0) 8% */ +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_decode_distance\n" /* if (!op) */ +" cmpb %%cl, %%bl\n" +" jae .L_add_bits_to_len\n" /* if (op <= bits) */ + +" movb %%cl, %%ch\n" /* stash op in ch, freeing cl */ +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ +" movb %%ch, %%cl\n" /* move op back to ecx */ + +".L_add_bits_to_len:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrl %%cl, %%edx\n" +" addl %%eax, 64(%%esp)\n" /* len += hold & mask[op] */ + +".L_decode_distance:\n" +" cmpb $15, %%bl\n" +" ja .L_get_distance_code\n" /* if (15 < bits) */ + +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ + +".L_get_distance_code:\n" +" movl 60(%%esp), %%eax\n" /* eax = dmask */ +" movl 36(%%esp), %%ecx\n" /* ecx = dcode */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" movl (%%ecx,%%eax,4), %%eax\n"/* eax = dcode[hold & dmask] */ + +".L_dodist:\n" +" movl %%eax, %%ebp\n" /* dist = this */ +" shrl $16, %%ebp\n" /* dist = this.val */ +" movb %%ah, %%cl\n" +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrl %%cl, %%edx\n" /* hold >>= this.bits */ +" movb %%al, %%cl\n" /* cl = this.op */ + +" testb $16, %%al\n" /* if ((op & 16) == 0) */ +" jz .L_test_for_second_level_dist\n" +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_check_dist_one\n" +" cmpb %%cl, %%bl\n" +" jae .L_add_bits_to_dist\n" /* if (op <= bits) 97.6% */ + +" movb %%cl, %%ch\n" /* stash op in ch, freeing cl */ +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ +" movb %%ch, %%cl\n" /* move op back to ecx */ + +".L_add_bits_to_dist:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" /* (1 << op) - 1 */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrl %%cl, %%edx\n" +" addl %%eax, %%ebp\n" /* dist += hold & ((1 << op) - 1) */ + +".L_check_window:\n" +" movl %%esi, 8(%%esp)\n" /* save in so from can use it's reg */ +" movl %%edi, %%eax\n" +" subl 20(%%esp), %%eax\n" /* nbytes = out - beg */ + +" cmpl %%ebp, %%eax\n" +" jb .L_clip_window\n" /* if (dist > nbytes) 4.2% */ + +" movl 64(%%esp), %%ecx\n" /* ecx = len */ +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ + +" sarl %%ecx\n" +" jnc .L_copy_two\n" /* if len % 2 == 0 */ + +" rep movsw\n" +" movb (%%esi), %%al\n" +" movb %%al, (%%edi)\n" +" incl %%edi\n" + +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".L_copy_two:\n" +" rep movsw\n" +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_check_dist_one:\n" +" cmpl $1, %%ebp\n" /* if dist 1, is a memset */ +" jne .L_check_window\n" +" cmpl %%edi, 20(%%esp)\n" +" je .L_check_window\n" /* out == beg, if outside window */ + +" movl 64(%%esp), %%ecx\n" /* ecx = len */ +" movb -1(%%edi), %%al\n" +" movb %%al, %%ah\n" + +" sarl %%ecx\n" +" jnc .L_set_two\n" +" movb %%al, (%%edi)\n" +" incl %%edi\n" + +".L_set_two:\n" +" rep stosw\n" +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_second_level_length:\n" +" testb $64, %%al\n" +" jnz .L_test_for_end_of_block\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl 64(%%esp), %%eax\n" /* eax += len */ +" movl (%%ebp,%%eax,4), %%eax\n" /* eax = lcode[val+(hold&mask[op])]*/ +" jmp .L_dolen\n" + +".align 32,0x90\n" +".L_test_for_second_level_dist:\n" +" testb $64, %%al\n" +" jnz .L_invalid_distance_code\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%ebp, %%eax\n" /* eax += dist */ +" movl 36(%%esp), %%ecx\n" /* ecx = dcode */ +" movl (%%ecx,%%eax,4), %%eax\n" /* eax = dcode[val+(hold&mask[op])]*/ +" jmp .L_dodist\n" + +".align 32,0x90\n" +".L_clip_window:\n" +" movl %%eax, %%ecx\n" +" movl 48(%%esp), %%eax\n" /* eax = wsize */ +" negl %%ecx\n" /* nbytes = -nbytes */ +" movl 28(%%esp), %%esi\n" /* from = window */ + +" cmpl %%ebp, %%eax\n" +" jb .L_invalid_distance_too_far\n" /* if (dist > wsize) */ + +" addl %%ebp, %%ecx\n" /* nbytes = dist - nbytes */ +" cmpl $0, 52(%%esp)\n" +" jne .L_wrap_around_window\n" /* if (write != 0) */ + +" subl %%ecx, %%eax\n" +" addl %%eax, %%esi\n" /* from += wsize - nbytes */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_wrap_around_window:\n" +" movl 52(%%esp), %%eax\n" /* eax = write */ +" cmpl %%eax, %%ecx\n" +" jbe .L_contiguous_in_window\n" /* if (write >= nbytes) */ + +" addl 48(%%esp), %%esi\n" /* from += wsize */ +" addl %%eax, %%esi\n" /* from += write */ +" subl %%ecx, %%esi\n" /* from -= nbytes */ +" subl %%eax, %%ecx\n" /* nbytes -= write */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl 28(%%esp), %%esi\n" /* from = window */ +" movl 52(%%esp), %%ecx\n" /* nbytes = write */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_contiguous_in_window:\n" +" addl %%eax, %%esi\n" +" subl %%ecx, %%esi\n" /* from += write - nbytes */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" /* if (nbytes >= len) */ + +".align 32,0x90\n" +".L_do_copy:\n" +" movl %%eax, %%ecx\n" +" rep movsb\n" + +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".L_test_for_end_of_block:\n" +" testb $32, %%al\n" +" jz .L_invalid_literal_length_code\n" +" movl $1, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_literal_length_code:\n" +" movl $2, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_code:\n" +" movl $3, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_too_far:\n" +" movl 8(%%esp), %%esi\n" +" movl $4, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_break_loop:\n" +" movl $0, 72(%%esp)\n" + +".L_break_loop_with_status:\n" +/* put in, out, bits, and hold back into ar and pop esp */ +" movl %%esi, 8(%%esp)\n" /* save in */ +" movl %%edi, 16(%%esp)\n" /* save out */ +" movl %%ebx, 44(%%esp)\n" /* save bits */ +" movl %%edx, 40(%%esp)\n" /* save hold */ +" movl 4(%%esp), %%ebp\n" /* restore esp, ebp */ +" movl (%%esp), %%esp\n" + : + : "m" (ar) + : "memory", "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi" + ); +#elif defined( _MSC_VER ) && ! defined( _M_AMD64 ) + __asm { + lea eax, ar + mov [eax], esp /* save esp, ebp */ + mov [eax+4], ebp + mov esp, eax + mov esi, [esp+8] /* esi = in */ + mov edi, [esp+16] /* edi = out */ + mov edx, [esp+40] /* edx = hold */ + mov ebx, [esp+44] /* ebx = bits */ + mov ebp, [esp+32] /* ebp = lcode */ + + cld + jmp L_do_loop + +ALIGN 4 +L_while_test: + cmp [esp+24], edi + jbe L_break_loop + cmp [esp+12], esi + jbe L_break_loop + +L_do_loop: + cmp bl, 15 + ja L_get_length_code /* if (15 < bits) */ + + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + +L_get_length_code: + mov eax, [esp+56] /* eax = lmask */ + and eax, edx /* eax &= hold */ + mov eax, [ebp+eax*4] /* eax = lcode[hold & lmask] */ + +L_dolen: + mov cl, ah /* cl = this.bits */ + sub bl, ah /* bits -= this.bits */ + shr edx, cl /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base /* if (op != 0) 45.7% */ + + shr eax, 16 /* output this.val char */ + stosb + jmp L_while_test + +ALIGN 4 +L_test_for_length_base: + mov ecx, eax /* len = this */ + shr ecx, 16 /* len = this.val */ + mov [esp+64], ecx /* save len */ + mov cl, al + + test al, 16 + jz L_test_for_second_level_length /* if ((op & 16) == 0) 8% */ + and cl, 15 /* op &= 15 */ + jz L_decode_distance /* if (!op) */ + cmp bl, cl + jae L_add_bits_to_len /* if (op <= bits) */ + + mov ch, cl /* stash op in ch, freeing cl */ + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + mov cl, ch /* move op back to ecx */ + +L_add_bits_to_len: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + shr edx, cl + add [esp+64], eax /* len += hold & mask[op] */ + +L_decode_distance: + cmp bl, 15 + ja L_get_distance_code /* if (15 < bits) */ + + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + +L_get_distance_code: + mov eax, [esp+60] /* eax = dmask */ + mov ecx, [esp+36] /* ecx = dcode */ + and eax, edx /* eax &= hold */ + mov eax, [ecx+eax*4]/* eax = dcode[hold & dmask] */ + +L_dodist: + mov ebp, eax /* dist = this */ + shr ebp, 16 /* dist = this.val */ + mov cl, ah + sub bl, ah /* bits -= this.bits */ + shr edx, cl /* hold >>= this.bits */ + mov cl, al /* cl = this.op */ + + test al, 16 /* if ((op & 16) == 0) */ + jz L_test_for_second_level_dist + and cl, 15 /* op &= 15 */ + jz L_check_dist_one + cmp bl, cl + jae L_add_bits_to_dist /* if (op <= bits) 97.6% */ + + mov ch, cl /* stash op in ch, freeing cl */ + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + mov cl, ch /* move op back to ecx */ + +L_add_bits_to_dist: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax /* (1 << op) - 1 */ + and eax, edx /* eax &= hold */ + shr edx, cl + add ebp, eax /* dist += hold & ((1 << op) - 1) */ + +L_check_window: + mov [esp+8], esi /* save in so from can use it's reg */ + mov eax, edi + sub eax, [esp+20] /* nbytes = out - beg */ + + cmp eax, ebp + jb L_clip_window /* if (dist > nbytes) 4.2% */ + + mov ecx, [esp+64] /* ecx = len */ + mov esi, edi + sub esi, ebp /* from = out - dist */ + + sar ecx, 1 + jnc L_copy_two + + rep movsw + mov al, [esi] + mov [edi], al + inc edi + + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +L_copy_two: + rep movsw + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp ebp, 1 /* if dist 1, is a memset */ + jne L_check_window + cmp [esp+20], edi + je L_check_window /* out == beg, if outside window */ + + mov ecx, [esp+64] /* ecx = len */ + mov al, [edi-1] + mov ah, al + + sar ecx, 1 + jnc L_set_two + mov [edi], al /* memset out with from[-1] */ + inc edi + +L_set_two: + rep stosw + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + test al, 64 + jnz L_test_for_end_of_block /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + add eax, [esp+64] /* eax += len */ + mov eax, [ebp+eax*4] /* eax = lcode[val+(hold&mask[op])]*/ + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + test al, 64 + jnz L_invalid_distance_code /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + add eax, ebp /* eax += dist */ + mov ecx, [esp+36] /* ecx = dcode */ + mov eax, [ecx+eax*4] /* eax = dcode[val+(hold&mask[op])]*/ + jmp L_dodist + +ALIGN 4 +L_clip_window: + mov ecx, eax + mov eax, [esp+48] /* eax = wsize */ + neg ecx /* nbytes = -nbytes */ + mov esi, [esp+28] /* from = window */ + + cmp eax, ebp + jb L_invalid_distance_too_far /* if (dist > wsize) */ + + add ecx, ebp /* nbytes = dist - nbytes */ + cmp dword ptr [esp+52], 0 + jne L_wrap_around_window /* if (write != 0) */ + + sub eax, ecx + add esi, eax /* from += wsize - nbytes */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_wrap_around_window: + mov eax, [esp+52] /* eax = write */ + cmp ecx, eax + jbe L_contiguous_in_window /* if (write >= nbytes) */ + + add esi, [esp+48] /* from += wsize */ + add esi, eax /* from += write */ + sub esi, ecx /* from -= nbytes */ + sub ecx, eax /* nbytes -= write */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, [esp+28] /* from = window */ + mov ecx, [esp+52] /* nbytes = write */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_contiguous_in_window: + add esi, eax + sub esi, ecx /* from += write - nbytes */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_do_copy: + mov ecx, eax + rep movsb + + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +L_test_for_end_of_block: + test al, 32 + jz L_invalid_literal_length_code + mov dword ptr [esp+72], 1 + jmp L_break_loop_with_status + +L_invalid_literal_length_code: + mov dword ptr [esp+72], 2 + jmp L_break_loop_with_status + +L_invalid_distance_code: + mov dword ptr [esp+72], 3 + jmp L_break_loop_with_status + +L_invalid_distance_too_far: + mov esi, [esp+4] + mov dword ptr [esp+72], 4 + jmp L_break_loop_with_status + +L_break_loop: + mov dword ptr [esp+72], 0 + +L_break_loop_with_status: +/* put in, out, bits, and hold back into ar and pop esp */ + mov [esp+8], esi /* save in */ + mov [esp+16], edi /* save out */ + mov [esp+44], ebx /* save bits */ + mov [esp+40], edx /* save hold */ + mov ebp, [esp+4] /* restore esp, ebp */ + mov esp, [esp] + } +#else +#error "x86 architecture not defined" +#endif + + if (ar.status > 1) { + if (ar.status == 2) + strm->msg = "invalid literal/length code"; + else if (ar.status == 3) + strm->msg = "invalid distance code"; + else + strm->msg = "invalid distance too far back"; + state->mode = BAD; + } + else if ( ar.status == 1 ) { + state->mode = TYPE; + } + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + ar.len = ar.bits >> 3; + ar.in -= ar.len; + ar.bits -= ar.len << 3; + ar.hold &= (1U << ar.bits) - 1; + + /* update state and return */ + strm->next_in = ar.in; + strm->next_out = ar.out; + strm->avail_in = (unsigned)(ar.in < ar.last ? + PAD_AVAIL_IN + (ar.last - ar.in) : + PAD_AVAIL_IN - (ar.in - ar.last)); + strm->avail_out = (unsigned)(ar.out < ar.end ? + PAD_AVAIL_OUT + (ar.end - ar.out) : + PAD_AVAIL_OUT - (ar.out - ar.end)); + state->hold = ar.hold; + state->bits = ar.bits; + return; +} + diff --git a/zlib/zlib/contrib/inflate86/inffast.S b/zlib/zlib/contrib/inflate86/inffast.S new file mode 100644 index 00000000..2245a290 --- /dev/null +++ b/zlib/zlib/contrib/inflate86/inffast.S @@ -0,0 +1,1368 @@ +/* + * inffast.S is a hand tuned assembler version of: + * + * inffast.c -- fast decoding + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * This version (Jan-23-2003) of inflate_fast was coded and tested under + * GNU/Linux on a pentium 3, using the gcc-3.2 compiler distribution. On that + * machine, I found that gzip style archives decompressed about 20% faster than + * the gcc-3.2 -O3 -fomit-frame-pointer compiled version. Your results will + * depend on how large of a buffer is used for z_stream.next_in & next_out + * (8K-32K worked best for my 256K cpu cache) and how much overhead there is in + * stream processing I/O and crc32/addler32. In my case, this routine used + * 70% of the cpu time and crc32 used 20%. + * + * I am confident that this version will work in the general case, but I have + * not tested a wide variety of datasets or a wide variety of platforms. + * + * Jan-24-2003 -- Added -DUSE_MMX define for slightly faster inflating. + * It should be a runtime flag instead of compile time flag... + * + * Jan-26-2003 -- Added runtime check for MMX support with cpuid instruction. + * With -DUSE_MMX, only MMX code is compiled. With -DNO_MMX, only non-MMX code + * is compiled. Without either option, runtime detection is enabled. Runtime + * detection should work on all modern cpus and the recomended algorithm (flip + * ID bit on eflags and then use the cpuid instruction) is used in many + * multimedia applications. Tested under win2k with gcc-2.95 and gas-2.12 + * distributed with cygwin3. Compiling with gcc-2.95 -c inffast.S -o + * inffast.obj generates a COFF object which can then be linked with MSVC++ + * compiled code. Tested under FreeBSD 4.7 with gcc-2.95. + * + * Jan-28-2003 -- Tested Athlon XP... MMX mode is slower than no MMX (and + * slower than compiler generated code). Adjusted cpuid check to use the MMX + * code only for Pentiums < P4 until I have more data on the P4. Speed + * improvment is only about 15% on the Athlon when compared with code generated + * with MSVC++. Not sure yet, but I think the P4 will also be slower using the + * MMX mode because many of it's x86 ALU instructions execute in .5 cycles and + * have less latency than MMX ops. Added code to buffer the last 11 bytes of + * the input stream since the MMX code grabs bits in chunks of 32, which + * differs from the inffast.c algorithm. I don't think there would have been + * read overruns where a page boundary was crossed (a segfault), but there + * could have been overruns when next_in ends on unaligned memory (unintialized + * memory read). + * + * Mar-13-2003 -- P4 MMX is slightly slower than P4 NO_MMX. I created a C + * version of the non-MMX code so that it doesn't depend on zstrm and zstate + * structure offsets which are hard coded in this file. This was last tested + * with zlib-1.2.0 which is currently in beta testing, newer versions of this + * and inffas86.c can be found at http://www.eetbeetee.com/zlib/ and + * http://www.charm.net/~christop/zlib/ + */ + + +/* + * if you have underscore linking problems (_inflate_fast undefined), try + * using -DGAS_COFF + */ +#if ! defined( GAS_COFF ) && ! defined( GAS_ELF ) + +#if defined( WIN32 ) || defined( __CYGWIN__ ) +#define GAS_COFF /* windows object format */ +#else +#define GAS_ELF +#endif + +#endif /* ! GAS_COFF && ! GAS_ELF */ + + +#if defined( GAS_COFF ) + +/* coff externals have underscores */ +#define inflate_fast _inflate_fast +#define inflate_fast_use_mmx _inflate_fast_use_mmx + +#endif /* GAS_COFF */ + + +.file "inffast.S" + +.globl inflate_fast + +.text +.align 4,0 +.L_invalid_literal_length_code_msg: +.string "invalid literal/length code" + +.align 4,0 +.L_invalid_distance_code_msg: +.string "invalid distance code" + +.align 4,0 +.L_invalid_distance_too_far_msg: +.string "invalid distance too far back" + +#if ! defined( NO_MMX ) +.align 4,0 +.L_mask: /* mask[N] = ( 1 << N ) - 1 */ +.long 0 +.long 1 +.long 3 +.long 7 +.long 15 +.long 31 +.long 63 +.long 127 +.long 255 +.long 511 +.long 1023 +.long 2047 +.long 4095 +.long 8191 +.long 16383 +.long 32767 +.long 65535 +.long 131071 +.long 262143 +.long 524287 +.long 1048575 +.long 2097151 +.long 4194303 +.long 8388607 +.long 16777215 +.long 33554431 +.long 67108863 +.long 134217727 +.long 268435455 +.long 536870911 +.long 1073741823 +.long 2147483647 +.long 4294967295 +#endif /* NO_MMX */ + +.text + +/* + * struct z_stream offsets, in zlib.h + */ +#define next_in_strm 0 /* strm->next_in */ +#define avail_in_strm 4 /* strm->avail_in */ +#define next_out_strm 12 /* strm->next_out */ +#define avail_out_strm 16 /* strm->avail_out */ +#define msg_strm 24 /* strm->msg */ +#define state_strm 28 /* strm->state */ + +/* + * struct inflate_state offsets, in inflate.h + */ +#define mode_state 0 /* state->mode */ +#define wsize_state 32 /* state->wsize */ +#define write_state 40 /* state->write */ +#define window_state 44 /* state->window */ +#define hold_state 48 /* state->hold */ +#define bits_state 52 /* state->bits */ +#define lencode_state 68 /* state->lencode */ +#define distcode_state 72 /* state->distcode */ +#define lenbits_state 76 /* state->lenbits */ +#define distbits_state 80 /* state->distbits */ + +/* + * inflate_fast's activation record + */ +#define local_var_size 64 /* how much local space for vars */ +#define strm_sp 88 /* first arg: z_stream * (local_var_size + 24) */ +#define start_sp 92 /* second arg: unsigned int (local_var_size + 28) */ + +/* + * offsets for local vars on stack + */ +#define out 60 /* unsigned char* */ +#define window 56 /* unsigned char* */ +#define wsize 52 /* unsigned int */ +#define write 48 /* unsigned int */ +#define in 44 /* unsigned char* */ +#define beg 40 /* unsigned char* */ +#define buf 28 /* char[ 12 ] */ +#define len 24 /* unsigned int */ +#define last 20 /* unsigned char* */ +#define end 16 /* unsigned char* */ +#define dcode 12 /* code* */ +#define lcode 8 /* code* */ +#define dmask 4 /* unsigned int */ +#define lmask 0 /* unsigned int */ + +/* + * typedef enum inflate_mode consts, in inflate.h + */ +#define INFLATE_MODE_TYPE 11 /* state->mode flags enum-ed in inflate.h */ +#define INFLATE_MODE_BAD 26 + + +#if ! defined( USE_MMX ) && ! defined( NO_MMX ) + +#define RUN_TIME_MMX + +#define CHECK_MMX 1 +#define DO_USE_MMX 2 +#define DONT_USE_MMX 3 + +.globl inflate_fast_use_mmx + +.data + +.align 4,0 +inflate_fast_use_mmx: /* integer flag for run time control 1=check,2=mmx,3=no */ +.long CHECK_MMX + +#if defined( GAS_ELF ) +/* elf info */ +.type inflate_fast_use_mmx,@object +.size inflate_fast_use_mmx,4 +#endif + +#endif /* RUN_TIME_MMX */ + +#if defined( GAS_COFF ) +/* coff info: scl 2 = extern, type 32 = function */ +.def inflate_fast; .scl 2; .type 32; .endef +#endif + +.text + +.align 32,0x90 +inflate_fast: + pushl %edi + pushl %esi + pushl %ebp + pushl %ebx + pushf /* save eflags (strm_sp, state_sp assumes this is 32 bits) */ + subl $local_var_size, %esp + cld + +#define strm_r %esi +#define state_r %edi + + movl strm_sp(%esp), strm_r + movl state_strm(strm_r), state_r + + /* in = strm->next_in; + * out = strm->next_out; + * last = in + strm->avail_in - 11; + * beg = out - (start - strm->avail_out); + * end = out + (strm->avail_out - 257); + */ + movl avail_in_strm(strm_r), %edx + movl next_in_strm(strm_r), %eax + + addl %eax, %edx /* avail_in += next_in */ + subl $11, %edx /* avail_in -= 11 */ + + movl %eax, in(%esp) + movl %edx, last(%esp) + + movl start_sp(%esp), %ebp + movl avail_out_strm(strm_r), %ecx + movl next_out_strm(strm_r), %ebx + + subl %ecx, %ebp /* start -= avail_out */ + negl %ebp /* start = -start */ + addl %ebx, %ebp /* start += next_out */ + + subl $257, %ecx /* avail_out -= 257 */ + addl %ebx, %ecx /* avail_out += out */ + + movl %ebx, out(%esp) + movl %ebp, beg(%esp) + movl %ecx, end(%esp) + + /* wsize = state->wsize; + * write = state->write; + * window = state->window; + * hold = state->hold; + * bits = state->bits; + * lcode = state->lencode; + * dcode = state->distcode; + * lmask = ( 1 << state->lenbits ) - 1; + * dmask = ( 1 << state->distbits ) - 1; + */ + + movl lencode_state(state_r), %eax + movl distcode_state(state_r), %ecx + + movl %eax, lcode(%esp) + movl %ecx, dcode(%esp) + + movl $1, %eax + movl lenbits_state(state_r), %ecx + shll %cl, %eax + decl %eax + movl %eax, lmask(%esp) + + movl $1, %eax + movl distbits_state(state_r), %ecx + shll %cl, %eax + decl %eax + movl %eax, dmask(%esp) + + movl wsize_state(state_r), %eax + movl write_state(state_r), %ecx + movl window_state(state_r), %edx + + movl %eax, wsize(%esp) + movl %ecx, write(%esp) + movl %edx, window(%esp) + + movl hold_state(state_r), %ebp + movl bits_state(state_r), %ebx + +#undef strm_r +#undef state_r + +#define in_r %esi +#define from_r %esi +#define out_r %edi + + movl in(%esp), in_r + movl last(%esp), %ecx + cmpl in_r, %ecx + ja .L_align_long /* if in < last */ + + addl $11, %ecx /* ecx = &in[ avail_in ] */ + subl in_r, %ecx /* ecx = avail_in */ + movl $12, %eax + subl %ecx, %eax /* eax = 12 - avail_in */ + leal buf(%esp), %edi + rep movsb /* memcpy( buf, in, avail_in ) */ + movl %eax, %ecx + xorl %eax, %eax + rep stosb /* memset( &buf[ avail_in ], 0, 12 - avail_in ) */ + leal buf(%esp), in_r /* in = buf */ + movl in_r, last(%esp) /* last = in, do just one iteration */ + jmp .L_is_aligned + + /* align in_r on long boundary */ +.L_align_long: + testl $3, in_r + jz .L_is_aligned + xorl %eax, %eax + movb (in_r), %al + incl in_r + movl %ebx, %ecx + addl $8, %ebx + shll %cl, %eax + orl %eax, %ebp + jmp .L_align_long + +.L_is_aligned: + movl out(%esp), out_r + +#if defined( NO_MMX ) + jmp .L_do_loop +#endif + +#if defined( USE_MMX ) + jmp .L_init_mmx +#endif + +/*** Runtime MMX check ***/ + +#if defined( RUN_TIME_MMX ) +.L_check_mmx: + cmpl $DO_USE_MMX, inflate_fast_use_mmx + je .L_init_mmx + ja .L_do_loop /* > 2 */ + + pushl %eax + pushl %ebx + pushl %ecx + pushl %edx + pushf + movl (%esp), %eax /* copy eflags to eax */ + xorl $0x200000, (%esp) /* try toggling ID bit of eflags (bit 21) + * to see if cpu supports cpuid... + * ID bit method not supported by NexGen but + * bios may load a cpuid instruction and + * cpuid may be disabled on Cyrix 5-6x86 */ + popf + pushf + popl %edx /* copy new eflags to edx */ + xorl %eax, %edx /* test if ID bit is flipped */ + jz .L_dont_use_mmx /* not flipped if zero */ + xorl %eax, %eax + cpuid + cmpl $0x756e6547, %ebx /* check for GenuineIntel in ebx,ecx,edx */ + jne .L_dont_use_mmx + cmpl $0x6c65746e, %ecx + jne .L_dont_use_mmx + cmpl $0x49656e69, %edx + jne .L_dont_use_mmx + movl $1, %eax + cpuid /* get cpu features */ + shrl $8, %eax + andl $15, %eax + cmpl $6, %eax /* check for Pentium family, is 0xf for P4 */ + jne .L_dont_use_mmx + testl $0x800000, %edx /* test if MMX feature is set (bit 23) */ + jnz .L_use_mmx + jmp .L_dont_use_mmx +.L_use_mmx: + movl $DO_USE_MMX, inflate_fast_use_mmx + jmp .L_check_mmx_pop +.L_dont_use_mmx: + movl $DONT_USE_MMX, inflate_fast_use_mmx +.L_check_mmx_pop: + popl %edx + popl %ecx + popl %ebx + popl %eax + jmp .L_check_mmx +#endif + + +/*** Non-MMX code ***/ + +#if defined ( NO_MMX ) || defined( RUN_TIME_MMX ) + +#define hold_r %ebp +#define bits_r %bl +#define bitslong_r %ebx + +.align 32,0x90 +.L_while_test: + /* while (in < last && out < end) + */ + cmpl out_r, end(%esp) + jbe .L_break_loop /* if (out >= end) */ + + cmpl in_r, last(%esp) + jbe .L_break_loop + +.L_do_loop: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out + * + * do { + * if (bits < 15) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * this = lcode[hold & lmask] + */ + cmpb $15, bits_r + ja .L_get_length_code /* if (15 < bits) */ + + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + +.L_get_length_code: + movl lmask(%esp), %edx /* edx = lmask */ + movl lcode(%esp), %ecx /* ecx = lcode */ + andl hold_r, %edx /* edx &= hold */ + movl (%ecx,%edx,4), %eax /* eax = lcode[hold & lmask] */ + +.L_dolen: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out + * + * dolen: + * bits -= this.bits; + * hold >>= this.bits + */ + movb %ah, %cl /* cl = this.bits */ + subb %ah, bits_r /* bits -= this.bits */ + shrl %cl, hold_r /* hold >>= this.bits */ + + /* check if op is a literal + * if (op == 0) { + * PUP(out) = this.val; + * } + */ + testb %al, %al + jnz .L_test_for_length_base /* if (op != 0) 45.7% */ + + shrl $16, %eax /* output this.val char */ + stosb + jmp .L_while_test + +.L_test_for_length_base: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out, %edx = len + * + * else if (op & 16) { + * len = this.val + * op &= 15 + * if (op) { + * if (op > bits) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * len += hold & mask[op]; + * bits -= op; + * hold >>= op; + * } + */ +#define len_r %edx + movl %eax, len_r /* len = this */ + shrl $16, len_r /* len = this.val */ + movb %al, %cl + + testb $16, %al + jz .L_test_for_second_level_length /* if ((op & 16) == 0) 8% */ + andb $15, %cl /* op &= 15 */ + jz .L_save_len /* if (!op) */ + cmpb %cl, bits_r + jae .L_add_bits_to_len /* if (op <= bits) */ + + movb %cl, %ch /* stash op in ch, freeing cl */ + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + movb %ch, %cl /* move op back to ecx */ + +.L_add_bits_to_len: + movl $1, %eax + shll %cl, %eax + decl %eax + subb %cl, bits_r + andl hold_r, %eax /* eax &= hold */ + shrl %cl, hold_r + addl %eax, len_r /* len += hold & mask[op] */ + +.L_save_len: + movl len_r, len(%esp) /* save len */ +#undef len_r + +.L_decode_distance: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * + * if (bits < 15) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * this = dcode[hold & dmask]; + * dodist: + * bits -= this.bits; + * hold >>= this.bits; + * op = this.op; + */ + + cmpb $15, bits_r + ja .L_get_distance_code /* if (15 < bits) */ + + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + +.L_get_distance_code: + movl dmask(%esp), %edx /* edx = dmask */ + movl dcode(%esp), %ecx /* ecx = dcode */ + andl hold_r, %edx /* edx &= hold */ + movl (%ecx,%edx,4), %eax /* eax = dcode[hold & dmask] */ + +#define dist_r %edx +.L_dodist: + movl %eax, dist_r /* dist = this */ + shrl $16, dist_r /* dist = this.val */ + movb %ah, %cl + subb %ah, bits_r /* bits -= this.bits */ + shrl %cl, hold_r /* hold >>= this.bits */ + + /* if (op & 16) { + * dist = this.val + * op &= 15 + * if (op > bits) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * dist += hold & mask[op]; + * bits -= op; + * hold >>= op; + */ + movb %al, %cl /* cl = this.op */ + + testb $16, %al /* if ((op & 16) == 0) */ + jz .L_test_for_second_level_dist + andb $15, %cl /* op &= 15 */ + jz .L_check_dist_one + cmpb %cl, bits_r + jae .L_add_bits_to_dist /* if (op <= bits) 97.6% */ + + movb %cl, %ch /* stash op in ch, freeing cl */ + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + movb %ch, %cl /* move op back to ecx */ + +.L_add_bits_to_dist: + movl $1, %eax + shll %cl, %eax + decl %eax /* (1 << op) - 1 */ + subb %cl, bits_r + andl hold_r, %eax /* eax &= hold */ + shrl %cl, hold_r + addl %eax, dist_r /* dist += hold & ((1 << op) - 1) */ + jmp .L_check_window + +.L_check_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes + * + * nbytes = out - beg; + * if (dist <= nbytes) { + * from = out - dist; + * do { + * PUP(out) = PUP(from); + * } while (--len > 0) { + * } + */ + + movl in_r, in(%esp) /* save in so from can use it's reg */ + movl out_r, %eax + subl beg(%esp), %eax /* nbytes = out - beg */ + + cmpl dist_r, %eax + jb .L_clip_window /* if (dist > nbytes) 4.2% */ + + movl len(%esp), %ecx + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + + subl $3, %ecx + movb (from_r), %al + movb %al, (out_r) + movb 1(from_r), %al + movb 2(from_r), %dl + addl $3, from_r + movb %al, 1(out_r) + movb %dl, 2(out_r) + addl $3, out_r + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + jmp .L_while_test + +.align 16,0x90 +.L_check_dist_one: + cmpl $1, dist_r + jne .L_check_window + cmpl out_r, beg(%esp) + je .L_check_window + + decl out_r + movl len(%esp), %ecx + movb (out_r), %al + subl $3, %ecx + + movb %al, 1(out_r) + movb %al, 2(out_r) + movb %al, 3(out_r) + addl $4, out_r + rep stosb + + jmp .L_while_test + +.align 16,0x90 +.L_test_for_second_level_length: + /* else if ((op & 64) == 0) { + * this = lcode[this.val + (hold & mask[op])]; + * } + */ + testb $64, %al + jnz .L_test_for_end_of_block /* if ((op & 64) != 0) */ + + movl $1, %eax + shll %cl, %eax + decl %eax + andl hold_r, %eax /* eax &= hold */ + addl %edx, %eax /* eax += this.val */ + movl lcode(%esp), %edx /* edx = lcode */ + movl (%edx,%eax,4), %eax /* eax = lcode[val + (hold&mask[op])] */ + jmp .L_dolen + +.align 16,0x90 +.L_test_for_second_level_dist: + /* else if ((op & 64) == 0) { + * this = dcode[this.val + (hold & mask[op])]; + * } + */ + testb $64, %al + jnz .L_invalid_distance_code /* if ((op & 64) != 0) */ + + movl $1, %eax + shll %cl, %eax + decl %eax + andl hold_r, %eax /* eax &= hold */ + addl %edx, %eax /* eax += this.val */ + movl dcode(%esp), %edx /* edx = dcode */ + movl (%edx,%eax,4), %eax /* eax = dcode[val + (hold&mask[op])] */ + jmp .L_dodist + +.align 16,0x90 +.L_clip_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes + * + * else { + * if (dist > wsize) { + * invalid distance + * } + * from = window; + * nbytes = dist - nbytes; + * if (write == 0) { + * from += wsize - nbytes; + */ +#define nbytes_r %ecx + movl %eax, nbytes_r + movl wsize(%esp), %eax /* prepare for dist compare */ + negl nbytes_r /* nbytes = -nbytes */ + movl window(%esp), from_r /* from = window */ + + cmpl dist_r, %eax + jb .L_invalid_distance_too_far /* if (dist > wsize) */ + + addl dist_r, nbytes_r /* nbytes = dist - nbytes */ + cmpl $0, write(%esp) + jne .L_wrap_around_window /* if (write != 0) */ + + subl nbytes_r, %eax + addl %eax, from_r /* from += wsize - nbytes */ + + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = len + * + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = out - dist; + * } + * } + */ +#define len_r %eax + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + +.L_wrap_around_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = write, %eax = len + * + * else if (write < nbytes) { + * from += wsize + write - nbytes; + * nbytes -= write; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = window; + * nbytes = write; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while(--nbytes); + * from = out - dist; + * } + * } + * } + */ +#define write_r %eax + movl write(%esp), write_r + cmpl write_r, nbytes_r + jbe .L_contiguous_in_window /* if (write >= nbytes) */ + + addl wsize(%esp), from_r + addl write_r, from_r + subl nbytes_r, from_r /* from += wsize + write - nbytes */ + subl write_r, nbytes_r /* nbytes -= write */ +#undef write_r + + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl window(%esp), from_r /* from = window */ + movl write(%esp), nbytes_r /* nbytes = write */ + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + +.L_contiguous_in_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = write, %eax = len + * + * else { + * from += write - nbytes; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = out - dist; + * } + * } + */ +#define write_r %eax + addl write_r, from_r + subl nbytes_r, from_r /* from += write - nbytes */ +#undef write_r + + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + +.L_do_copy1: + /* regs: %esi = from, %esi = in, %ebp = hold, %bl = bits, %edi = out + * %eax = len + * + * while (len > 0) { + * PUP(out) = PUP(from); + * len--; + * } + * } + * } while (in < last && out < end); + */ +#undef nbytes_r +#define in_r %esi + movl len_r, %ecx + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + jmp .L_while_test + +#undef len_r +#undef dist_r + +#endif /* NO_MMX || RUN_TIME_MMX */ + + +/*** MMX code ***/ + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +.align 32,0x90 +.L_init_mmx: + emms + +#undef bits_r +#undef bitslong_r +#define bitslong_r %ebp +#define hold_mm %mm0 + movd %ebp, hold_mm + movl %ebx, bitslong_r + +#define used_mm %mm1 +#define dmask2_mm %mm2 +#define lmask2_mm %mm3 +#define lmask_mm %mm4 +#define dmask_mm %mm5 +#define tmp_mm %mm6 + + movd lmask(%esp), lmask_mm + movq lmask_mm, lmask2_mm + movd dmask(%esp), dmask_mm + movq dmask_mm, dmask2_mm + pxor used_mm, used_mm + movl lcode(%esp), %ebx /* ebx = lcode */ + jmp .L_do_loop_mmx + +.align 32,0x90 +.L_while_test_mmx: + /* while (in < last && out < end) + */ + cmpl out_r, end(%esp) + jbe .L_break_loop /* if (out >= end) */ + + cmpl in_r, last(%esp) + jbe .L_break_loop + +.L_do_loop_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + + cmpl $32, bitslong_r + ja .L_get_length_code_mmx /* if (32 < bits) */ + + movd bitslong_r, tmp_mm + movd (in_r), %mm7 + addl $4, in_r + psllq tmp_mm, %mm7 + addl $32, bitslong_r + por %mm7, hold_mm /* hold_mm |= *((uint *)in)++ << bits */ + +.L_get_length_code_mmx: + pand hold_mm, lmask_mm + movd lmask_mm, %eax + movq lmask2_mm, lmask_mm + movl (%ebx,%eax,4), %eax /* eax = lcode[hold & lmask] */ + +.L_dolen_mmx: + movzbl %ah, %ecx /* ecx = this.bits */ + movd %ecx, used_mm + subl %ecx, bitslong_r /* bits -= this.bits */ + + testb %al, %al + jnz .L_test_for_length_base_mmx /* if (op != 0) 45.7% */ + + shrl $16, %eax /* output this.val char */ + stosb + jmp .L_while_test_mmx + +.L_test_for_length_base_mmx: +#define len_r %edx + movl %eax, len_r /* len = this */ + shrl $16, len_r /* len = this.val */ + + testb $16, %al + jz .L_test_for_second_level_length_mmx /* if ((op & 16) == 0) 8% */ + andl $15, %eax /* op &= 15 */ + jz .L_decode_distance_mmx /* if (!op) */ + + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd %eax, used_mm + movd hold_mm, %ecx + subl %eax, bitslong_r + andl .L_mask(,%eax,4), %ecx + addl %ecx, len_r /* len += hold & mask[op] */ + +.L_decode_distance_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + + cmpl $32, bitslong_r + ja .L_get_dist_code_mmx /* if (32 < bits) */ + + movd bitslong_r, tmp_mm + movd (in_r), %mm7 + addl $4, in_r + psllq tmp_mm, %mm7 + addl $32, bitslong_r + por %mm7, hold_mm /* hold_mm |= *((uint *)in)++ << bits */ + +.L_get_dist_code_mmx: + movl dcode(%esp), %ebx /* ebx = dcode */ + pand hold_mm, dmask_mm + movd dmask_mm, %eax + movq dmask2_mm, dmask_mm + movl (%ebx,%eax,4), %eax /* eax = dcode[hold & lmask] */ + +.L_dodist_mmx: +#define dist_r %ebx + movzbl %ah, %ecx /* ecx = this.bits */ + movl %eax, dist_r + shrl $16, dist_r /* dist = this.val */ + subl %ecx, bitslong_r /* bits -= this.bits */ + movd %ecx, used_mm + + testb $16, %al /* if ((op & 16) == 0) */ + jz .L_test_for_second_level_dist_mmx + andl $15, %eax /* op &= 15 */ + jz .L_check_dist_one_mmx + +.L_add_bits_to_dist_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd %eax, used_mm /* save bit length of current op */ + movd hold_mm, %ecx /* get the next bits on input stream */ + subl %eax, bitslong_r /* bits -= op bits */ + andl .L_mask(,%eax,4), %ecx /* ecx = hold & mask[op] */ + addl %ecx, dist_r /* dist += hold & mask[op] */ + +.L_check_window_mmx: + movl in_r, in(%esp) /* save in so from can use it's reg */ + movl out_r, %eax + subl beg(%esp), %eax /* nbytes = out - beg */ + + cmpl dist_r, %eax + jb .L_clip_window_mmx /* if (dist > nbytes) 4.2% */ + + movl len_r, %ecx + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + + subl $3, %ecx + movb (from_r), %al + movb %al, (out_r) + movb 1(from_r), %al + movb 2(from_r), %dl + addl $3, from_r + movb %al, 1(out_r) + movb %dl, 2(out_r) + addl $3, out_r + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +.align 16,0x90 +.L_check_dist_one_mmx: + cmpl $1, dist_r + jne .L_check_window_mmx + cmpl out_r, beg(%esp) + je .L_check_window_mmx + + decl out_r + movl len_r, %ecx + movb (out_r), %al + subl $3, %ecx + + movb %al, 1(out_r) + movb %al, 2(out_r) + movb %al, 3(out_r) + addl $4, out_r + rep stosb + + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +.align 16,0x90 +.L_test_for_second_level_length_mmx: + testb $64, %al + jnz .L_test_for_end_of_block /* if ((op & 64) != 0) */ + + andl $15, %eax + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ecx + andl .L_mask(,%eax,4), %ecx + addl len_r, %ecx + movl (%ebx,%ecx,4), %eax /* eax = lcode[hold & lmask] */ + jmp .L_dolen_mmx + +.align 16,0x90 +.L_test_for_second_level_dist_mmx: + testb $64, %al + jnz .L_invalid_distance_code /* if ((op & 64) != 0) */ + + andl $15, %eax + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ecx + andl .L_mask(,%eax,4), %ecx + movl dcode(%esp), %eax /* ecx = dcode */ + addl dist_r, %ecx + movl (%eax,%ecx,4), %eax /* eax = lcode[hold & lmask] */ + jmp .L_dodist_mmx + +.align 16,0x90 +.L_clip_window_mmx: +#define nbytes_r %ecx + movl %eax, nbytes_r + movl wsize(%esp), %eax /* prepare for dist compare */ + negl nbytes_r /* nbytes = -nbytes */ + movl window(%esp), from_r /* from = window */ + + cmpl dist_r, %eax + jb .L_invalid_distance_too_far /* if (dist > wsize) */ + + addl dist_r, nbytes_r /* nbytes = dist - nbytes */ + cmpl $0, write(%esp) + jne .L_wrap_around_window_mmx /* if (write != 0) */ + + subl nbytes_r, %eax + addl %eax, from_r /* from += wsize - nbytes */ + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + +.L_wrap_around_window_mmx: +#define write_r %eax + movl write(%esp), write_r + cmpl write_r, nbytes_r + jbe .L_contiguous_in_window_mmx /* if (write >= nbytes) */ + + addl wsize(%esp), from_r + addl write_r, from_r + subl nbytes_r, from_r /* from += wsize + write - nbytes */ + subl write_r, nbytes_r /* nbytes -= write */ +#undef write_r + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl window(%esp), from_r /* from = window */ + movl write(%esp), nbytes_r /* nbytes = write */ + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + +.L_contiguous_in_window_mmx: +#define write_r %eax + addl write_r, from_r + subl nbytes_r, from_r /* from += write - nbytes */ +#undef write_r + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + +.L_do_copy1_mmx: +#undef nbytes_r +#define in_r %esi + movl len_r, %ecx + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +#undef hold_r +#undef bitslong_r + +#endif /* USE_MMX || RUN_TIME_MMX */ + + +/*** USE_MMX, NO_MMX, and RUNTIME_MMX from here on ***/ + +.L_invalid_distance_code: + /* else { + * strm->msg = "invalid distance code"; + * state->mode = BAD; + * } + */ + movl $.L_invalid_distance_code_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_test_for_end_of_block: + /* else if (op & 32) { + * state->mode = TYPE; + * break; + * } + */ + testb $32, %al + jz .L_invalid_literal_length_code /* if ((op & 32) == 0) */ + + movl $0, %ecx + movl $INFLATE_MODE_TYPE, %edx + jmp .L_update_stream_state + +.L_invalid_literal_length_code: + /* else { + * strm->msg = "invalid literal/length code"; + * state->mode = BAD; + * } + */ + movl $.L_invalid_literal_length_code_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_invalid_distance_too_far: + /* strm->msg = "invalid distance too far back"; + * state->mode = BAD; + */ + movl in(%esp), in_r /* from_r has in's reg, put in back */ + movl $.L_invalid_distance_too_far_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_update_stream_state: + /* set strm->msg = %ecx, strm->state->mode = %edx */ + movl strm_sp(%esp), %eax + testl %ecx, %ecx /* if (msg != NULL) */ + jz .L_skip_msg + movl %ecx, msg_strm(%eax) /* strm->msg = msg */ +.L_skip_msg: + movl state_strm(%eax), %eax /* state = strm->state */ + movl %edx, mode_state(%eax) /* state->mode = edx (BAD | TYPE) */ + jmp .L_break_loop + +.align 32,0x90 +.L_break_loop: + +/* + * Regs: + * + * bits = %ebp when mmx, and in %ebx when non-mmx + * hold = %hold_mm when mmx, and in %ebp when non-mmx + * in = %esi + * out = %edi + */ + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +#if defined( RUN_TIME_MMX ) + + cmpl $DO_USE_MMX, inflate_fast_use_mmx + jne .L_update_next_in + +#endif /* RUN_TIME_MMX */ + + movl %ebp, %ebx + +.L_update_next_in: + +#endif + +#define strm_r %eax +#define state_r %edx + + /* len = bits >> 3; + * in -= len; + * bits -= len << 3; + * hold &= (1U << bits) - 1; + * state->hold = hold; + * state->bits = bits; + * strm->next_in = in; + * strm->next_out = out; + */ + movl strm_sp(%esp), strm_r + movl %ebx, %ecx + movl state_strm(strm_r), state_r + shrl $3, %ecx + subl %ecx, in_r + shll $3, %ecx + subl %ecx, %ebx + movl out_r, next_out_strm(strm_r) + movl %ebx, bits_state(state_r) + movl %ebx, %ecx + + leal buf(%esp), %ebx + cmpl %ebx, last(%esp) + jne .L_buf_not_used /* if buf != last */ + + subl %ebx, in_r /* in -= buf */ + movl next_in_strm(strm_r), %ebx + movl %ebx, last(%esp) /* last = strm->next_in */ + addl %ebx, in_r /* in += strm->next_in */ + movl avail_in_strm(strm_r), %ebx + subl $11, %ebx + addl %ebx, last(%esp) /* last = &strm->next_in[ avail_in - 11 ] */ + +.L_buf_not_used: + movl in_r, next_in_strm(strm_r) + + movl $1, %ebx + shll %cl, %ebx + decl %ebx + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +#if defined( RUN_TIME_MMX ) + + cmpl $DO_USE_MMX, inflate_fast_use_mmx + jne .L_update_hold + +#endif /* RUN_TIME_MMX */ + + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ebp + + emms + +.L_update_hold: + +#endif /* USE_MMX || RUN_TIME_MMX */ + + andl %ebx, %ebp + movl %ebp, hold_state(state_r) + +#define last_r %ebx + + /* strm->avail_in = in < last ? 11 + (last - in) : 11 - (in - last) */ + movl last(%esp), last_r + cmpl in_r, last_r + jbe .L_last_is_smaller /* if (in >= last) */ + + subl in_r, last_r /* last -= in */ + addl $11, last_r /* last += 11 */ + movl last_r, avail_in_strm(strm_r) + jmp .L_fixup_out +.L_last_is_smaller: + subl last_r, in_r /* in -= last */ + negl in_r /* in = -in */ + addl $11, in_r /* in += 11 */ + movl in_r, avail_in_strm(strm_r) + +#undef last_r +#define end_r %ebx + +.L_fixup_out: + /* strm->avail_out = out < end ? 257 + (end - out) : 257 - (out - end)*/ + movl end(%esp), end_r + cmpl out_r, end_r + jbe .L_end_is_smaller /* if (out >= end) */ + + subl out_r, end_r /* end -= out */ + addl $257, end_r /* end += 257 */ + movl end_r, avail_out_strm(strm_r) + jmp .L_done +.L_end_is_smaller: + subl end_r, out_r /* out -= end */ + negl out_r /* out = -out */ + addl $257, out_r /* out += 257 */ + movl out_r, avail_out_strm(strm_r) + +#undef end_r +#undef strm_r +#undef state_r + +.L_done: + addl $local_var_size, %esp + popf + popl %ebx + popl %ebp + popl %esi + popl %edi + ret + +#if defined( GAS_ELF ) +/* elf info */ +.type inflate_fast,@function +.size inflate_fast,.-inflate_fast +#endif diff --git a/zlib/zlib/contrib/iostream/test.cpp b/zlib/zlib/contrib/iostream/test.cpp new file mode 100644 index 00000000..7d265b3b --- /dev/null +++ b/zlib/zlib/contrib/iostream/test.cpp @@ -0,0 +1,24 @@ + +#include "zfstream.h" + +int main() { + + // Construct a stream object with this filebuffer. Anything sent + // to this stream will go to standard out. + gzofstream os( 1, ios::out ); + + // This text is getting compressed and sent to stdout. + // To prove this, run 'test | zcat'. + os << "Hello, Mommy" << endl; + + os << setcompressionlevel( Z_NO_COMPRESSION ); + os << "hello, hello, hi, ho!" << endl; + + setcompressionlevel( os, Z_DEFAULT_COMPRESSION ) + << "I'm compressing again" << endl; + + os.close(); + + return 0; + +} diff --git a/zlib/zlib/contrib/iostream/zfstream.cpp b/zlib/zlib/contrib/iostream/zfstream.cpp new file mode 100644 index 00000000..d0cd85fa --- /dev/null +++ b/zlib/zlib/contrib/iostream/zfstream.cpp @@ -0,0 +1,329 @@ + +#include "zfstream.h" + +gzfilebuf::gzfilebuf() : + file(NULL), + mode(0), + own_file_descriptor(0) +{ } + +gzfilebuf::~gzfilebuf() { + + sync(); + if ( own_file_descriptor ) + close(); + +} + +gzfilebuf *gzfilebuf::open( const char *name, + int io_mode ) { + + if ( is_open() ) + return NULL; + + char char_mode[10]; + char *p = char_mode; + + if ( io_mode & ios::in ) { + mode = ios::in; + *p++ = 'r'; + } else if ( io_mode & ios::app ) { + mode = ios::app; + *p++ = 'a'; + } else { + mode = ios::out; + *p++ = 'w'; + } + + if ( io_mode & ios::binary ) { + mode |= ios::binary; + *p++ = 'b'; + } + + // Hard code the compression level + if ( io_mode & (ios::out|ios::app )) { + *p++ = '9'; + } + + // Put the end-of-string indicator + *p = '\0'; + + if ( (file = gzopen(name, char_mode)) == NULL ) + return NULL; + + own_file_descriptor = 1; + + return this; + +} + +gzfilebuf *gzfilebuf::attach( int file_descriptor, + int io_mode ) { + + if ( is_open() ) + return NULL; + + char char_mode[10]; + char *p = char_mode; + + if ( io_mode & ios::in ) { + mode = ios::in; + *p++ = 'r'; + } else if ( io_mode & ios::app ) { + mode = ios::app; + *p++ = 'a'; + } else { + mode = ios::out; + *p++ = 'w'; + } + + if ( io_mode & ios::binary ) { + mode |= ios::binary; + *p++ = 'b'; + } + + // Hard code the compression level + if ( io_mode & (ios::out|ios::app )) { + *p++ = '9'; + } + + // Put the end-of-string indicator + *p = '\0'; + + if ( (file = gzdopen(file_descriptor, char_mode)) == NULL ) + return NULL; + + own_file_descriptor = 0; + + return this; + +} + +gzfilebuf *gzfilebuf::close() { + + if ( is_open() ) { + + sync(); + gzclose( file ); + file = NULL; + + } + + return this; + +} + +int gzfilebuf::setcompressionlevel( int comp_level ) { + + return gzsetparams(file, comp_level, -2); + +} + +int gzfilebuf::setcompressionstrategy( int comp_strategy ) { + + return gzsetparams(file, -2, comp_strategy); + +} + + +streampos gzfilebuf::seekoff( streamoff off, ios::seek_dir dir, int which ) { + + return streampos(EOF); + +} + +int gzfilebuf::underflow() { + + // If the file hasn't been opened for reading, error. + if ( !is_open() || !(mode & ios::in) ) + return EOF; + + // if a buffer doesn't exists, allocate one. + if ( !base() ) { + + if ( (allocate()) == EOF ) + return EOF; + setp(0,0); + + } else { + + if ( in_avail() ) + return (unsigned char) *gptr(); + + if ( out_waiting() ) { + if ( flushbuf() == EOF ) + return EOF; + } + + } + + // Attempt to fill the buffer. + + int result = fillbuf(); + if ( result == EOF ) { + // disable get area + setg(0,0,0); + return EOF; + } + + return (unsigned char) *gptr(); + +} + +int gzfilebuf::overflow( int c ) { + + if ( !is_open() || !(mode & ios::out) ) + return EOF; + + if ( !base() ) { + if ( allocate() == EOF ) + return EOF; + setg(0,0,0); + } else { + if (in_avail()) { + return EOF; + } + if (out_waiting()) { + if (flushbuf() == EOF) + return EOF; + } + } + + int bl = blen(); + setp( base(), base() + bl); + + if ( c != EOF ) { + + *pptr() = c; + pbump(1); + + } + + return 0; + +} + +int gzfilebuf::sync() { + + if ( !is_open() ) + return EOF; + + if ( out_waiting() ) + return flushbuf(); + + return 0; + +} + +int gzfilebuf::flushbuf() { + + int n; + char *q; + + q = pbase(); + n = pptr() - q; + + if ( gzwrite( file, q, n) < n ) + return EOF; + + setp(0,0); + + return 0; + +} + +int gzfilebuf::fillbuf() { + + int required; + char *p; + + p = base(); + + required = blen(); + + int t = gzread( file, p, required ); + + if ( t <= 0) return EOF; + + setg( base(), base(), base()+t); + + return t; + +} + +gzfilestream_common::gzfilestream_common() : + ios( gzfilestream_common::rdbuf() ) +{ } + +gzfilestream_common::~gzfilestream_common() +{ } + +void gzfilestream_common::attach( int fd, int io_mode ) { + + if ( !buffer.attach( fd, io_mode) ) + clear( ios::failbit | ios::badbit ); + else + clear(); + +} + +void gzfilestream_common::open( const char *name, int io_mode ) { + + if ( !buffer.open( name, io_mode ) ) + clear( ios::failbit | ios::badbit ); + else + clear(); + +} + +void gzfilestream_common::close() { + + if ( !buffer.close() ) + clear( ios::failbit | ios::badbit ); + +} + +gzfilebuf *gzfilestream_common::rdbuf() +{ + return &buffer; +} + +gzifstream::gzifstream() : + ios( gzfilestream_common::rdbuf() ) +{ + clear( ios::badbit ); +} + +gzifstream::gzifstream( const char *name, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::open( name, io_mode ); +} + +gzifstream::gzifstream( int fd, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::attach( fd, io_mode ); +} + +gzifstream::~gzifstream() { } + +gzofstream::gzofstream() : + ios( gzfilestream_common::rdbuf() ) +{ + clear( ios::badbit ); +} + +gzofstream::gzofstream( const char *name, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::open( name, io_mode ); +} + +gzofstream::gzofstream( int fd, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::attach( fd, io_mode ); +} + +gzofstream::~gzofstream() { } diff --git a/zlib/zlib/contrib/iostream/zfstream.h b/zlib/zlib/contrib/iostream/zfstream.h new file mode 100644 index 00000000..ed79098a --- /dev/null +++ b/zlib/zlib/contrib/iostream/zfstream.h @@ -0,0 +1,128 @@ + +#ifndef zfstream_h +#define zfstream_h + +#include +#include "zlib.h" + +class gzfilebuf : public streambuf { + +public: + + gzfilebuf( ); + virtual ~gzfilebuf(); + + gzfilebuf *open( const char *name, int io_mode ); + gzfilebuf *attach( int file_descriptor, int io_mode ); + gzfilebuf *close(); + + int setcompressionlevel( int comp_level ); + int setcompressionstrategy( int comp_strategy ); + + inline int is_open() const { return (file !=NULL); } + + virtual streampos seekoff( streamoff, ios::seek_dir, int ); + + virtual int sync(); + +protected: + + virtual int underflow(); + virtual int overflow( int = EOF ); + +private: + + gzFile file; + short mode; + short own_file_descriptor; + + int flushbuf(); + int fillbuf(); + +}; + +class gzfilestream_common : virtual public ios { + + friend class gzifstream; + friend class gzofstream; + friend gzofstream &setcompressionlevel( gzofstream &, int ); + friend gzofstream &setcompressionstrategy( gzofstream &, int ); + +public: + virtual ~gzfilestream_common(); + + void attach( int fd, int io_mode ); + void open( const char *name, int io_mode ); + void close(); + +protected: + gzfilestream_common(); + +private: + gzfilebuf *rdbuf(); + + gzfilebuf buffer; + +}; + +class gzifstream : public gzfilestream_common, public istream { + +public: + + gzifstream(); + gzifstream( const char *name, int io_mode = ios::in ); + gzifstream( int fd, int io_mode = ios::in ); + + virtual ~gzifstream(); + +}; + +class gzofstream : public gzfilestream_common, public ostream { + +public: + + gzofstream(); + gzofstream( const char *name, int io_mode = ios::out ); + gzofstream( int fd, int io_mode = ios::out ); + + virtual ~gzofstream(); + +}; + +template class gzomanip { + friend gzofstream &operator<<(gzofstream &, const gzomanip &); +public: + gzomanip(gzofstream &(*f)(gzofstream &, T), T v) : func(f), val(v) { } +private: + gzofstream &(*func)(gzofstream &, T); + T val; +}; + +template gzofstream &operator<<(gzofstream &s, const gzomanip &m) +{ + return (*m.func)(s, m.val); +} + +inline gzofstream &setcompressionlevel( gzofstream &s, int l ) +{ + (s.rdbuf())->setcompressionlevel(l); + return s; +} + +inline gzofstream &setcompressionstrategy( gzofstream &s, int l ) +{ + (s.rdbuf())->setcompressionstrategy(l); + return s; +} + +inline gzomanip setcompressionlevel(int l) +{ + return gzomanip(&setcompressionlevel,l); +} + +inline gzomanip setcompressionstrategy(int l) +{ + return gzomanip(&setcompressionstrategy,l); +} + +#endif diff --git a/zlib/zlib/contrib/iostream2/zstream.h b/zlib/zlib/contrib/iostream2/zstream.h new file mode 100644 index 00000000..43d2332b --- /dev/null +++ b/zlib/zlib/contrib/iostream2/zstream.h @@ -0,0 +1,307 @@ +/* + * + * Copyright (c) 1997 + * Christian Michelsen Research AS + * Advanced Computing + * Fantoftvegen 38, 5036 BERGEN, Norway + * http://www.cmr.no + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Christian Michelsen Research AS makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + */ + +#ifndef ZSTREAM__H +#define ZSTREAM__H + +/* + * zstream.h - C++ interface to the 'zlib' general purpose compression library + * $Id: zstream.h 1.1 1997-06-25 12:00:56+02 tyge Exp tyge $ + */ + +#include +#include +#include +#include "zlib.h" + +#if defined(_WIN32) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +class zstringlen { +public: + zstringlen(class izstream&); + zstringlen(class ozstream&, const char*); + size_t value() const { return val.word; } +private: + struct Val { unsigned char byte; size_t word; } val; +}; + +// ----------------------------- izstream ----------------------------- + +class izstream +{ + public: + izstream() : m_fp(0) {} + izstream(FILE* fp) : m_fp(0) { open(fp); } + izstream(const char* name) : m_fp(0) { open(name); } + ~izstream() { close(); } + + /* Opens a gzip (.gz) file for reading. + * open() can be used to read a file which is not in gzip format; + * in this case read() will directly read from the file without + * decompression. errno can be checked to distinguish two error + * cases (if errno is zero, the zlib error is Z_MEM_ERROR). + */ + void open(const char* name) { + if (m_fp) close(); + m_fp = ::gzopen(name, "rb"); + } + + void open(FILE* fp) { + SET_BINARY_MODE(fp); + if (m_fp) close(); + m_fp = ::gzdopen(fileno(fp), "rb"); + } + + /* Flushes all pending input if necessary, closes the compressed file + * and deallocates all the (de)compression state. The return value is + * the zlib error number (see function error() below). + */ + int close() { + int r = ::gzclose(m_fp); + m_fp = 0; return r; + } + + /* Binary read the given number of bytes from the compressed file. + */ + int read(void* buf, size_t len) { + return ::gzread(m_fp, buf, len); + } + + /* Returns the error message for the last error which occurred on the + * given compressed file. errnum is set to zlib error number. If an + * error occurred in the file system and not in the compression library, + * errnum is set to Z_ERRNO and the application may consult errno + * to get the exact error code. + */ + const char* error(int* errnum) { + return ::gzerror(m_fp, errnum); + } + + gzFile fp() { return m_fp; } + + private: + gzFile m_fp; +}; + +/* + * Binary read the given (array of) object(s) from the compressed file. + * If the input file was not in gzip format, read() copies the objects number + * of bytes into the buffer. + * returns the number of uncompressed bytes actually read + * (0 for end of file, -1 for error). + */ +template +inline int read(izstream& zs, T* x, Items items) { + return ::gzread(zs.fp(), x, items*sizeof(T)); +} + +/* + * Binary input with the '>' operator. + */ +template +inline izstream& operator>(izstream& zs, T& x) { + ::gzread(zs.fp(), &x, sizeof(T)); + return zs; +} + + +inline zstringlen::zstringlen(izstream& zs) { + zs > val.byte; + if (val.byte == 255) zs > val.word; + else val.word = val.byte; +} + +/* + * Read length of string + the string with the '>' operator. + */ +inline izstream& operator>(izstream& zs, char* x) { + zstringlen len(zs); + ::gzread(zs.fp(), x, len.value()); + x[len.value()] = '\0'; + return zs; +} + +inline char* read_string(izstream& zs) { + zstringlen len(zs); + char* x = new char[len.value()+1]; + ::gzread(zs.fp(), x, len.value()); + x[len.value()] = '\0'; + return x; +} + +// ----------------------------- ozstream ----------------------------- + +class ozstream +{ + public: + ozstream() : m_fp(0), m_os(0) { + } + ozstream(FILE* fp, int level = Z_DEFAULT_COMPRESSION) + : m_fp(0), m_os(0) { + open(fp, level); + } + ozstream(const char* name, int level = Z_DEFAULT_COMPRESSION) + : m_fp(0), m_os(0) { + open(name, level); + } + ~ozstream() { + close(); + } + + /* Opens a gzip (.gz) file for writing. + * The compression level parameter should be in 0..9 + * errno can be checked to distinguish two error cases + * (if errno is zero, the zlib error is Z_MEM_ERROR). + */ + void open(const char* name, int level = Z_DEFAULT_COMPRESSION) { + char mode[4] = "wb\0"; + if (level != Z_DEFAULT_COMPRESSION) mode[2] = '0'+level; + if (m_fp) close(); + m_fp = ::gzopen(name, mode); + } + + /* open from a FILE pointer. + */ + void open(FILE* fp, int level = Z_DEFAULT_COMPRESSION) { + SET_BINARY_MODE(fp); + char mode[4] = "wb\0"; + if (level != Z_DEFAULT_COMPRESSION) mode[2] = '0'+level; + if (m_fp) close(); + m_fp = ::gzdopen(fileno(fp), mode); + } + + /* Flushes all pending output if necessary, closes the compressed file + * and deallocates all the (de)compression state. The return value is + * the zlib error number (see function error() below). + */ + int close() { + if (m_os) { + ::gzwrite(m_fp, m_os->str(), m_os->pcount()); + delete[] m_os->str(); delete m_os; m_os = 0; + } + int r = ::gzclose(m_fp); m_fp = 0; return r; + } + + /* Binary write the given number of bytes into the compressed file. + */ + int write(const void* buf, size_t len) { + return ::gzwrite(m_fp, (voidp) buf, len); + } + + /* Flushes all pending output into the compressed file. The parameter + * _flush is as in the deflate() function. The return value is the zlib + * error number (see function gzerror below). flush() returns Z_OK if + * the flush_ parameter is Z_FINISH and all output could be flushed. + * flush() should be called only when strictly necessary because it can + * degrade compression. + */ + int flush(int _flush) { + os_flush(); + return ::gzflush(m_fp, _flush); + } + + /* Returns the error message for the last error which occurred on the + * given compressed file. errnum is set to zlib error number. If an + * error occurred in the file system and not in the compression library, + * errnum is set to Z_ERRNO and the application may consult errno + * to get the exact error code. + */ + const char* error(int* errnum) { + return ::gzerror(m_fp, errnum); + } + + gzFile fp() { return m_fp; } + + ostream& os() { + if (m_os == 0) m_os = new ostrstream; + return *m_os; + } + + void os_flush() { + if (m_os && m_os->pcount()>0) { + ostrstream* oss = new ostrstream; + oss->fill(m_os->fill()); + oss->flags(m_os->flags()); + oss->precision(m_os->precision()); + oss->width(m_os->width()); + ::gzwrite(m_fp, m_os->str(), m_os->pcount()); + delete[] m_os->str(); delete m_os; m_os = oss; + } + } + + private: + gzFile m_fp; + ostrstream* m_os; +}; + +/* + * Binary write the given (array of) object(s) into the compressed file. + * returns the number of uncompressed bytes actually written + * (0 in case of error). + */ +template +inline int write(ozstream& zs, const T* x, Items items) { + return ::gzwrite(zs.fp(), (voidp) x, items*sizeof(T)); +} + +/* + * Binary output with the '<' operator. + */ +template +inline ozstream& operator<(ozstream& zs, const T& x) { + ::gzwrite(zs.fp(), (voidp) &x, sizeof(T)); + return zs; +} + +inline zstringlen::zstringlen(ozstream& zs, const char* x) { + val.byte = 255; val.word = ::strlen(x); + if (val.word < 255) zs < (val.byte = val.word); + else zs < val; +} + +/* + * Write length of string + the string with the '<' operator. + */ +inline ozstream& operator<(ozstream& zs, const char* x) { + zstringlen len(zs, x); + ::gzwrite(zs.fp(), (voidp) x, len.value()); + return zs; +} + +#ifdef _MSC_VER +inline ozstream& operator<(ozstream& zs, char* const& x) { + return zs < (const char*) x; +} +#endif + +/* + * Ascii write with the << operator; + */ +template +inline ostream& operator<<(ozstream& zs, const T& x) { + zs.os_flush(); + return zs.os() << x; +} + +#endif diff --git a/zlib/zlib/contrib/iostream2/zstream_test.cpp b/zlib/zlib/contrib/iostream2/zstream_test.cpp new file mode 100644 index 00000000..6273f62d --- /dev/null +++ b/zlib/zlib/contrib/iostream2/zstream_test.cpp @@ -0,0 +1,25 @@ +#include "zstream.h" +#include +#include +#include + +void main() { + char h[256] = "Hello"; + char* g = "Goodbye"; + ozstream out("temp.gz"); + out < "This works well" < h < g; + out.close(); + + izstream in("temp.gz"); // read it back + char *x = read_string(in), *y = new char[256], z[256]; + in > y > z; + in.close(); + cout << x << endl << y << endl << z << endl; + + out.open("temp.gz"); // try ascii output; zcat temp.gz to see the results + out << setw(50) << setfill('#') << setprecision(20) << x << endl << y << endl << z << endl; + out << z << endl << y << endl << x << endl; + out << 1.1234567890123456789 << endl; + + delete[] x; delete[] y; +} diff --git a/zlib/zlib/contrib/iostream3/README b/zlib/zlib/contrib/iostream3/README new file mode 100644 index 00000000..f7b319ab --- /dev/null +++ b/zlib/zlib/contrib/iostream3/README @@ -0,0 +1,35 @@ +These classes provide a C++ stream interface to the zlib library. It allows you +to do things like: + + gzofstream outf("blah.gz"); + outf << "These go into the gzip file " << 123 << endl; + +It does this by deriving a specialized stream buffer for gzipped files, which is +the way Stroustrup would have done it. :-> + +The gzifstream and gzofstream classes were originally written by Kevin Ruland +and made available in the zlib contrib/iostream directory. The older version still +compiles under gcc 2.xx, but not under gcc 3.xx, which sparked the development of +this version. + +The new classes are as standard-compliant as possible, closely following the +approach of the standard library's fstream classes. It compiles under gcc versions +3.2 and 3.3, but not under gcc 2.xx. This is mainly due to changes in the standard +library naming scheme. The new version of gzifstream/gzofstream/gzfilebuf differs +from the previous one in the following respects: +- added showmanyc +- added setbuf, with support for unbuffered output via setbuf(0,0) +- a few bug fixes of stream behavior +- gzipped output file opened with default compression level instead of maximum level +- setcompressionlevel()/strategy() members replaced by single setcompression() + +The code is provided "as is", with the permission to use, copy, modify, distribute +and sell it for any purpose without fee. + +Ludwig Schwardt + + +DSP Lab +Electrical & Electronic Engineering Department +University of Stellenbosch +South Africa diff --git a/zlib/zlib/contrib/iostream3/TODO b/zlib/zlib/contrib/iostream3/TODO new file mode 100644 index 00000000..7032f97b --- /dev/null +++ b/zlib/zlib/contrib/iostream3/TODO @@ -0,0 +1,17 @@ +Possible upgrades to gzfilebuf: + +- The ability to do putback (e.g. putbackfail) + +- The ability to seek (zlib supports this, but could be slow/tricky) + +- Simultaneous read/write access (does it make sense?) + +- Support for ios_base::ate open mode + +- Locale support? + +- Check public interface to see which calls give problems + (due to dependence on library internals) + +- Override operator<<(ostream&, gzfilebuf*) to allow direct copying + of stream buffer to stream ( i.e. os << is.rdbuf(); ) diff --git a/zlib/zlib/contrib/iostream3/test.cc b/zlib/zlib/contrib/iostream3/test.cc new file mode 100644 index 00000000..94235334 --- /dev/null +++ b/zlib/zlib/contrib/iostream3/test.cc @@ -0,0 +1,50 @@ +/* + * Test program for gzifstream and gzofstream + * + * by Ludwig Schwardt + * original version by Kevin Ruland + */ + +#include "zfstream.h" +#include // for cout + +int main() { + + gzofstream outf; + gzifstream inf; + char buf[80]; + + outf.open("test1.txt.gz"); + outf << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + outf.close(); + std::cout << "Wrote the following message to 'test1.txt.gz' (check with zcat or zless):\n" + << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + + std::cout << "\nReading 'test1.txt.gz' (buffered) produces:\n"; + inf.open("test1.txt.gz"); + while (inf.getline(buf,80,'\n')) { + std::cout << buf << "\t(" << inf.rdbuf()->in_avail() << " chars left in buffer)\n"; + } + inf.close(); + + outf.rdbuf()->pubsetbuf(0,0); + outf.open("test2.txt.gz"); + outf << setcompression(Z_NO_COMPRESSION) + << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + outf.close(); + std::cout << "\nWrote the same message to 'test2.txt.gz' in uncompressed form"; + + std::cout << "\nReading 'test2.txt.gz' (unbuffered) produces:\n"; + inf.rdbuf()->pubsetbuf(0,0); + inf.open("test2.txt.gz"); + while (inf.getline(buf,80,'\n')) { + std::cout << buf << "\t(" << inf.rdbuf()->in_avail() << " chars left in buffer)\n"; + } + inf.close(); + + return 0; + +} diff --git a/zlib/zlib/contrib/iostream3/zfstream.cc b/zlib/zlib/contrib/iostream3/zfstream.cc new file mode 100644 index 00000000..94eb9334 --- /dev/null +++ b/zlib/zlib/contrib/iostream3/zfstream.cc @@ -0,0 +1,479 @@ +/* + * A C++ I/O streams interface to the zlib gz* functions + * + * by Ludwig Schwardt + * original version by Kevin Ruland + * + * This version is standard-compliant and compatible with gcc 3.x. + */ + +#include "zfstream.h" +#include // for strcpy, strcat, strlen (mode strings) +#include // for BUFSIZ + +// Internal buffer sizes (default and "unbuffered" versions) +#define BIGBUFSIZE BUFSIZ +#define SMALLBUFSIZE 1 + +/*****************************************************************************/ + +// Default constructor +gzfilebuf::gzfilebuf() +: file(NULL), io_mode(std::ios_base::openmode(0)), own_fd(false), + buffer(NULL), buffer_size(BIGBUFSIZE), own_buffer(true) +{ + // No buffers to start with + this->disable_buffer(); +} + +// Destructor +gzfilebuf::~gzfilebuf() +{ + // Sync output buffer and close only if responsible for file + // (i.e. attached streams should be left open at this stage) + this->sync(); + if (own_fd) + this->close(); + // Make sure internal buffer is deallocated + this->disable_buffer(); +} + +// Set compression level and strategy +int +gzfilebuf::setcompression(int comp_level, + int comp_strategy) +{ + return gzsetparams(file, comp_level, comp_strategy); +} + +// Open gzipped file +gzfilebuf* +gzfilebuf::open(const char *name, + std::ios_base::openmode mode) +{ + // Fail if file already open + if (this->is_open()) + return NULL; + // Don't support simultaneous read/write access (yet) + if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) + return NULL; + + // Build mode string for gzopen and check it [27.8.1.3.2] + char char_mode[6] = "\0\0\0\0\0"; + if (!this->open_mode(mode, char_mode)) + return NULL; + + // Attempt to open file + if ((file = gzopen(name, char_mode)) == NULL) + return NULL; + + // On success, allocate internal buffer and set flags + this->enable_buffer(); + io_mode = mode; + own_fd = true; + return this; +} + +// Attach to gzipped file +gzfilebuf* +gzfilebuf::attach(int fd, + std::ios_base::openmode mode) +{ + // Fail if file already open + if (this->is_open()) + return NULL; + // Don't support simultaneous read/write access (yet) + if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) + return NULL; + + // Build mode string for gzdopen and check it [27.8.1.3.2] + char char_mode[6] = "\0\0\0\0\0"; + if (!this->open_mode(mode, char_mode)) + return NULL; + + // Attempt to attach to file + if ((file = gzdopen(fd, char_mode)) == NULL) + return NULL; + + // On success, allocate internal buffer and set flags + this->enable_buffer(); + io_mode = mode; + own_fd = false; + return this; +} + +// Close gzipped file +gzfilebuf* +gzfilebuf::close() +{ + // Fail immediately if no file is open + if (!this->is_open()) + return NULL; + // Assume success + gzfilebuf* retval = this; + // Attempt to sync and close gzipped file + if (this->sync() == -1) + retval = NULL; + if (gzclose(file) < 0) + retval = NULL; + // File is now gone anyway (postcondition [27.8.1.3.8]) + file = NULL; + own_fd = false; + // Destroy internal buffer if it exists + this->disable_buffer(); + return retval; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Convert int open mode to mode string +bool +gzfilebuf::open_mode(std::ios_base::openmode mode, + char* c_mode) const +{ + bool testb = mode & std::ios_base::binary; + bool testi = mode & std::ios_base::in; + bool testo = mode & std::ios_base::out; + bool testt = mode & std::ios_base::trunc; + bool testa = mode & std::ios_base::app; + + // Check for valid flag combinations - see [27.8.1.3.2] (Table 92) + // Original zfstream hardcoded the compression level to maximum here... + // Double the time for less than 1% size improvement seems + // excessive though - keeping it at the default level + // To change back, just append "9" to the next three mode strings + if (!testi && testo && !testt && !testa) + strcpy(c_mode, "w"); + if (!testi && testo && !testt && testa) + strcpy(c_mode, "a"); + if (!testi && testo && testt && !testa) + strcpy(c_mode, "w"); + if (testi && !testo && !testt && !testa) + strcpy(c_mode, "r"); + // No read/write mode yet +// if (testi && testo && !testt && !testa) +// strcpy(c_mode, "r+"); +// if (testi && testo && testt && !testa) +// strcpy(c_mode, "w+"); + + // Mode string should be empty for invalid combination of flags + if (strlen(c_mode) == 0) + return false; + if (testb) + strcat(c_mode, "b"); + return true; +} + +// Determine number of characters in internal get buffer +std::streamsize +gzfilebuf::showmanyc() +{ + // Calls to underflow will fail if file not opened for reading + if (!this->is_open() || !(io_mode & std::ios_base::in)) + return -1; + // Make sure get area is in use + if (this->gptr() && (this->gptr() < this->egptr())) + return std::streamsize(this->egptr() - this->gptr()); + else + return 0; +} + +// Fill get area from gzipped file +gzfilebuf::int_type +gzfilebuf::underflow() +{ + // If something is left in the get area by chance, return it + // (this shouldn't normally happen, as underflow is only supposed + // to be called when gptr >= egptr, but it serves as error check) + if (this->gptr() && (this->gptr() < this->egptr())) + return traits_type::to_int_type(*(this->gptr())); + + // If the file hasn't been opened for reading, produce error + if (!this->is_open() || !(io_mode & std::ios_base::in)) + return traits_type::eof(); + + // Attempt to fill internal buffer from gzipped file + // (buffer must be guaranteed to exist...) + int bytes_read = gzread(file, buffer, buffer_size); + // Indicates error or EOF + if (bytes_read <= 0) + { + // Reset get area + this->setg(buffer, buffer, buffer); + return traits_type::eof(); + } + // Make all bytes read from file available as get area + this->setg(buffer, buffer, buffer + bytes_read); + + // Return next character in get area + return traits_type::to_int_type(*(this->gptr())); +} + +// Write put area to gzipped file +gzfilebuf::int_type +gzfilebuf::overflow(int_type c) +{ + // Determine whether put area is in use + if (this->pbase()) + { + // Double-check pointer range + if (this->pptr() > this->epptr() || this->pptr() < this->pbase()) + return traits_type::eof(); + // Add extra character to buffer if not EOF + if (!traits_type::eq_int_type(c, traits_type::eof())) + { + *(this->pptr()) = traits_type::to_char_type(c); + this->pbump(1); + } + // Number of characters to write to file + int bytes_to_write = this->pptr() - this->pbase(); + // Overflow doesn't fail if nothing is to be written + if (bytes_to_write > 0) + { + // If the file hasn't been opened for writing, produce error + if (!this->is_open() || !(io_mode & std::ios_base::out)) + return traits_type::eof(); + // If gzipped file won't accept all bytes written to it, fail + if (gzwrite(file, this->pbase(), bytes_to_write) != bytes_to_write) + return traits_type::eof(); + // Reset next pointer to point to pbase on success + this->pbump(-bytes_to_write); + } + } + // Write extra character to file if not EOF + else if (!traits_type::eq_int_type(c, traits_type::eof())) + { + // If the file hasn't been opened for writing, produce error + if (!this->is_open() || !(io_mode & std::ios_base::out)) + return traits_type::eof(); + // Impromptu char buffer (allows "unbuffered" output) + char_type last_char = traits_type::to_char_type(c); + // If gzipped file won't accept this character, fail + if (gzwrite(file, &last_char, 1) != 1) + return traits_type::eof(); + } + + // If you got here, you have succeeded (even if c was EOF) + // The return value should therefore be non-EOF + if (traits_type::eq_int_type(c, traits_type::eof())) + return traits_type::not_eof(c); + else + return c; +} + +// Assign new buffer +std::streambuf* +gzfilebuf::setbuf(char_type* p, + std::streamsize n) +{ + // First make sure stuff is sync'ed, for safety + if (this->sync() == -1) + return NULL; + // If buffering is turned off on purpose via setbuf(0,0), still allocate one... + // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at + // least a buffer of size 1 (very inefficient though, therefore make it bigger?) + // This follows from [27.5.2.4.3]/12 (gptr needs to point at something, it seems) + if (!p || !n) + { + // Replace existing buffer (if any) with small internal buffer + this->disable_buffer(); + buffer = NULL; + buffer_size = 0; + own_buffer = true; + this->enable_buffer(); + } + else + { + // Replace existing buffer (if any) with external buffer + this->disable_buffer(); + buffer = p; + buffer_size = n; + own_buffer = false; + this->enable_buffer(); + } + return this; +} + +// Write put area to gzipped file (i.e. ensures that put area is empty) +int +gzfilebuf::sync() +{ + return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Allocate internal buffer +void +gzfilebuf::enable_buffer() +{ + // If internal buffer required, allocate one + if (own_buffer && !buffer) + { + // Check for buffered vs. "unbuffered" + if (buffer_size > 0) + { + // Allocate internal buffer + buffer = new char_type[buffer_size]; + // Get area starts empty and will be expanded by underflow as need arises + this->setg(buffer, buffer, buffer); + // Setup entire internal buffer as put area. + // The one-past-end pointer actually points to the last element of the buffer, + // so that overflow(c) can safely add the extra character c to the sequence. + // These pointers remain in place for the duration of the buffer + this->setp(buffer, buffer + buffer_size - 1); + } + else + { + // Even in "unbuffered" case, (small?) get buffer is still required + buffer_size = SMALLBUFSIZE; + buffer = new char_type[buffer_size]; + this->setg(buffer, buffer, buffer); + // "Unbuffered" means no put buffer + this->setp(0, 0); + } + } + else + { + // If buffer already allocated, reset buffer pointers just to make sure no + // stale chars are lying around + this->setg(buffer, buffer, buffer); + this->setp(buffer, buffer + buffer_size - 1); + } +} + +// Destroy internal buffer +void +gzfilebuf::disable_buffer() +{ + // If internal buffer exists, deallocate it + if (own_buffer && buffer) + { + // Preserve unbuffered status by zeroing size + if (!this->pbase()) + buffer_size = 0; + delete[] buffer; + buffer = NULL; + this->setg(0, 0, 0); + this->setp(0, 0); + } + else + { + // Reset buffer pointers to initial state if external buffer exists + this->setg(buffer, buffer, buffer); + if (buffer) + this->setp(buffer, buffer + buffer_size - 1); + else + this->setp(0, 0); + } +} + +/*****************************************************************************/ + +// Default constructor initializes stream buffer +gzifstream::gzifstream() +: std::istream(NULL), sb() +{ this->init(&sb); } + +// Initialize stream buffer and open file +gzifstream::gzifstream(const char* name, + std::ios_base::openmode mode) +: std::istream(NULL), sb() +{ + this->init(&sb); + this->open(name, mode); +} + +// Initialize stream buffer and attach to file +gzifstream::gzifstream(int fd, + std::ios_base::openmode mode) +: std::istream(NULL), sb() +{ + this->init(&sb); + this->attach(fd, mode); +} + +// Open file and go into fail() state if unsuccessful +void +gzifstream::open(const char* name, + std::ios_base::openmode mode) +{ + if (!sb.open(name, mode | std::ios_base::in)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Attach to file and go into fail() state if unsuccessful +void +gzifstream::attach(int fd, + std::ios_base::openmode mode) +{ + if (!sb.attach(fd, mode | std::ios_base::in)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Close file +void +gzifstream::close() +{ + if (!sb.close()) + this->setstate(std::ios_base::failbit); +} + +/*****************************************************************************/ + +// Default constructor initializes stream buffer +gzofstream::gzofstream() +: std::ostream(NULL), sb() +{ this->init(&sb); } + +// Initialize stream buffer and open file +gzofstream::gzofstream(const char* name, + std::ios_base::openmode mode) +: std::ostream(NULL), sb() +{ + this->init(&sb); + this->open(name, mode); +} + +// Initialize stream buffer and attach to file +gzofstream::gzofstream(int fd, + std::ios_base::openmode mode) +: std::ostream(NULL), sb() +{ + this->init(&sb); + this->attach(fd, mode); +} + +// Open file and go into fail() state if unsuccessful +void +gzofstream::open(const char* name, + std::ios_base::openmode mode) +{ + if (!sb.open(name, mode | std::ios_base::out)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Attach to file and go into fail() state if unsuccessful +void +gzofstream::attach(int fd, + std::ios_base::openmode mode) +{ + if (!sb.attach(fd, mode | std::ios_base::out)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Close file +void +gzofstream::close() +{ + if (!sb.close()) + this->setstate(std::ios_base::failbit); +} diff --git a/zlib/zlib/contrib/iostream3/zfstream.h b/zlib/zlib/contrib/iostream3/zfstream.h new file mode 100644 index 00000000..8574479a --- /dev/null +++ b/zlib/zlib/contrib/iostream3/zfstream.h @@ -0,0 +1,466 @@ +/* + * A C++ I/O streams interface to the zlib gz* functions + * + * by Ludwig Schwardt + * original version by Kevin Ruland + * + * This version is standard-compliant and compatible with gcc 3.x. + */ + +#ifndef ZFSTREAM_H +#define ZFSTREAM_H + +#include // not iostream, since we don't need cin/cout +#include +#include "zlib.h" + +/*****************************************************************************/ + +/** + * @brief Gzipped file stream buffer class. + * + * This class implements basic_filebuf for gzipped files. It doesn't yet support + * seeking (allowed by zlib but slow/limited), putback and read/write access + * (tricky). Otherwise, it attempts to be a drop-in replacement for the standard + * file streambuf. +*/ +class gzfilebuf : public std::streambuf +{ +public: + // Default constructor. + gzfilebuf(); + + // Destructor. + virtual + ~gzfilebuf(); + + /** + * @brief Set compression level and strategy on the fly. + * @param comp_level Compression level (see zlib.h for allowed values) + * @param comp_strategy Compression strategy (see zlib.h for allowed values) + * @return Z_OK on success, Z_STREAM_ERROR otherwise. + * + * Unfortunately, these parameters cannot be modified separately, as the + * previous zfstream version assumed. Since the strategy is seldom changed, + * it can default and setcompression(level) then becomes like the old + * setcompressionlevel(level). + */ + int + setcompression(int comp_level, + int comp_strategy = Z_DEFAULT_STRATEGY); + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() const { return (file != NULL); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + open(const char* name, + std::ios_base::openmode mode); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + attach(int fd, + std::ios_base::openmode mode); + + /** + * @brief Close gzipped file. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + close(); + +protected: + /** + * @brief Convert ios open mode int to mode string used by zlib. + * @return True if valid mode flag combination. + */ + bool + open_mode(std::ios_base::openmode mode, + char* c_mode) const; + + /** + * @brief Number of characters available in stream buffer. + * @return Number of characters. + * + * This indicates number of characters in get area of stream buffer. + * These characters can be read without accessing the gzipped file. + */ + virtual std::streamsize + showmanyc(); + + /** + * @brief Fill get area from gzipped file. + * @return First character in get area on success, EOF on error. + * + * This actually reads characters from gzipped file to stream + * buffer. Always buffered. + */ + virtual int_type + underflow(); + + /** + * @brief Write put area to gzipped file. + * @param c Extra character to add to buffer contents. + * @return Non-EOF on success, EOF on error. + * + * This actually writes characters in stream buffer to + * gzipped file. With unbuffered output this is done one + * character at a time. + */ + virtual int_type + overflow(int_type c = traits_type::eof()); + + /** + * @brief Installs external stream buffer. + * @param p Pointer to char buffer. + * @param n Size of external buffer. + * @return @c this on success, NULL on failure. + * + * Call setbuf(0,0) to enable unbuffered output. + */ + virtual std::streambuf* + setbuf(char_type* p, + std::streamsize n); + + /** + * @brief Flush stream buffer to file. + * @return 0 on success, -1 on error. + * + * This calls underflow(EOF) to do the job. + */ + virtual int + sync(); + +// +// Some future enhancements +// +// virtual int_type uflow(); +// virtual int_type pbackfail(int_type c = traits_type::eof()); +// virtual pos_type +// seekoff(off_type off, +// std::ios_base::seekdir way, +// std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out); +// virtual pos_type +// seekpos(pos_type sp, +// std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out); + +private: + /** + * @brief Allocate internal buffer. + * + * This function is safe to call multiple times. It will ensure + * that a proper internal buffer exists if it is required. If the + * buffer already exists or is external, the buffer pointers will be + * reset to their original state. + */ + void + enable_buffer(); + + /** + * @brief Destroy internal buffer. + * + * This function is safe to call multiple times. It will ensure + * that the internal buffer is deallocated if it exists. In any + * case, it will also reset the buffer pointers. + */ + void + disable_buffer(); + + /** + * Underlying file pointer. + */ + gzFile file; + + /** + * Mode in which file was opened. + */ + std::ios_base::openmode io_mode; + + /** + * @brief True if this object owns file descriptor. + * + * This makes the class responsible for closing the file + * upon destruction. + */ + bool own_fd; + + /** + * @brief Stream buffer. + * + * For simplicity this remains allocated on the free store for the + * entire life span of the gzfilebuf object, unless replaced by setbuf. + */ + char_type* buffer; + + /** + * @brief Stream buffer size. + * + * Defaults to system default buffer size (typically 8192 bytes). + * Modified by setbuf. + */ + std::streamsize buffer_size; + + /** + * @brief True if this object owns stream buffer. + * + * This makes the class responsible for deleting the buffer + * upon destruction. + */ + bool own_buffer; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file input stream class. + * + * This class implements ifstream for gzipped files. Seeking and putback + * is not supported yet. +*/ +class gzifstream : public std::istream +{ +public: + // Default constructor + gzifstream(); + + /** + * @brief Construct stream on gzipped file to be opened. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::in). + */ + explicit + gzifstream(const char* name, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Construct stream on already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::in). + */ + explicit + gzifstream(int fd, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * Obtain underlying stream buffer. + */ + gzfilebuf* + rdbuf() const + { return const_cast(&sb); } + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() { return sb.is_open(); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::in). + * + * Stream will be in state good() if file opens successfully; + * otherwise in state fail(). This differs from the behavior of + * ifstream, which never sets the state to good() and therefore + * won't allow you to reuse the stream for a second file unless + * you manually clear() the state. The choice is a matter of + * convenience. + */ + void + open(const char* name, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::in). + * + * Stream will be in state good() if attach succeeded; otherwise + * in state fail(). + */ + void + attach(int fd, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Close gzipped file. + * + * Stream will be in state fail() if close failed. + */ + void + close(); + +private: + /** + * Underlying stream buffer. + */ + gzfilebuf sb; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file output stream class. + * + * This class implements ofstream for gzipped files. Seeking and putback + * is not supported yet. +*/ +class gzofstream : public std::ostream +{ +public: + // Default constructor + gzofstream(); + + /** + * @brief Construct stream on gzipped file to be opened. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::out). + */ + explicit + gzofstream(const char* name, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Construct stream on already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::out). + */ + explicit + gzofstream(int fd, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * Obtain underlying stream buffer. + */ + gzfilebuf* + rdbuf() const + { return const_cast(&sb); } + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() { return sb.is_open(); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::out). + * + * Stream will be in state good() if file opens successfully; + * otherwise in state fail(). This differs from the behavior of + * ofstream, which never sets the state to good() and therefore + * won't allow you to reuse the stream for a second file unless + * you manually clear() the state. The choice is a matter of + * convenience. + */ + void + open(const char* name, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::out). + * + * Stream will be in state good() if attach succeeded; otherwise + * in state fail(). + */ + void + attach(int fd, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Close gzipped file. + * + * Stream will be in state fail() if close failed. + */ + void + close(); + +private: + /** + * Underlying stream buffer. + */ + gzfilebuf sb; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file output stream manipulator class. + * + * This class defines a two-argument manipulator for gzofstream. It is used + * as base for the setcompression(int,int) manipulator. +*/ +template + class gzomanip2 + { + public: + // Allows insertor to peek at internals + template + friend gzofstream& + operator<<(gzofstream&, + const gzomanip2&); + + // Constructor + gzomanip2(gzofstream& (*f)(gzofstream&, T1, T2), + T1 v1, + T2 v2); + private: + // Underlying manipulator function + gzofstream& + (*func)(gzofstream&, T1, T2); + + // Arguments for manipulator function + T1 val1; + T2 val2; + }; + +/*****************************************************************************/ + +// Manipulator function thunks through to stream buffer +inline gzofstream& +setcompression(gzofstream &gzs, int l, int s = Z_DEFAULT_STRATEGY) +{ + (gzs.rdbuf())->setcompression(l, s); + return gzs; +} + +// Manipulator constructor stores arguments +template + inline + gzomanip2::gzomanip2(gzofstream &(*f)(gzofstream &, T1, T2), + T1 v1, + T2 v2) + : func(f), val1(v1), val2(v2) + { } + +// Insertor applies underlying manipulator function to stream +template + inline gzofstream& + operator<<(gzofstream& s, const gzomanip2& m) + { return (*m.func)(s, m.val1, m.val2); } + +// Insert this onto stream to simplify setting of compression level +inline gzomanip2 +setcompression(int l, int s = Z_DEFAULT_STRATEGY) +{ return gzomanip2(&setcompression, l, s); } + +#endif // ZFSTREAM_H diff --git a/zlib/zlib/contrib/masmx64/bld_ml64.bat b/zlib/zlib/contrib/masmx64/bld_ml64.bat new file mode 100755 index 00000000..f74bcef5 --- /dev/null +++ b/zlib/zlib/contrib/masmx64/bld_ml64.bat @@ -0,0 +1,2 @@ +ml64.exe /Flinffasx64 /c /Zi inffasx64.asm +ml64.exe /Flgvmat64 /c /Zi gvmat64.asm diff --git a/zlib/zlib/contrib/masmx64/gvmat64.asm b/zlib/zlib/contrib/masmx64/gvmat64.asm new file mode 100644 index 00000000..9879c28b --- /dev/null +++ b/zlib/zlib/contrib/masmx64/gvmat64.asm @@ -0,0 +1,553 @@ +;uInt longest_match_x64( +; deflate_state *s, +; IPos cur_match); /* current match */ + +; gvmat64.asm -- Asm portion of the optimized longest_match for 32 bits x86_64 +; (AMD64 on Athlon 64, Opteron, Phenom +; and Intel EM64T on Pentium 4 with EM64T, Pentium D, Core 2 Duo, Core I5/I7) +; Copyright (C) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; +; File written by Gilles Vollant, by converting to assembly the longest_match +; from Jean-loup Gailly in deflate.c of zLib and infoZip zip. +; +; and by taking inspiration on asm686 with masm, optimised assembly code +; from Brian Raiter, written 1998 +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software +; 3. This notice may not be removed or altered from any source distribution. +; +; +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; to compile this file for infozip Zip, I use option: +; ml64.exe /Flgvmat64 /c /Zi /DINFOZIP gvmat64.asm +; +; to compile this file for zLib, I use option: +; ml64.exe /Flgvmat64 /c /Zi gvmat64.asm +; Be carrefull to adapt zlib1222add below to your version of zLib +; (if you use a version of zLib before 1.0.4 or after 1.2.2.2, change +; value of zlib1222add later) +; +; This file compile with Microsoft Macro Assembler (x64) for AMD64 +; +; ml64.exe is given with Visual Studio 2005/2008/2010 and Windows WDK +; +; (you can get Windows WDK with ml64 for AMD64 from +; http://www.microsoft.com/whdc/Devtools/wdk/default.mspx for low price) +; + + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; /* current match */ +.code +longest_match PROC + + +;LocalVarsSize equ 88 + LocalVarsSize equ 72 + +; register used : rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12 +; free register : r14,r15 +; register can be saved : rsp + + chainlenwmask equ rsp + 8 - LocalVarsSize ; high word: current chain len + ; low word: s->wmask +;window equ rsp + xx - LocalVarsSize ; local copy of s->window ; stored in r10 +;windowbestlen equ rsp + xx - LocalVarsSize ; s->window + bestlen , use r10+r11 +;scanstart equ rsp + xx - LocalVarsSize ; first two bytes of string ; stored in r12w +;scanend equ rsp + xx - LocalVarsSize ; last two bytes of string use ebx +;scanalign equ rsp + xx - LocalVarsSize ; dword-misalignment of string r13 +;bestlen equ rsp + xx - LocalVarsSize ; size of best match so far -> r11d +;scan equ rsp + xx - LocalVarsSize ; ptr to string wanting match -> r9 +IFDEF INFOZIP +ELSE + nicematch equ (rsp + 16 - LocalVarsSize) ; a good enough match size +ENDIF + +save_rdi equ rsp + 24 - LocalVarsSize +save_rsi equ rsp + 32 - LocalVarsSize +save_rbx equ rsp + 40 - LocalVarsSize +save_rbp equ rsp + 48 - LocalVarsSize +save_r12 equ rsp + 56 - LocalVarsSize +save_r13 equ rsp + 64 - LocalVarsSize +;save_r14 equ rsp + 72 - LocalVarsSize +;save_r15 equ rsp + 80 - LocalVarsSize + + +; summary of register usage +; scanend ebx +; scanendw bx +; chainlenwmask edx +; curmatch rsi +; curmatchd esi +; windowbestlen r8 +; scanalign r9 +; scanalignd r9d +; window r10 +; bestlen r11 +; bestlend r11d +; scanstart r12d +; scanstartw r12w +; scan r13 +; nicematch r14d +; limit r15 +; limitd r15d +; prev rcx + +; all the +4 offsets are due to the addition of pending_buf_size (in zlib +; in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, remove the +4). +; Note : these value are good with a 8 bytes boundary pack structure + + + MAX_MATCH equ 258 + MIN_MATCH equ 3 + MIN_LOOKAHEAD equ (MAX_MATCH+MIN_MATCH+1) + + +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + + +IFDEF INFOZIP + +_DATA SEGMENT +COMM window_size:DWORD +; WMask ; 7fff +COMM window:BYTE:010040H +COMM prev:WORD:08000H +; MatchLen : unused +; PrevMatch : unused +COMM strstart:DWORD +COMM match_start:DWORD +; Lookahead : ignore +COMM prev_length:DWORD ; PrevLen +COMM max_chain_length:DWORD +COMM good_match:DWORD +COMM nice_match:DWORD +prev_ad equ OFFSET prev +window_ad equ OFFSET window +nicematch equ nice_match +_DATA ENDS +WMask equ 07fffh + +ELSE + + IFNDEF zlib1222add + zlib1222add equ 8 + ENDIF +dsWSize equ 56+zlib1222add+(zlib1222add/2) +dsWMask equ 64+zlib1222add+(zlib1222add/2) +dsWindow equ 72+zlib1222add +dsPrev equ 88+zlib1222add +dsMatchLen equ 128+zlib1222add +dsPrevMatch equ 132+zlib1222add +dsStrStart equ 140+zlib1222add +dsMatchStart equ 144+zlib1222add +dsLookahead equ 148+zlib1222add +dsPrevLen equ 152+zlib1222add +dsMaxChainLen equ 156+zlib1222add +dsGoodMatch equ 172+zlib1222add +dsNiceMatch equ 176+zlib1222add + +window_size equ [ rcx + dsWSize] +WMask equ [ rcx + dsWMask] +window_ad equ [ rcx + dsWindow] +prev_ad equ [ rcx + dsPrev] +strstart equ [ rcx + dsStrStart] +match_start equ [ rcx + dsMatchStart] +Lookahead equ [ rcx + dsLookahead] ; 0ffffffffh on infozip +prev_length equ [ rcx + dsPrevLen] +max_chain_length equ [ rcx + dsMaxChainLen] +good_match equ [ rcx + dsGoodMatch] +nice_match equ [ rcx + dsNiceMatch] +ENDIF + +; parameter 1 in r8(deflate state s), param 2 in rdx (cur match) + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r9, r10, and r11, which are scratch. + + + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + +;;; Retrieve the function arguments. r8d will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + +; parameter 1 in rcx (deflate_state* s), param 2 in edx -> r8 (cur match) + +; this clear high 32 bits of r8, which can be garbage in both r8 and rdx + + mov [save_rdi],rdi + mov [save_rsi],rsi + mov [save_rbx],rbx + mov [save_rbp],rbp +IFDEF INFOZIP + mov r8d,ecx +ELSE + mov r8d,edx +ENDIF + mov [save_r12],r12 + mov [save_r13],r13 +; mov [save_r14],r14 +; mov [save_r15],r15 + + +;;; uInt wmask = s->w_mask; +;;; unsigned chain_length = s->max_chain_length; +;;; if (s->prev_length >= s->good_match) { +;;; chain_length >>= 2; +;;; } + + mov edi, prev_length + mov esi, good_match + mov eax, WMask + mov ebx, max_chain_length + cmp edi, esi + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +;;; chainlen is decremented once beforehand so that the function can +;;; use the sign flag instead of the zero flag for the exit test. +;;; It is then shifted into the high word, to make room for the wmask +;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + +;;; on zlib only +;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + +IFDEF INFOZIP + mov [chainlenwmask], ebx +; on infozip nice_match = [nice_match] +ELSE + mov eax, nice_match + mov [chainlenwmask], ebx + mov r10d, Lookahead + cmp r10d, eax + cmovnl r10d, eax + mov [nicematch],r10d +ENDIF + +;;; register Bytef *scan = s->window + s->strstart; + mov r10, window_ad + mov ebp, strstart + lea r13, [r10 + rbp] + +;;; Determine how many bytes the scan ptr is off from being +;;; dword-aligned. + + mov r9,r13 + neg r13 + and r13,3 + +;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +;;; s->strstart - (IPos)MAX_DIST(s) : NIL; +IFDEF INFOZIP + mov eax,07efah ; MAX_DIST = (WSIZE-MIN_LOOKAHEAD) (0x8000-(3+8+1)) +ELSE + mov eax, window_size + sub eax, MIN_LOOKAHEAD +ENDIF + xor edi,edi + sub ebp, eax + + mov r11d, prev_length + + cmovng ebp,edi + +;;; int best_len = s->prev_length; + + +;;; Store the sum of s->window + best_len in esi locally, and in esi. + + lea rsi,[r10+r11] + +;;; register ush scan_start = *(ushf*)scan; +;;; register ush scan_end = *(ushf*)(scan+best_len-1); +;;; Posf *prev = s->prev; + + movzx r12d,word ptr [r9] + movzx ebx, word ptr [r9 + r11 - 1] + + mov rdi, prev_ad + +;;; Jump into the main loop. + + mov edx, [chainlenwmask] + + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop1: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry1: + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop2: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry2: + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop4: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry4: + + cmp bx,word ptr [rsi + r8 - 1] + jnz LookupLoop1 + jmp LookupLoopIsZero + + +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; r8d = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit + +LookupLoop: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry: + + cmp bx,word ptr [rsi + r8 - 1] + jnz LookupLoop1 +LookupLoopIsZero: + cmp r12w, word ptr [r10 + r8] + jnz LookupLoop1 + + +;;; Store the current value of chainlen. + mov [chainlenwmask], edx + +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). + + lea rsi,[r8+r10] + mov rdx, 0fffffffffffffef8h; -(MAX_MATCH_8) + lea rsi, [rsi + r13 + 0108h] ;MAX_MATCH_8] + lea rdi, [r9 + r13 + 0108h] ;MAX_MATCH_8] + + prefetcht1 [rsi+rdx] + prefetcht1 [rdi+rdx] + + +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust rdx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (rsi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. + + +LoopCmps: + mov rax, [rsi + rdx] + xor rax, [rdi + rdx] + jnz LeaveLoopCmps + + mov rax, [rsi + rdx + 8] + xor rax, [rdi + rdx + 8] + jnz LeaveLoopCmps8 + + + mov rax, [rsi + rdx + 8+8] + xor rax, [rdi + rdx + 8+8] + jnz LeaveLoopCmps16 + + add rdx,8+8+8 + + jnz short LoopCmps + jmp short LenMaximum +LeaveLoopCmps16: add rdx,8 +LeaveLoopCmps8: add rdx,8 +LeaveLoopCmps: + + test eax, 0000FFFFh + jnz LenLower + + test eax,0ffffffffh + + jnz LenLower32 + + add rdx,4 + shr rax,32 + or ax,ax + jnz LenLower + +LenLower32: + shr eax,16 + add rdx,2 +LenLower: sub al, 1 + adc rdx, 0 +;;; Calculate the length of the match. If it is longer than MAX_MATCH, +;;; then automatically accept it as the best possible match and leave. + + lea rax, [rdi + rdx] + sub rax, r9 + cmp eax, MAX_MATCH + jge LenMaximum + +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. +;/////////////////////////////////// + + cmp eax, r11d + jg LongerMatch + + lea rsi,[r10+r11] + + mov rdi, prev_ad + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); + +LongerMatch: + mov r11d, eax + mov match_start, r8d + cmp eax, [nicematch] + jge LeaveNow + + lea rsi,[r10+rax] + + movzx ebx, word ptr [r9 + rax - 1] + mov rdi, prev_ad + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; Accept the current string, with the maximum possible length. + +LenMaximum: + mov r11d,MAX_MATCH + mov match_start, r8d + +;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +;;; return s->lookahead; + +LeaveNow: +IFDEF INFOZIP + mov eax,r11d +ELSE + mov eax, Lookahead + cmp r11d, eax + cmovng eax, r11d +ENDIF + +;;; Restore the stack and return from whence we came. + + + mov rsi,[save_rsi] + mov rdi,[save_rdi] + mov rbx,[save_rbx] + mov rbp,[save_rbp] + mov r12,[save_r12] + mov r13,[save_r13] +; mov r14,[save_r14] +; mov r15,[save_r15] + + + ret 0 +; please don't remove this string ! +; Your can freely use gvmat64 in any free or commercial app +; but it is far better don't remove the string in the binary! + db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998, converted to amd 64 by Gilles Vollant 2005",0dh,0ah,0 +longest_match ENDP + +match_init PROC + ret 0 +match_init ENDP + + +END diff --git a/zlib/zlib/contrib/masmx64/inffas8664.c b/zlib/zlib/contrib/masmx64/inffas8664.c new file mode 100644 index 00000000..aa861a33 --- /dev/null +++ b/zlib/zlib/contrib/masmx64/inffas8664.c @@ -0,0 +1,186 @@ +/* inffas8664.c is a hand tuned assembler version of inffast.c - fast decoding + * version for AMD64 on Windows using Microsoft C compiler + * + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * 2005 - Adaptation to Microsoft C Compiler for AMD64 by Gilles Vollant + * + * inffas8664.c call function inffas8664fnc in inffasx64.asm + * inffasx64.asm is automatically convert from AMD64 portion of inffas86.c + * + * Dec-29-2003 -- I added AMD64 inflate asm support. This version is also + * slightly quicker on x86 systems because, instead of using rep movsb to copy + * data, it uses rep movsw, which moves data in 2-byte chunks instead of single + * bytes. I've tested the AMD64 code on a Fedora Core 1 + the x86_64 updates + * from http://fedora.linux.duke.edu/fc1_x86_64 + * which is running on an Athlon 64 3000+ / Gigabyte GA-K8VT800M system with + * 1GB ram. The 64-bit version is about 4% faster than the 32-bit version, + * when decompressing mozilla-source-1.3.tar.gz. + * + * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from + * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at + * the moment. I have successfully compiled and tested this code with gcc2.96, + * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S + * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX + * enabled. I will attempt to merge the MMX code into this version. Newer + * versions of this and inffast.S can be found at + * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ + * + */ + +#include +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* Mark Adler's comments from inffast.c: */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ + + + + typedef struct inffast_ar { +/* 64 32 x86 x86_64 */ +/* ar offset register */ +/* 0 0 */ void *esp; /* esp save */ +/* 8 4 */ void *ebp; /* ebp save */ +/* 16 8 */ unsigned char FAR *in; /* esi rsi local strm->next_in */ +/* 24 12 */ unsigned char FAR *last; /* r9 while in < last */ +/* 32 16 */ unsigned char FAR *out; /* edi rdi local strm->next_out */ +/* 40 20 */ unsigned char FAR *beg; /* inflate()'s init next_out */ +/* 48 24 */ unsigned char FAR *end; /* r10 while out < end */ +/* 56 28 */ unsigned char FAR *window;/* size of window, wsize!=0 */ +/* 64 32 */ code const FAR *lcode; /* ebp rbp local strm->lencode */ +/* 72 36 */ code const FAR *dcode; /* r11 local strm->distcode */ +/* 80 40 */ size_t /*unsigned long */hold; /* edx rdx local strm->hold */ +/* 88 44 */ unsigned bits; /* ebx rbx local strm->bits */ +/* 92 48 */ unsigned wsize; /* window size */ +/* 96 52 */ unsigned write; /* window write index */ +/*100 56 */ unsigned lmask; /* r12 mask for lcode */ +/*104 60 */ unsigned dmask; /* r13 mask for dcode */ +/*108 64 */ unsigned len; /* r14 match length */ +/*112 68 */ unsigned dist; /* r15 match distance */ +/*116 72 */ unsigned status; /* set when state chng*/ + } type_ar; +#ifdef ASMINF + +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + type_ar ar; + void inffas8664fnc(struct inffast_ar * par); + + + +#if (defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 )) || (defined(_MSC_VER) && defined(_M_AMD64)) +#define PAD_AVAIL_IN 6 +#define PAD_AVAIL_OUT 258 +#else +#define PAD_AVAIL_IN 5 +#define PAD_AVAIL_OUT 257 +#endif + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + + ar.in = strm->next_in; + ar.last = ar.in + (strm->avail_in - PAD_AVAIL_IN); + ar.out = strm->next_out; + ar.beg = ar.out - (start - strm->avail_out); + ar.end = ar.out + (strm->avail_out - PAD_AVAIL_OUT); + ar.wsize = state->wsize; + ar.write = state->wnext; + ar.window = state->window; + ar.hold = state->hold; + ar.bits = state->bits; + ar.lcode = state->lencode; + ar.dcode = state->distcode; + ar.lmask = (1U << state->lenbits) - 1; + ar.dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + /* align in on 1/2 hold size boundary */ + while (((size_t)(void *)ar.in & (sizeof(ar.hold) / 2 - 1)) != 0) { + ar.hold += (unsigned long)*ar.in++ << ar.bits; + ar.bits += 8; + } + + inffas8664fnc(&ar); + + if (ar.status > 1) { + if (ar.status == 2) + strm->msg = "invalid literal/length code"; + else if (ar.status == 3) + strm->msg = "invalid distance code"; + else + strm->msg = "invalid distance too far back"; + state->mode = BAD; + } + else if ( ar.status == 1 ) { + state->mode = TYPE; + } + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + ar.len = ar.bits >> 3; + ar.in -= ar.len; + ar.bits -= ar.len << 3; + ar.hold &= (1U << ar.bits) - 1; + + /* update state and return */ + strm->next_in = ar.in; + strm->next_out = ar.out; + strm->avail_in = (unsigned)(ar.in < ar.last ? + PAD_AVAIL_IN + (ar.last - ar.in) : + PAD_AVAIL_IN - (ar.in - ar.last)); + strm->avail_out = (unsigned)(ar.out < ar.end ? + PAD_AVAIL_OUT + (ar.end - ar.out) : + PAD_AVAIL_OUT - (ar.out - ar.end)); + state->hold = (unsigned long)ar.hold; + state->bits = ar.bits; + return; +} + +#endif diff --git a/zlib/zlib/contrib/masmx64/inffasx64.asm b/zlib/zlib/contrib/masmx64/inffasx64.asm new file mode 100644 index 00000000..60a8d89b --- /dev/null +++ b/zlib/zlib/contrib/masmx64/inffasx64.asm @@ -0,0 +1,396 @@ +; inffasx64.asm is a hand tuned assembler version of inffast.c - fast decoding +; version for AMD64 on Windows using Microsoft C compiler +; +; inffasx64.asm is automatically convert from AMD64 portion of inffas86.c +; inffasx64.asm is called by inffas8664.c, which contain more info. + + +; to compile this file, I use option +; ml64.exe /Flinffasx64 /c /Zi inffasx64.asm +; with Microsoft Macro Assembler (x64) for AMD64 +; + +; This file compile with Microsoft Macro Assembler (x64) for AMD64 +; +; ml64.exe is given with Visual Studio 2005/2008/2010 and Windows WDK +; +; (you can get Windows WDK with ml64 for AMD64 from +; http://www.microsoft.com/whdc/Devtools/wdk/default.mspx for low price) +; + + +.code +inffas8664fnc PROC + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r-9, r10, and r11, which are scratch. + + + mov [rsp-8],rsi + mov [rsp-16],rdi + mov [rsp-24],r12 + mov [rsp-32],r13 + mov [rsp-40],r14 + mov [rsp-48],r15 + mov [rsp-56],rbx + + mov rax,rcx + + mov [rax+8], rbp ; /* save regs rbp and rsp */ + mov [rax], rsp + + mov rsp, rax ; /* make rsp point to &ar */ + + mov rsi, [rsp+16] ; /* rsi = in */ + mov rdi, [rsp+32] ; /* rdi = out */ + mov r9, [rsp+24] ; /* r9 = last */ + mov r10, [rsp+48] ; /* r10 = end */ + mov rbp, [rsp+64] ; /* rbp = lcode */ + mov r11, [rsp+72] ; /* r11 = dcode */ + mov rdx, [rsp+80] ; /* rdx = hold */ + mov ebx, [rsp+88] ; /* ebx = bits */ + mov r12d, [rsp+100] ; /* r12d = lmask */ + mov r13d, [rsp+104] ; /* r13d = dmask */ + ; /* r14d = len */ + ; /* r15d = dist */ + + + cld + cmp r10, rdi + je L_one_time ; /* if only one decode left */ + cmp r9, rsi + + jne L_do_loop + + +L_one_time: + mov r8, r12 ; /* r8 = lmask */ + cmp bl, 32 + ja L_get_length_code_one_time + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + jmp L_get_length_code_one_time + +ALIGN 4 +L_while_test: + cmp r10, rdi + jbe L_break_loop + cmp r9, rsi + jbe L_break_loop + +L_do_loop: + mov r8, r12 ; /* r8 = lmask */ + cmp bl, 32 + ja L_get_length_code ; /* if (32 < bits) */ + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + +L_get_length_code: + and r8, rdx ; /* r8 &= hold */ + mov eax, [rbp+r8*4] ; /* eax = lcode[hold & lmask] */ + + mov cl, ah ; /* cl = this.bits */ + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base ; /* if (op != 0) 45.7% */ + + mov r8, r12 ; /* r8 = lmask */ + shr eax, 16 ; /* output this.val char */ + stosb + +L_get_length_code_one_time: + and r8, rdx ; /* r8 &= hold */ + mov eax, [rbp+r8*4] ; /* eax = lcode[hold & lmask] */ + +L_dolen: + mov cl, ah ; /* cl = this.bits */ + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base ; /* if (op != 0) 45.7% */ + + shr eax, 16 ; /* output this.val char */ + stosb + jmp L_while_test + +ALIGN 4 +L_test_for_length_base: + mov r14d, eax ; /* len = this */ + shr r14d, 16 ; /* len = this.val */ + mov cl, al + + test al, 16 + jz L_test_for_second_level_length ; /* if ((op & 16) == 0) 8% */ + and cl, 15 ; /* op &= 15 */ + jz L_decode_distance ; /* if (!op) */ + +L_add_bits_to_len: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + shr rdx, cl + add r14d, eax ; /* len += hold & mask[op] */ + +L_decode_distance: + mov r8, r13 ; /* r8 = dmask */ + cmp bl, 32 + ja L_get_distance_code ; /* if (32 < bits) */ + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + +L_get_distance_code: + and r8, rdx ; /* r8 &= hold */ + mov eax, [r11+r8*4] ; /* eax = dcode[hold & dmask] */ + +L_dodist: + mov r15d, eax ; /* dist = this */ + shr r15d, 16 ; /* dist = this.val */ + mov cl, ah + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + mov cl, al ; /* cl = this.op */ + + test al, 16 ; /* if ((op & 16) == 0) */ + jz L_test_for_second_level_dist + and cl, 15 ; /* op &= 15 */ + jz L_check_dist_one + +L_add_bits_to_dist: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax ; /* (1 << op) - 1 */ + and eax, edx ; /* eax &= hold */ + shr rdx, cl + add r15d, eax ; /* dist += hold & ((1 << op) - 1) */ + +L_check_window: + mov r8, rsi ; /* save in so from can use it's reg */ + mov rax, rdi + sub rax, [rsp+40] ; /* nbytes = out - beg */ + + cmp eax, r15d + jb L_clip_window ; /* if (dist > nbytes) 4.2% */ + + mov ecx, r14d ; /* ecx = len */ + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + + sar ecx, 1 + jnc L_copy_two ; /* if len % 2 == 0 */ + + rep movsw + mov al, [rsi] + mov [rdi], al + inc rdi + + mov rsi, r8 ; /* move in back to %rsi, toss from */ + jmp L_while_test + +L_copy_two: + rep movsw + mov rsi, r8 ; /* move in back to %rsi, toss from */ + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp r15d, 1 ; /* if dist 1, is a memset */ + jne L_check_window + cmp [rsp+40], rdi ; /* if out == beg, outside window */ + je L_check_window + + mov ecx, r14d ; /* ecx = len */ + mov al, [rdi-1] + mov ah, al + + sar ecx, 1 + jnc L_set_two + mov [rdi], al + inc rdi + +L_set_two: + rep stosw + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + test al, 64 + jnz L_test_for_end_of_block ; /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + add eax, r14d ; /* eax += len */ + mov eax, [rbp+rax*4] ; /* eax = lcode[val+(hold&mask[op])]*/ + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + test al, 64 + jnz L_invalid_distance_code ; /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + add eax, r15d ; /* eax += dist */ + mov eax, [r11+rax*4] ; /* eax = dcode[val+(hold&mask[op])]*/ + jmp L_dodist + +ALIGN 4 +L_clip_window: + mov ecx, eax ; /* ecx = nbytes */ + mov eax, [rsp+92] ; /* eax = wsize, prepare for dist cmp */ + neg ecx ; /* nbytes = -nbytes */ + + cmp eax, r15d + jb L_invalid_distance_too_far ; /* if (dist > wsize) */ + + add ecx, r15d ; /* nbytes = dist - nbytes */ + cmp dword ptr [rsp+96], 0 + jne L_wrap_around_window ; /* if (write != 0) */ + + mov rsi, [rsp+56] ; /* from = window */ + sub eax, ecx ; /* eax -= nbytes */ + add rsi, rax ; /* from += wsize - nbytes */ + + mov eax, r14d ; /* eax = len */ + cmp r14d, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* eax -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = &out[ -dist ] */ + jmp L_do_copy + +ALIGN 4 +L_wrap_around_window: + mov eax, [rsp+96] ; /* eax = write */ + cmp ecx, eax + jbe L_contiguous_in_window ; /* if (write >= nbytes) */ + + mov esi, [rsp+92] ; /* from = wsize */ + add rsi, [rsp+56] ; /* from += window */ + add rsi, rax ; /* from += write */ + sub rsi, rcx ; /* from -= nbytes */ + sub ecx, eax ; /* nbytes -= write */ + + mov eax, r14d ; /* eax = len */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, [rsp+56] ; /* from = window */ + mov ecx, [rsp+96] ; /* nbytes = write */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_contiguous_in_window: + mov rsi, [rsp+56] ; /* rsi = window */ + add rsi, rax + sub rsi, rcx ; /* from += write - nbytes */ + + mov eax, r14d ; /* eax = len */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + jmp L_do_copy ; /* if (nbytes >= len) */ + +ALIGN 4 +L_do_copy: + mov ecx, eax ; /* ecx = len */ + rep movsb + + mov rsi, r8 ; /* move in back to %esi, toss from */ + jmp L_while_test + +L_test_for_end_of_block: + test al, 32 + jz L_invalid_literal_length_code + mov dword ptr [rsp+116], 1 + jmp L_break_loop_with_status + +L_invalid_literal_length_code: + mov dword ptr [rsp+116], 2 + jmp L_break_loop_with_status + +L_invalid_distance_code: + mov dword ptr [rsp+116], 3 + jmp L_break_loop_with_status + +L_invalid_distance_too_far: + mov dword ptr [rsp+116], 4 + jmp L_break_loop_with_status + +L_break_loop: + mov dword ptr [rsp+116], 0 + +L_break_loop_with_status: +; /* put in, out, bits, and hold back into ar and pop esp */ + mov [rsp+16], rsi ; /* in */ + mov [rsp+32], rdi ; /* out */ + mov [rsp+88], ebx ; /* bits */ + mov [rsp+80], rdx ; /* hold */ + + mov rax, [rsp] ; /* restore rbp and rsp */ + mov rbp, [rsp+8] + mov rsp, rax + + + + mov rsi,[rsp-8] + mov rdi,[rsp-16] + mov r12,[rsp-24] + mov r13,[rsp-32] + mov r14,[rsp-40] + mov r15,[rsp-48] + mov rbx,[rsp-56] + + ret 0 +; : +; : "m" (ar) +; : "memory", "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", +; "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" +; ); + +inffas8664fnc ENDP +;_TEXT ENDS +END diff --git a/zlib/zlib/contrib/masmx64/readme.txt b/zlib/zlib/contrib/masmx64/readme.txt new file mode 100644 index 00000000..652571c7 --- /dev/null +++ b/zlib/zlib/contrib/masmx64/readme.txt @@ -0,0 +1,31 @@ +Summary +------- +This directory contains ASM implementations of the functions +longest_match() and inflate_fast(), for 64 bits x86 (both AMD64 and Intel EM64t), +for use with Microsoft Macro Assembler (x64) for AMD64 and Microsoft C++ 64 bits. + +gvmat64.asm is written by Gilles Vollant (2005), by using Brian Raiter 686/32 bits + assembly optimized version from Jean-loup Gailly original longest_match function + +inffasx64.asm and inffas8664.c were written by Chris Anderson, by optimizing + original function from Mark Adler + +Use instructions +---------------- +Assemble the .asm files using MASM and put the object files into the zlib source +directory. You can also get object files here: + + http://www.winimage.com/zLibDll/zlib124_masm_obj.zip + +define ASMV and ASMINF in your project. Include inffas8664.c in your source tree, +and inffasx64.obj and gvmat64.obj as object to link. + + +Build instructions +------------------ +run bld_64.bat with Microsoft Macro Assembler (x64) for AMD64 (ml64.exe) + +ml64.exe is given with Visual Studio 2005, Windows 2003 server DDK + +You can get Windows 2003 server DDK with ml64 and cl for AMD64 from + http://www.microsoft.com/whdc/devtools/ddk/default.mspx for low price) diff --git a/zlib/zlib/contrib/masmx86/bld_ml32.bat b/zlib/zlib/contrib/masmx86/bld_ml32.bat new file mode 100755 index 00000000..fcf5755e --- /dev/null +++ b/zlib/zlib/contrib/masmx86/bld_ml32.bat @@ -0,0 +1,2 @@ +ml /coff /Zi /c /Flmatch686.lst match686.asm +ml /coff /Zi /c /Flinffas32.lst inffas32.asm diff --git a/zlib/zlib/contrib/masmx86/inffas32.asm b/zlib/zlib/contrib/masmx86/inffas32.asm new file mode 100644 index 00000000..03d20f83 --- /dev/null +++ b/zlib/zlib/contrib/masmx86/inffas32.asm @@ -0,0 +1,1080 @@ +;/* inffas32.asm is a hand tuned assembler version of inffast.c -- fast decoding +; * +; * inffas32.asm is derivated from inffas86.c, with translation of assembly code +; * +; * Copyright (C) 1995-2003 Mark Adler +; * For conditions of distribution and use, see copyright notice in zlib.h +; * +; * Copyright (C) 2003 Chris Anderson +; * Please use the copyright conditions above. +; * +; * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from +; * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at +; * the moment. I have successfully compiled and tested this code with gcc2.96, +; * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S +; * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX +; * enabled. I will attempt to merge the MMX code into this version. Newer +; * versions of this and inffast.S can be found at +; * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ +; * +; * 2005 : modification by Gilles Vollant +; */ +; For Visual C++ 4.x and higher and ML 6.x and higher +; ml.exe is in directory \MASM611C of Win95 DDK +; ml.exe is also distributed in http://www.masm32.com/masmdl.htm +; and in VC++2003 toolkit at http://msdn.microsoft.com/visualc/vctoolkit2003/ +; +; +; compile with command line option +; ml /coff /Zi /c /Flinffas32.lst inffas32.asm + +; if you define NO_GZIP (see inflate.h), compile with +; ml /coff /Zi /c /Flinffas32.lst /DNO_GUNZIP inffas32.asm + + +; zlib122sup is 0 fort zlib 1.2.2.1 and lower +; zlib122sup is 8 fort zlib 1.2.2.2 and more (with addition of dmax and head +; in inflate_state in inflate.h) +zlib1222sup equ 8 + + +IFDEF GUNZIP + INFLATE_MODE_TYPE equ 11 + INFLATE_MODE_BAD equ 26 +ELSE + IFNDEF NO_GUNZIP + INFLATE_MODE_TYPE equ 11 + INFLATE_MODE_BAD equ 26 + ELSE + INFLATE_MODE_TYPE equ 3 + INFLATE_MODE_BAD equ 17 + ENDIF +ENDIF + + +; 75 "inffast.S" +;FILE "inffast.S" + +;;;GLOBAL _inflate_fast + +;;;SECTION .text + + + + .586p + .mmx + + name inflate_fast_x86 + .MODEL FLAT + +_DATA segment +inflate_fast_use_mmx: + dd 1 + + +_TEXT segment + + + +ALIGN 4 + db 'Fast decoding Code from Chris Anderson' + db 0 + +ALIGN 4 +invalid_literal_length_code_msg: + db 'invalid literal/length code' + db 0 + +ALIGN 4 +invalid_distance_code_msg: + db 'invalid distance code' + db 0 + +ALIGN 4 +invalid_distance_too_far_msg: + db 'invalid distance too far back' + db 0 + + +ALIGN 4 +inflate_fast_mask: +dd 0 +dd 1 +dd 3 +dd 7 +dd 15 +dd 31 +dd 63 +dd 127 +dd 255 +dd 511 +dd 1023 +dd 2047 +dd 4095 +dd 8191 +dd 16383 +dd 32767 +dd 65535 +dd 131071 +dd 262143 +dd 524287 +dd 1048575 +dd 2097151 +dd 4194303 +dd 8388607 +dd 16777215 +dd 33554431 +dd 67108863 +dd 134217727 +dd 268435455 +dd 536870911 +dd 1073741823 +dd 2147483647 +dd 4294967295 + + +mode_state equ 0 ;/* state->mode */ +wsize_state equ (32+zlib1222sup) ;/* state->wsize */ +write_state equ (36+4+zlib1222sup) ;/* state->write */ +window_state equ (40+4+zlib1222sup) ;/* state->window */ +hold_state equ (44+4+zlib1222sup) ;/* state->hold */ +bits_state equ (48+4+zlib1222sup) ;/* state->bits */ +lencode_state equ (64+4+zlib1222sup) ;/* state->lencode */ +distcode_state equ (68+4+zlib1222sup) ;/* state->distcode */ +lenbits_state equ (72+4+zlib1222sup) ;/* state->lenbits */ +distbits_state equ (76+4+zlib1222sup) ;/* state->distbits */ + + +;;SECTION .text +; 205 "inffast.S" +;GLOBAL inflate_fast_use_mmx + +;SECTION .data + + +; GLOBAL inflate_fast_use_mmx:object +;.size inflate_fast_use_mmx, 4 +; 226 "inffast.S" +;SECTION .text + +ALIGN 4 +_inflate_fast proc near +.FPO (16, 4, 0, 0, 1, 0) + push edi + push esi + push ebp + push ebx + pushfd + sub esp,64 + cld + + + + + mov esi, [esp+88] + mov edi, [esi+28] + + + + + + + + mov edx, [esi+4] + mov eax, [esi+0] + + add edx,eax + sub edx,11 + + mov [esp+44],eax + mov [esp+20],edx + + mov ebp, [esp+92] + mov ecx, [esi+16] + mov ebx, [esi+12] + + sub ebp,ecx + neg ebp + add ebp,ebx + + sub ecx,257 + add ecx,ebx + + mov [esp+60],ebx + mov [esp+40],ebp + mov [esp+16],ecx +; 285 "inffast.S" + mov eax, [edi+lencode_state] + mov ecx, [edi+distcode_state] + + mov [esp+8],eax + mov [esp+12],ecx + + mov eax,1 + mov ecx, [edi+lenbits_state] + shl eax,cl + dec eax + mov [esp+0],eax + + mov eax,1 + mov ecx, [edi+distbits_state] + shl eax,cl + dec eax + mov [esp+4],eax + + mov eax, [edi+wsize_state] + mov ecx, [edi+write_state] + mov edx, [edi+window_state] + + mov [esp+52],eax + mov [esp+48],ecx + mov [esp+56],edx + + mov ebp, [edi+hold_state] + mov ebx, [edi+bits_state] +; 321 "inffast.S" + mov esi, [esp+44] + mov ecx, [esp+20] + cmp ecx,esi + ja L_align_long + + add ecx,11 + sub ecx,esi + mov eax,12 + sub eax,ecx + lea edi, [esp+28] + rep movsb + mov ecx,eax + xor eax,eax + rep stosb + lea esi, [esp+28] + mov [esp+20],esi + jmp L_is_aligned + + +L_align_long: + test esi,3 + jz L_is_aligned + xor eax,eax + mov al, [esi] + inc esi + mov ecx,ebx + add ebx,8 + shl eax,cl + or ebp,eax + jmp L_align_long + +L_is_aligned: + mov edi, [esp+60] +; 366 "inffast.S" +L_check_mmx: + cmp dword ptr [inflate_fast_use_mmx],2 + je L_init_mmx + ja L_do_loop + + push eax + push ebx + push ecx + push edx + pushfd + mov eax, [esp] + xor dword ptr [esp],0200000h + + + + + popfd + pushfd + pop edx + xor edx,eax + jz L_dont_use_mmx + xor eax,eax + cpuid + cmp ebx,0756e6547h + jne L_dont_use_mmx + cmp ecx,06c65746eh + jne L_dont_use_mmx + cmp edx,049656e69h + jne L_dont_use_mmx + mov eax,1 + cpuid + shr eax,8 + and eax,15 + cmp eax,6 + jne L_dont_use_mmx + test edx,0800000h + jnz L_use_mmx + jmp L_dont_use_mmx +L_use_mmx: + mov dword ptr [inflate_fast_use_mmx],2 + jmp L_check_mmx_pop +L_dont_use_mmx: + mov dword ptr [inflate_fast_use_mmx],3 +L_check_mmx_pop: + pop edx + pop ecx + pop ebx + pop eax + jmp L_check_mmx +; 426 "inffast.S" +ALIGN 4 +L_do_loop: +; 437 "inffast.S" + cmp bl,15 + ja L_get_length_code + + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + +L_get_length_code: + mov edx, [esp+0] + mov ecx, [esp+8] + and edx,ebp + mov eax, [ecx+edx*4] + +L_dolen: + + + + + + + mov cl,ah + sub bl,ah + shr ebp,cl + + + + + + + test al,al + jnz L_test_for_length_base + + shr eax,16 + stosb + +L_while_test: + + + cmp [esp+16],edi + jbe L_break_loop + + cmp [esp+20],esi + ja L_do_loop + jmp L_break_loop + +L_test_for_length_base: +; 502 "inffast.S" + mov edx,eax + shr edx,16 + mov cl,al + + test al,16 + jz L_test_for_second_level_length + and cl,15 + jz L_save_len + cmp bl,cl + jae L_add_bits_to_len + + mov ch,cl + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + mov cl,ch + +L_add_bits_to_len: + mov eax,1 + shl eax,cl + dec eax + sub bl,cl + and eax,ebp + shr ebp,cl + add edx,eax + +L_save_len: + mov [esp+24],edx + + +L_decode_distance: +; 549 "inffast.S" + cmp bl,15 + ja L_get_distance_code + + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + +L_get_distance_code: + mov edx, [esp+4] + mov ecx, [esp+12] + and edx,ebp + mov eax, [ecx+edx*4] + + +L_dodist: + mov edx,eax + shr edx,16 + mov cl,ah + sub bl,ah + shr ebp,cl +; 584 "inffast.S" + mov cl,al + + test al,16 + jz L_test_for_second_level_dist + and cl,15 + jz L_check_dist_one + cmp bl,cl + jae L_add_bits_to_dist + + mov ch,cl + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + mov cl,ch + +L_add_bits_to_dist: + mov eax,1 + shl eax,cl + dec eax + sub bl,cl + and eax,ebp + shr ebp,cl + add edx,eax + jmp L_check_window + +L_check_window: +; 625 "inffast.S" + mov [esp+44],esi + mov eax,edi + sub eax, [esp+40] + + cmp eax,edx + jb L_clip_window + + mov ecx, [esp+24] + mov esi,edi + sub esi,edx + + sub ecx,3 + mov al, [esi] + mov [edi],al + mov al, [esi+1] + mov dl, [esi+2] + add esi,3 + mov [edi+1],al + mov [edi+2],dl + add edi,3 + rep movsb + + mov esi, [esp+44] + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp edx,1 + jne L_check_window + cmp [esp+40],edi + je L_check_window + + dec edi + mov ecx, [esp+24] + mov al, [edi] + sub ecx,3 + + mov [edi+1],al + mov [edi+2],al + mov [edi+3],al + add edi,4 + rep stosb + + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + + + + + test al,64 + jnz L_test_for_end_of_block + + mov eax,1 + shl eax,cl + dec eax + and eax,ebp + add eax,edx + mov edx, [esp+8] + mov eax, [edx+eax*4] + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + + + + + test al,64 + jnz L_invalid_distance_code + + mov eax,1 + shl eax,cl + dec eax + and eax,ebp + add eax,edx + mov edx, [esp+12] + mov eax, [edx+eax*4] + jmp L_dodist + +ALIGN 4 +L_clip_window: +; 721 "inffast.S" + mov ecx,eax + mov eax, [esp+52] + neg ecx + mov esi, [esp+56] + + cmp eax,edx + jb L_invalid_distance_too_far + + add ecx,edx + cmp dword ptr [esp+48],0 + jne L_wrap_around_window + + sub eax,ecx + add esi,eax +; 749 "inffast.S" + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + +L_wrap_around_window: +; 793 "inffast.S" + mov eax, [esp+48] + cmp ecx,eax + jbe L_contiguous_in_window + + add esi, [esp+52] + add esi,eax + sub esi,ecx + sub ecx,eax + + + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi, [esp+56] + mov ecx, [esp+48] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + +L_contiguous_in_window: +; 836 "inffast.S" + add esi,eax + sub esi,ecx + + + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + +L_do_copy1: +; 862 "inffast.S" + mov ecx,eax + rep movsb + + mov esi, [esp+44] + jmp L_while_test +; 878 "inffast.S" +ALIGN 4 +L_init_mmx: + emms + + + + + + movd mm0,ebp + mov ebp,ebx +; 896 "inffast.S" + movd mm4,dword ptr [esp+0] + movq mm3,mm4 + movd mm5,dword ptr [esp+4] + movq mm2,mm5 + pxor mm1,mm1 + mov ebx, [esp+8] + jmp L_do_loop_mmx + +ALIGN 4 +L_do_loop_mmx: + psrlq mm0,mm1 + + cmp ebp,32 + ja L_get_length_code_mmx + + movd mm6,ebp + movd mm7,dword ptr [esi] + add esi,4 + psllq mm7,mm6 + add ebp,32 + por mm0,mm7 + +L_get_length_code_mmx: + pand mm4,mm0 + movd eax,mm4 + movq mm4,mm3 + mov eax, [ebx+eax*4] + +L_dolen_mmx: + movzx ecx,ah + movd mm1,ecx + sub ebp,ecx + + test al,al + jnz L_test_for_length_base_mmx + + shr eax,16 + stosb + +L_while_test_mmx: + + + cmp [esp+16],edi + jbe L_break_loop + + cmp [esp+20],esi + ja L_do_loop_mmx + jmp L_break_loop + +L_test_for_length_base_mmx: + + mov edx,eax + shr edx,16 + + test al,16 + jz L_test_for_second_level_length_mmx + and eax,15 + jz L_decode_distance_mmx + + psrlq mm0,mm1 + movd mm1,eax + movd ecx,mm0 + sub ebp,eax + and ecx, [inflate_fast_mask+eax*4] + add edx,ecx + +L_decode_distance_mmx: + psrlq mm0,mm1 + + cmp ebp,32 + ja L_get_dist_code_mmx + + movd mm6,ebp + movd mm7,dword ptr [esi] + add esi,4 + psllq mm7,mm6 + add ebp,32 + por mm0,mm7 + +L_get_dist_code_mmx: + mov ebx, [esp+12] + pand mm5,mm0 + movd eax,mm5 + movq mm5,mm2 + mov eax, [ebx+eax*4] + +L_dodist_mmx: + + movzx ecx,ah + mov ebx,eax + shr ebx,16 + sub ebp,ecx + movd mm1,ecx + + test al,16 + jz L_test_for_second_level_dist_mmx + and eax,15 + jz L_check_dist_one_mmx + +L_add_bits_to_dist_mmx: + psrlq mm0,mm1 + movd mm1,eax + movd ecx,mm0 + sub ebp,eax + and ecx, [inflate_fast_mask+eax*4] + add ebx,ecx + +L_check_window_mmx: + mov [esp+44],esi + mov eax,edi + sub eax, [esp+40] + + cmp eax,ebx + jb L_clip_window_mmx + + mov ecx,edx + mov esi,edi + sub esi,ebx + + sub ecx,3 + mov al, [esi] + mov [edi],al + mov al, [esi+1] + mov dl, [esi+2] + add esi,3 + mov [edi+1],al + mov [edi+2],dl + add edi,3 + rep movsb + + mov esi, [esp+44] + mov ebx, [esp+8] + jmp L_while_test_mmx + +ALIGN 4 +L_check_dist_one_mmx: + cmp ebx,1 + jne L_check_window_mmx + cmp [esp+40],edi + je L_check_window_mmx + + dec edi + mov ecx,edx + mov al, [edi] + sub ecx,3 + + mov [edi+1],al + mov [edi+2],al + mov [edi+3],al + add edi,4 + rep stosb + + mov ebx, [esp+8] + jmp L_while_test_mmx + +ALIGN 4 +L_test_for_second_level_length_mmx: + test al,64 + jnz L_test_for_end_of_block + + and eax,15 + psrlq mm0,mm1 + movd ecx,mm0 + and ecx, [inflate_fast_mask+eax*4] + add ecx,edx + mov eax, [ebx+ecx*4] + jmp L_dolen_mmx + +ALIGN 4 +L_test_for_second_level_dist_mmx: + test al,64 + jnz L_invalid_distance_code + + and eax,15 + psrlq mm0,mm1 + movd ecx,mm0 + and ecx, [inflate_fast_mask+eax*4] + mov eax, [esp+12] + add ecx,ebx + mov eax, [eax+ecx*4] + jmp L_dodist_mmx + +ALIGN 4 +L_clip_window_mmx: + + mov ecx,eax + mov eax, [esp+52] + neg ecx + mov esi, [esp+56] + + cmp eax,ebx + jb L_invalid_distance_too_far + + add ecx,ebx + cmp dword ptr [esp+48],0 + jne L_wrap_around_window_mmx + + sub eax,ecx + add esi,eax + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + +L_wrap_around_window_mmx: + + mov eax, [esp+48] + cmp ecx,eax + jbe L_contiguous_in_window_mmx + + add esi, [esp+52] + add esi,eax + sub esi,ecx + sub ecx,eax + + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi, [esp+56] + mov ecx, [esp+48] + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + +L_contiguous_in_window_mmx: + + add esi,eax + sub esi,ecx + + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + +L_do_copy1_mmx: + + + mov ecx,edx + rep movsb + + mov esi, [esp+44] + mov ebx, [esp+8] + jmp L_while_test_mmx +; 1174 "inffast.S" +L_invalid_distance_code: + + + + + + mov ecx, invalid_distance_code_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_test_for_end_of_block: + + + + + + test al,32 + jz L_invalid_literal_length_code + + mov ecx,0 + mov edx,INFLATE_MODE_TYPE + jmp L_update_stream_state + +L_invalid_literal_length_code: + + + + + + mov ecx, invalid_literal_length_code_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_invalid_distance_too_far: + + + + mov esi, [esp+44] + mov ecx, invalid_distance_too_far_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_update_stream_state: + + mov eax, [esp+88] + test ecx,ecx + jz L_skip_msg + mov [eax+24],ecx +L_skip_msg: + mov eax, [eax+28] + mov [eax+mode_state],edx + jmp L_break_loop + +ALIGN 4 +L_break_loop: +; 1243 "inffast.S" + cmp dword ptr [inflate_fast_use_mmx],2 + jne L_update_next_in + + + + mov ebx,ebp + +L_update_next_in: +; 1266 "inffast.S" + mov eax, [esp+88] + mov ecx,ebx + mov edx, [eax+28] + shr ecx,3 + sub esi,ecx + shl ecx,3 + sub ebx,ecx + mov [eax+12],edi + mov [edx+bits_state],ebx + mov ecx,ebx + + lea ebx, [esp+28] + cmp [esp+20],ebx + jne L_buf_not_used + + sub esi,ebx + mov ebx, [eax+0] + mov [esp+20],ebx + add esi,ebx + mov ebx, [eax+4] + sub ebx,11 + add [esp+20],ebx + +L_buf_not_used: + mov [eax+0],esi + + mov ebx,1 + shl ebx,cl + dec ebx + + + + + + cmp dword ptr [inflate_fast_use_mmx],2 + jne L_update_hold + + + + psrlq mm0,mm1 + movd ebp,mm0 + + emms + +L_update_hold: + + + + and ebp,ebx + mov [edx+hold_state],ebp + + + + + mov ebx, [esp+20] + cmp ebx,esi + jbe L_last_is_smaller + + sub ebx,esi + add ebx,11 + mov [eax+4],ebx + jmp L_fixup_out +L_last_is_smaller: + sub esi,ebx + neg esi + add esi,11 + mov [eax+4],esi + + + + +L_fixup_out: + + mov ebx, [esp+16] + cmp ebx,edi + jbe L_end_is_smaller + + sub ebx,edi + add ebx,257 + mov [eax+16],ebx + jmp L_done +L_end_is_smaller: + sub edi,ebx + neg edi + add edi,257 + mov [eax+16],edi + + + + + +L_done: + add esp,64 + popfd + pop ebx + pop ebp + pop esi + pop edi + ret +_inflate_fast endp + +_TEXT ends +end diff --git a/zlib/zlib/contrib/masmx86/match686.asm b/zlib/zlib/contrib/masmx86/match686.asm new file mode 100644 index 00000000..3b09212f --- /dev/null +++ b/zlib/zlib/contrib/masmx86/match686.asm @@ -0,0 +1,479 @@ +; match686.asm -- Asm portion of the optimized longest_match for 32 bits x86 +; Copyright (C) 1995-1996 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; File written by Gilles Vollant, by converting match686.S from Brian Raiter +; for MASM. This is as assembly version of longest_match +; from Jean-loup Gailly in deflate.c +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; For Visual C++ 4.x and higher and ML 6.x and higher +; ml.exe is distributed in +; http://www.microsoft.com/downloads/details.aspx?FamilyID=7a1c9da0-0510-44a2-b042-7ef370530c64 +; +; this file contain two implementation of longest_match +; +; this longest_match was written by Brian raiter (1998), optimized for Pentium Pro +; (and the faster known version of match_init on modern Core 2 Duo and AMD Phenom) +; +; for using an assembly version of longest_match, you need define ASMV in project +; +; compile the asm file running +; ml /coff /Zi /c /Flmatch686.lst match686.asm +; and do not include match686.obj in your project +; +; note: contrib of zLib 1.2.3 and earlier contained both a deprecated version for +; Pentium (prior Pentium Pro) and this version for Pentium Pro and modern processor +; with autoselect (with cpu detection code) +; if you want support the old pentium optimization, you can still use these version +; +; this file is not optimized for old pentium, but it compatible with all x86 32 bits +; processor (starting 80386) +; +; +; see below : zlib1222add must be adjuster if you use a zlib version < 1.2.2.2 + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; /* current match */ + + NbStack equ 76 + cur_match equ dword ptr[esp+NbStack-0] + str_s equ dword ptr[esp+NbStack-4] +; 5 dword on top (ret,ebp,esi,edi,ebx) + adrret equ dword ptr[esp+NbStack-8] + pushebp equ dword ptr[esp+NbStack-12] + pushedi equ dword ptr[esp+NbStack-16] + pushesi equ dword ptr[esp+NbStack-20] + pushebx equ dword ptr[esp+NbStack-24] + + chain_length equ dword ptr [esp+NbStack-28] + limit equ dword ptr [esp+NbStack-32] + best_len equ dword ptr [esp+NbStack-36] + window equ dword ptr [esp+NbStack-40] + prev equ dword ptr [esp+NbStack-44] + scan_start equ word ptr [esp+NbStack-48] + wmask equ dword ptr [esp+NbStack-52] + match_start_ptr equ dword ptr [esp+NbStack-56] + nice_match equ dword ptr [esp+NbStack-60] + scan equ dword ptr [esp+NbStack-64] + + windowlen equ dword ptr [esp+NbStack-68] + match_start equ dword ptr [esp+NbStack-72] + strend equ dword ptr [esp+NbStack-76] + NbStackAdd equ (NbStack-24) + + .386p + + name gvmatch + .MODEL FLAT + + + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + + zlib1222add equ 8 + +; Note : these value are good with a 8 bytes boundary pack structure + dep_chain_length equ 74h+zlib1222add + dep_window equ 30h+zlib1222add + dep_strstart equ 64h+zlib1222add + dep_prev_length equ 70h+zlib1222add + dep_nice_match equ 88h+zlib1222add + dep_w_size equ 24h+zlib1222add + dep_prev equ 38h+zlib1222add + dep_w_mask equ 2ch+zlib1222add + dep_good_match equ 84h+zlib1222add + dep_match_start equ 68h+zlib1222add + dep_lookahead equ 6ch+zlib1222add + + +_TEXT segment + +IFDEF NOUNDERLINE + public longest_match + public match_init +ELSE + public _longest_match + public _match_init +ENDIF + + MAX_MATCH equ 258 + MIN_MATCH equ 3 + MIN_LOOKAHEAD equ (MAX_MATCH+MIN_MATCH+1) + + + +MAX_MATCH equ 258 +MIN_MATCH equ 3 +MIN_LOOKAHEAD equ (MAX_MATCH + MIN_MATCH + 1) +MAX_MATCH_8_ equ ((MAX_MATCH + 7) AND 0FFF0h) + + +;;; stack frame offsets + +chainlenwmask equ esp + 0 ; high word: current chain len + ; low word: s->wmask +window equ esp + 4 ; local copy of s->window +windowbestlen equ esp + 8 ; s->window + bestlen +scanstart equ esp + 16 ; first two bytes of string +scanend equ esp + 12 ; last two bytes of string +scanalign equ esp + 20 ; dword-misalignment of string +nicematch equ esp + 24 ; a good enough match size +bestlen equ esp + 28 ; size of best match so far +scan equ esp + 32 ; ptr to string wanting match + +LocalVarsSize equ 36 +; saved ebx byte esp + 36 +; saved edi byte esp + 40 +; saved esi byte esp + 44 +; saved ebp byte esp + 48 +; return address byte esp + 52 +deflatestate equ esp + 56 ; the function arguments +curmatch equ esp + 60 + +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +dsWSize equ 36+zlib1222add +dsWMask equ 44+zlib1222add +dsWindow equ 48+zlib1222add +dsPrev equ 56+zlib1222add +dsMatchLen equ 88+zlib1222add +dsPrevMatch equ 92+zlib1222add +dsStrStart equ 100+zlib1222add +dsMatchStart equ 104+zlib1222add +dsLookahead equ 108+zlib1222add +dsPrevLen equ 112+zlib1222add +dsMaxChainLen equ 116+zlib1222add +dsGoodMatch equ 132+zlib1222add +dsNiceMatch equ 136+zlib1222add + + +;;; match686.asm -- Pentium-Pro-optimized version of longest_match() +;;; Written for zlib 1.1.2 +;;; Copyright (C) 1998 Brian Raiter +;;; You can look at http://www.muppetlabs.com/~breadbox/software/assembly.html +;;; +;; +;; This software is provided 'as-is', without any express or implied +;; warranty. In no event will the authors be held liable for any damages +;; arising from the use of this software. +;; +;; Permission is granted to anyone to use this software for any purpose, +;; including commercial applications, and to alter it and redistribute it +;; freely, subject to the following restrictions: +;; +;; 1. The origin of this software must not be misrepresented; you must not +;; claim that you wrote the original software. If you use this software +;; in a product, an acknowledgment in the product documentation would be +;; appreciated but is not required. +;; 2. Altered source versions must be plainly marked as such, and must not be +;; misrepresented as being the original software +;; 3. This notice may not be removed or altered from any source distribution. +;; + +;GLOBAL _longest_match, _match_init + + +;SECTION .text + +;;; uInt longest_match(deflate_state *deflatestate, IPos curmatch) + +;_longest_match: + IFDEF NOUNDERLINE + longest_match proc near + ELSE + _longest_match proc near + ENDIF +.FPO (9, 4, 0, 0, 1, 0) + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + push ebp + push edi + push esi + push ebx + sub esp, LocalVarsSize + +;;; Retrieve the function arguments. ecx will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + + mov edx, [deflatestate] + mov ecx, [curmatch] + +;;; uInt wmask = s->w_mask; +;;; unsigned chain_length = s->max_chain_length; +;;; if (s->prev_length >= s->good_match) { +;;; chain_length >>= 2; +;;; } + + mov eax, [edx + dsPrevLen] + mov ebx, [edx + dsGoodMatch] + cmp eax, ebx + mov eax, [edx + dsWMask] + mov ebx, [edx + dsMaxChainLen] + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +;;; chainlen is decremented once beforehand so that the function can +;;; use the sign flag instead of the zero flag for the exit test. +;;; It is then shifted into the high word, to make room for the wmask +;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + mov [chainlenwmask], ebx + +;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + mov eax, [edx + dsNiceMatch] + mov ebx, [edx + dsLookahead] + cmp ebx, eax + jl LookaheadLess + mov ebx, eax +LookaheadLess: mov [nicematch], ebx + +;;; register Bytef *scan = s->window + s->strstart; + + mov esi, [edx + dsWindow] + mov [window], esi + mov ebp, [edx + dsStrStart] + lea edi, [esi + ebp] + mov [scan], edi + +;;; Determine how many bytes the scan ptr is off from being +;;; dword-aligned. + + mov eax, edi + neg eax + and eax, 3 + mov [scanalign], eax + +;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +;;; s->strstart - (IPos)MAX_DIST(s) : NIL; + + mov eax, [edx + dsWSize] + sub eax, MIN_LOOKAHEAD + sub ebp, eax + jg LimitPositive + xor ebp, ebp +LimitPositive: + +;;; int best_len = s->prev_length; + + mov eax, [edx + dsPrevLen] + mov [bestlen], eax + +;;; Store the sum of s->window + best_len in esi locally, and in esi. + + add esi, eax + mov [windowbestlen], esi + +;;; register ush scan_start = *(ushf*)scan; +;;; register ush scan_end = *(ushf*)(scan+best_len-1); +;;; Posf *prev = s->prev; + + movzx ebx, word ptr [edi] + mov [scanstart], ebx + movzx ebx, word ptr [edi + eax - 1] + mov [scanend], ebx + mov edi, [edx + dsPrev] + +;;; Jump into the main loop. + + mov edx, [chainlenwmask] + jmp short LoopEntry + +align 4 + +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; ecx = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit + +LookupLoop: + and ecx, edx + movzx ecx, word ptr [edi + ecx*2] + cmp ecx, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow +LoopEntry: movzx eax, word ptr [esi + ecx - 1] + cmp eax, ebx + jnz LookupLoop + mov eax, [window] + movzx eax, word ptr [eax + ecx] + cmp eax, [scanstart] + jnz LookupLoop + +;;; Store the current value of chainlen. + + mov [chainlenwmask], edx + +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). + + mov esi, [window] + mov edi, [scan] + add esi, ecx + mov eax, [scanalign] + mov edx, 0fffffef8h; -(MAX_MATCH_8) + lea edi, [edi + eax + 0108h] ;MAX_MATCH_8] + lea esi, [esi + eax + 0108h] ;MAX_MATCH_8] + +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust edx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (esi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. + +LoopCmps: + mov eax, [esi + edx] + xor eax, [edi + edx] + jnz LeaveLoopCmps + mov eax, [esi + edx + 4] + xor eax, [edi + edx + 4] + jnz LeaveLoopCmps4 + add edx, 8 + jnz LoopCmps + jmp short LenMaximum +LeaveLoopCmps4: add edx, 4 +LeaveLoopCmps: test eax, 0000FFFFh + jnz LenLower + add edx, 2 + shr eax, 16 +LenLower: sub al, 1 + adc edx, 0 + +;;; Calculate the length of the match. If it is longer than MAX_MATCH, +;;; then automatically accept it as the best possible match and leave. + + lea eax, [edi + edx] + mov edi, [scan] + sub eax, edi + cmp eax, MAX_MATCH + jge LenMaximum + +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. + + mov edx, [deflatestate] + mov ebx, [bestlen] + cmp eax, ebx + jg LongerMatch + mov esi, [windowbestlen] + mov edi, [edx + dsPrev] + mov ebx, [scanend] + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); + +LongerMatch: mov ebx, [nicematch] + mov [bestlen], eax + mov [edx + dsMatchStart], ecx + cmp eax, ebx + jge LeaveNow + mov esi, [window] + add esi, eax + mov [windowbestlen], esi + movzx ebx, word ptr [edi + eax - 1] + mov edi, [edx + dsPrev] + mov [scanend], ebx + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; Accept the current string, with the maximum possible length. + +LenMaximum: mov edx, [deflatestate] + mov dword ptr [bestlen], MAX_MATCH + mov [edx + dsMatchStart], ecx + +;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +;;; return s->lookahead; + +LeaveNow: + mov edx, [deflatestate] + mov ebx, [bestlen] + mov eax, [edx + dsLookahead] + cmp ebx, eax + jg LookaheadRet + mov eax, ebx +LookaheadRet: + +;;; Restore the stack and return from whence we came. + + add esp, LocalVarsSize + pop ebx + pop esi + pop edi + pop ebp + + ret +; please don't remove this string ! +; Your can freely use match686 in any free or commercial app if you don't remove the string in the binary! + db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998",0dh,0ah + + + IFDEF NOUNDERLINE + longest_match endp + ELSE + _longest_match endp + ENDIF + + IFDEF NOUNDERLINE + match_init proc near + ret + match_init endp + ELSE + _match_init proc near + ret + _match_init endp + ENDIF + + +_TEXT ends +end diff --git a/zlib/zlib/contrib/masmx86/readme.txt b/zlib/zlib/contrib/masmx86/readme.txt new file mode 100644 index 00000000..3f888867 --- /dev/null +++ b/zlib/zlib/contrib/masmx86/readme.txt @@ -0,0 +1,27 @@ + +Summary +------- +This directory contains ASM implementations of the functions +longest_match() and inflate_fast(). + + +Use instructions +---------------- +Assemble using MASM, and copy the object files into the zlib source +directory, then run the appropriate makefile, as suggested below. You can +donwload MASM from here: + + http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=7a1c9da0-0510-44a2-b042-7ef370530c64 + +You can also get objects files here: + + http://www.winimage.com/zLibDll/zlib124_masm_obj.zip + +Build instructions +------------------ +* With Microsoft C and MASM: +nmake -f win32/Makefile.msc LOC="-DASMV -DASMINF" OBJA="match686.obj inffas32.obj" + +* With Borland C and TASM: +make -f win32/Makefile.bor LOCAL_ZLIB="-DASMV -DASMINF" OBJA="match686.obj inffas32.obj" OBJPA="+match686c.obj+match686.obj+inffas32.obj" + diff --git a/zlib/zlib/contrib/minizip/Makefile b/zlib/zlib/contrib/minizip/Makefile new file mode 100644 index 00000000..84eaad20 --- /dev/null +++ b/zlib/zlib/contrib/minizip/Makefile @@ -0,0 +1,25 @@ +CC=cc +CFLAGS=-O -I../.. + +UNZ_OBJS = miniunz.o unzip.o ioapi.o ../../libz.a +ZIP_OBJS = minizip.o zip.o ioapi.o ../../libz.a + +.c.o: + $(CC) -c $(CFLAGS) $*.c + +all: miniunz minizip + +miniunz: $(UNZ_OBJS) + $(CC) $(CFLAGS) -o $@ $(UNZ_OBJS) + +minizip: $(ZIP_OBJS) + $(CC) $(CFLAGS) -o $@ $(ZIP_OBJS) + +test: miniunz minizip + ./minizip test readme.txt + ./miniunz -l test.zip + mv readme.txt readme.old + ./miniunz test.zip + +clean: + /bin/rm -f *.o *~ minizip miniunz diff --git a/zlib/zlib/contrib/minizip/Makefile.am b/zlib/zlib/contrib/minizip/Makefile.am new file mode 100644 index 00000000..d343011e --- /dev/null +++ b/zlib/zlib/contrib/minizip/Makefile.am @@ -0,0 +1,45 @@ +lib_LTLIBRARIES = libminizip.la + +if COND_DEMOS +bin_PROGRAMS = miniunzip minizip +endif + +zlib_top_srcdir = $(top_srcdir)/../.. +zlib_top_builddir = $(top_builddir)/../.. + +AM_CPPFLAGS = -I$(zlib_top_srcdir) +AM_LDFLAGS = -L$(zlib_top_builddir) + +if WIN32 +iowin32_src = iowin32.c +iowin32_h = iowin32.h +endif + +libminizip_la_SOURCES = \ + ioapi.c \ + mztools.c \ + unzip.c \ + zip.c \ + ${iowin32_src} + +libminizip_la_LDFLAGS = $(AM_LDFLAGS) -version-info 1:0:0 -lz + +minizip_includedir = $(includedir)/minizip +minizip_include_HEADERS = \ + crypt.h \ + ioapi.h \ + mztools.h \ + unzip.h \ + zip.h \ + ${iowin32_h} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = minizip.pc + +EXTRA_PROGRAMS = miniunzip minizip + +miniunzip_SOURCES = miniunz.c +miniunzip_LDADD = libminizip.la + +minizip_SOURCES = minizip.c +minizip_LDADD = libminizip.la -lz diff --git a/zlib/zlib/contrib/minizip/MiniZip64_Changes.txt b/zlib/zlib/contrib/minizip/MiniZip64_Changes.txt new file mode 100644 index 00000000..13a1bd91 --- /dev/null +++ b/zlib/zlib/contrib/minizip/MiniZip64_Changes.txt @@ -0,0 +1,6 @@ + +MiniZip 1.1 was derrived from MiniZip at version 1.01f + +Change in 1.0 (Okt 2009) + - **TODO - Add history** + diff --git a/zlib/zlib/contrib/minizip/MiniZip64_info.txt b/zlib/zlib/contrib/minizip/MiniZip64_info.txt new file mode 100644 index 00000000..57d71524 --- /dev/null +++ b/zlib/zlib/contrib/minizip/MiniZip64_info.txt @@ -0,0 +1,74 @@ +MiniZip - Copyright (c) 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson + +Introduction +--------------------- +MiniZip 1.1 is built from MiniZip 1.0 by Gilles Vollant ( http://www.winimage.com/zLibDll/minizip.html ) + +When adding ZIP64 support into minizip it would result into risk of breaking compatibility with minizip 1.0. +All possible work was done for compatibility. + + +Background +--------------------- +When adding ZIP64 support Mathias Svensson found that Even Rouault have added ZIP64 +support for unzip.c into minizip for a open source project called gdal ( http://www.gdal.org/ ) + +That was used as a starting point. And after that ZIP64 support was added to zip.c +some refactoring and code cleanup was also done. + + +Changed from MiniZip 1.0 to MiniZip 1.1 +--------------------------------------- +* Added ZIP64 support for unzip ( by Even Rouault ) +* Added ZIP64 support for zip ( by Mathias Svensson ) +* Reverted some changed that Even Rouault did. +* Bunch of patches received from Gulles Vollant that he received for MiniZip from various users. +* Added unzip patch for BZIP Compression method (patch create by Daniel Borca) +* Added BZIP Compress method for zip +* Did some refactoring and code cleanup + + +Credits + + Gilles Vollant - Original MiniZip author + Even Rouault - ZIP64 unzip Support + Daniel Borca - BZip Compression method support in unzip + Mathias Svensson - ZIP64 zip support + Mathias Svensson - BZip Compression method support in zip + + Resources + + ZipLayout http://result42.com/projects/ZipFileLayout + Command line tool for Windows that shows the layout and information of the headers in a zip archive. + Used when debugging and validating the creation of zip files using MiniZip64 + + + ZIP App Note http://www.pkware.com/documents/casestudies/APPNOTE.TXT + Zip File specification + + +Notes. + * To be able to use BZip compression method in zip64.c or unzip64.c the BZIP2 lib is needed and HAVE_BZIP2 need to be defined. + +License +---------------------------------------------------------- + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +---------------------------------------------------------- + diff --git a/zlib/zlib/contrib/minizip/configure.ac b/zlib/zlib/contrib/minizip/configure.ac new file mode 100644 index 00000000..827a4e05 --- /dev/null +++ b/zlib/zlib/contrib/minizip/configure.ac @@ -0,0 +1,32 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_INIT([minizip], [1.2.8], [bugzilla.redhat.com]) +AC_CONFIG_SRCDIR([minizip.c]) +AM_INIT_AUTOMAKE([foreign]) +LT_INIT + +AC_MSG_CHECKING([whether to build example programs]) +AC_ARG_ENABLE([demos], AC_HELP_STRING([--enable-demos], [build example programs])) +AM_CONDITIONAL([COND_DEMOS], [test "$enable_demos" = yes]) +if test "$enable_demos" = yes +then + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +case "${host}" in + *-mingw* | mingw*) + WIN32="yes" + ;; + *) + ;; +esac +AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"]) + + +AC_SUBST([HAVE_UNISTD_H], [0]) +AC_CHECK_HEADER([unistd.h], [HAVE_UNISTD_H=1], []) +AC_CONFIG_FILES([Makefile minizip.pc]) +AC_OUTPUT diff --git a/zlib/zlib/contrib/minizip/crypt.h b/zlib/zlib/contrib/minizip/crypt.h new file mode 100644 index 00000000..1e9e8200 --- /dev/null +++ b/zlib/zlib/contrib/minizip/crypt.h @@ -0,0 +1,131 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab) +{ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const z_crc_t* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(const char* passwd, /* password string */ + unsigned char* buf, /* where to write header */ + int bufSize, + unsigned long* pkeys, + const z_crc_t* pcrc_32_tab, + unsigned long crcForCrypting) +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/zlib/zlib/contrib/minizip/ioapi.c b/zlib/zlib/contrib/minizip/ioapi.c new file mode 100644 index 00000000..7f5c191b --- /dev/null +++ b/zlib/zlib/contrib/minizip/ioapi.c @@ -0,0 +1,247 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS))) + #define _CRT_SECURE_NO_WARNINGS +#endif + +#if defined(__APPLE__) || defined(IOAPI_NO_64) +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + +#include "ioapi.h" + +voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode) +{ + if (pfilefunc->zfile_func64.zopen64_file != NULL) + return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode); + else + { + return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode); + } +} + +long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); + else + { + uLong offsetTruncated = (uLong)offset; + if (offsetTruncated != offset) + return -1; + else + return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); + } +} + +ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); + else + { + uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); + if ((tell_uLong) == MAXU32) + return (ZPOS64_T)-1; + else + return tell_uLong; + } +} + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) +{ + p_filefunc64_32->zfile_func64.zopen64_file = NULL; + p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; + p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; + p_filefunc64_32->zfile_func64.ztell64_file = NULL; + p_filefunc64_32->zfile_func64.zseek64_file = NULL; + p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; + p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; + p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; +} + + + +static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode)); +static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size)); +static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream)); +static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream)); +static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); + +static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + +static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = FOPEN_FUNC((const char*)filename, mode_fopen); + return file; +} + + +static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + + +static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) +{ + ZPOS64_T ret; + ret = FTELLO_FUNC((FILE *)stream); + return ret; +} + +static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + if (fseek((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + return ret; +} + +static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + + if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + + return ret; +} + + +static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = fopen64_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell64_file = ftell64_file_func; + pzlib_filefunc_def->zseek64_file = fseek64_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/zlib/zlib/contrib/minizip/ioapi.h b/zlib/zlib/contrib/minizip/ioapi.h new file mode 100644 index 00000000..8dcbdb06 --- /dev/null +++ b/zlib/zlib/contrib/minizip/ioapi.h @@ -0,0 +1,208 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + + Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) + Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. + More if/def section may be needed to support other platforms + Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. + (but you should use iowin32.c for windows instead) + +*/ + +#ifndef _ZLIBIOAPI64_H +#define _ZLIBIOAPI64_H + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + + // Linux needs this to support file operation on files larger then 4+GB + // But might need better if/def to select just the platforms that needs them. + + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif + +#endif + +#include +#include +#include "zlib.h" + +#if defined(USE_FILE32API) +#define fopen64 fopen +#define ftello64 ftell +#define fseeko64 fseek +#else +#ifdef __FreeBSD__ +#define fopen64 fopen +#define ftello64 ftello +#define fseeko64 fseeko +#endif +#ifdef _MSC_VER + #define fopen64 fopen + #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) + #define ftello64 _ftelli64 + #define fseeko64 _fseeki64 + #else // old MSC + #define ftello64 ftell + #define fseeko64 fseek + #endif +#endif +#endif + +/* +#ifndef ZPOS64_T + #ifdef _WIN32 + #define ZPOS64_T fpos_t + #else + #include + #define ZPOS64_T uint64_t + #endif +#endif +*/ + +#ifdef HAVE_MINIZIP64_CONF_H +#include "mz64conf.h" +#endif + +/* a type choosen by DEFINE */ +#ifdef HAVE_64BIT_INT_CUSTOM +typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; +#else +#ifdef HAS_STDINT_H +#include "stdint.h" +typedef uint64_t ZPOS64_T; +#else + +/* Maximum unsigned 32-bit value used as placeholder for zip64 */ +#define MAXU32 0xffffffff + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 ZPOS64_T; +#else +typedef unsigned long long int ZPOS64_T; +#endif +#endif +#endif + + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) + #define ZCALLBACK CALLBACK + #else + #define ZCALLBACK + #endif +#endif + + + + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); + + +/* here is the "old" 32 bits structure structure */ +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + +typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode)); + +typedef struct zlib_filefunc64_def_s +{ + open64_file_func zopen64_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell64_file_func ztell64_file; + seek64_file_func zseek64_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc64_def; + +void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +/* now internal definition, only for zip.c and unzip.h */ +typedef struct zlib_filefunc64_32_def_s +{ + zlib_filefunc64_def zfile_func64; + open_file_func zopen32_file; + tell_file_func ztell32_file; + seek_file_func zseek32_file; +} zlib_filefunc64_32_def; + + +#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +//#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) +//#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) +#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) + +voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)); +long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); +ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32); + +#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) +#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) +#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zlib/zlib/contrib/minizip/iowin32.c b/zlib/zlib/contrib/minizip/iowin32.c new file mode 100644 index 00000000..a46d96c7 --- /dev/null +++ b/zlib/zlib/contrib/minizip/iowin32.c @@ -0,0 +1,461 @@ +/* iowin32.c -- IO base function header for compress/uncompress .zip + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#include + +#include "zlib.h" +#include "ioapi.h" +#include "iowin32.h" + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE (0xFFFFFFFF) +#endif + +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + + +#if defined(WINAPI_FAMILY_PARTITION) && (!(defined(IOWIN32_USING_WINRT_API))) +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +#define IOWIN32_USING_WINRT_API 1 +#endif +#endif + +voidpf ZCALLBACK win32_open_file_func OF((voidpf opaque, const char* filename, int mode)); +uLong ZCALLBACK win32_read_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +uLong ZCALLBACK win32_write_file_func OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +ZPOS64_T ZCALLBACK win32_tell64_file_func OF((voidpf opaque, voidpf stream)); +long ZCALLBACK win32_seek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +int ZCALLBACK win32_close_file_func OF((voidpf opaque, voidpf stream)); +int ZCALLBACK win32_error_file_func OF((voidpf opaque, voidpf stream)); + +typedef struct +{ + HANDLE hf; + int error; +} WIN32FILE_IOWIN; + + +static void win32_translate_open_mode(int mode, + DWORD* lpdwDesiredAccess, + DWORD* lpdwCreationDisposition, + DWORD* lpdwShareMode, + DWORD* lpdwFlagsAndAttributes) +{ + *lpdwDesiredAccess = *lpdwShareMode = *lpdwFlagsAndAttributes = *lpdwCreationDisposition = 0; + + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + { + *lpdwDesiredAccess = GENERIC_READ; + *lpdwCreationDisposition = OPEN_EXISTING; + *lpdwShareMode = FILE_SHARE_READ; + } + else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + { + *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + *lpdwCreationDisposition = OPEN_EXISTING; + } + else if (mode & ZLIB_FILEFUNC_MODE_CREATE) + { + *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + *lpdwCreationDisposition = CREATE_ALWAYS; + } +} + +static voidpf win32_build_iowin(HANDLE hFile) +{ + voidpf ret=NULL; + + if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + WIN32FILE_IOWIN w32fiow; + w32fiow.hf = hFile; + w32fiow.error = 0; + ret = malloc(sizeof(WIN32FILE_IOWIN)); + + if (ret==NULL) + CloseHandle(hFile); + else + *((WIN32FILE_IOWIN*)ret) = w32fiow; + } + return ret; +} + +voidpf ZCALLBACK win32_open64_file_func (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API +#ifdef UNICODE + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#endif +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open64_file_funcA (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFileA((LPCSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open64_file_funcW (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCWSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition,NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFileW((LPCWSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open_file_func (voidpf opaque,const char* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API +#ifdef UNICODE + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#endif +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +uLong ZCALLBACK win32_read_file_func (voidpf opaque, voidpf stream, void* buf,uLong size) +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + + if (hFile != NULL) + { + if (!ReadFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + } + + return ret; +} + + +uLong ZCALLBACK win32_write_file_func (voidpf opaque,voidpf stream,const void* buf,uLong size) +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + + if (hFile != NULL) + { + if (!WriteFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + } + + return ret; +} + +static BOOL MySetFilePointerEx(HANDLE hFile, LARGE_INTEGER pos, LARGE_INTEGER *newPos, DWORD dwMoveMethod) +{ +#ifdef IOWIN32_USING_WINRT_API + return SetFilePointerEx(hFile, pos, newPos, dwMoveMethod); +#else + LONG lHigh = pos.HighPart; + DWORD dwNewPos = SetFilePointer(hFile, pos.LowPart, &lHigh, FILE_CURRENT); + BOOL fOk = TRUE; + if (dwNewPos == 0xFFFFFFFF) + if (GetLastError() != NO_ERROR) + fOk = FALSE; + if ((newPos != NULL) && (fOk)) + { + newPos->LowPart = dwNewPos; + newPos->HighPart = lHigh; + } + return fOk; +#endif +} + +long ZCALLBACK win32_tell_file_func (voidpf opaque,voidpf stream) +{ + long ret=-1; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + LARGE_INTEGER pos; + pos.QuadPart = 0; + + if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=(long)pos.LowPart; + } + return ret; +} + +ZPOS64_T ZCALLBACK win32_tell64_file_func (voidpf opaque, voidpf stream) +{ + ZPOS64_T ret= (ZPOS64_T)-1; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream)->hf; + + if (hFile) + { + LARGE_INTEGER pos; + pos.QuadPart = 0; + + if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = (ZPOS64_T)-1; + } + else + ret=pos.QuadPart; + } + return ret; +} + + +long ZCALLBACK win32_seek_file_func (voidpf opaque,voidpf stream,uLong offset,int origin) +{ + DWORD dwMoveMethod=0xFFFFFFFF; + HANDLE hFile = NULL; + + long ret=-1; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + dwMoveMethod = FILE_CURRENT; + break; + case ZLIB_FILEFUNC_SEEK_END : + dwMoveMethod = FILE_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + dwMoveMethod = FILE_BEGIN; + break; + default: return -1; + } + + if (hFile != NULL) + { + LARGE_INTEGER pos; + pos.QuadPart = offset; + if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=0; + } + return ret; +} + +long ZCALLBACK win32_seek64_file_func (voidpf opaque, voidpf stream,ZPOS64_T offset,int origin) +{ + DWORD dwMoveMethod=0xFFFFFFFF; + HANDLE hFile = NULL; + long ret=-1; + + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream)->hf; + + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + dwMoveMethod = FILE_CURRENT; + break; + case ZLIB_FILEFUNC_SEEK_END : + dwMoveMethod = FILE_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + dwMoveMethod = FILE_BEGIN; + break; + default: return -1; + } + + if (hFile) + { + LARGE_INTEGER pos; + pos.QuadPart = offset; + if (!MySetFilePointerEx(hFile, pos, NULL, FILE_CURRENT)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=0; + } + return ret; +} + +int ZCALLBACK win32_close_file_func (voidpf opaque, voidpf stream) +{ + int ret=-1; + + if (stream!=NULL) + { + HANDLE hFile; + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + CloseHandle(hFile); + ret=0; + } + free(stream); + } + return ret; +} + +int ZCALLBACK win32_error_file_func (voidpf opaque,voidpf stream) +{ + int ret=-1; + if (stream!=NULL) + { + ret = ((WIN32FILE_IOWIN*)stream) -> error; + } + return ret; +} + +void fill_win32_filefunc (zlib_filefunc_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen_file = win32_open_file_func; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell_file = win32_tell_file_func; + pzlib_filefunc_def->zseek_file = win32_seek_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_win32_filefunc64(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_func; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + + +void fill_win32_filefunc64A(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_funcA; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + + +void fill_win32_filefunc64W(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_funcW; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/zlib/zlib/contrib/minizip/iowin32.h b/zlib/zlib/contrib/minizip/iowin32.h new file mode 100644 index 00000000..0ca0969a --- /dev/null +++ b/zlib/zlib/contrib/minizip/iowin32.h @@ -0,0 +1,28 @@ +/* iowin32.h -- IO base function header for compress/uncompress .zip + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void fill_win32_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); +void fill_win32_filefunc64 OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_win32_filefunc64A OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_win32_filefunc64W OF((zlib_filefunc64_def* pzlib_filefunc_def)); + +#ifdef __cplusplus +} +#endif diff --git a/zlib/zlib/contrib/minizip/make_vms.com b/zlib/zlib/contrib/minizip/make_vms.com new file mode 100644 index 00000000..9ac13a98 --- /dev/null +++ b/zlib/zlib/contrib/minizip/make_vms.com @@ -0,0 +1,25 @@ +$ if f$search("ioapi.h_orig") .eqs. "" then copy ioapi.h ioapi.h_orig +$ open/write zdef vmsdefs.h +$ copy sys$input: zdef +$ deck +#define unix +#define fill_zlib_filefunc64_32_def_from_filefunc32 fillzffunc64from +#define Write_Zip64EndOfCentralDirectoryLocator Write_Zip64EoDLocator +#define Write_Zip64EndOfCentralDirectoryRecord Write_Zip64EoDRecord +#define Write_EndOfCentralDirectoryRecord Write_EoDRecord +$ eod +$ close zdef +$ copy vmsdefs.h,ioapi.h_orig ioapi.h +$ cc/include=[--]/prefix=all ioapi.c +$ cc/include=[--]/prefix=all miniunz.c +$ cc/include=[--]/prefix=all unzip.c +$ cc/include=[--]/prefix=all minizip.c +$ cc/include=[--]/prefix=all zip.c +$ link miniunz,unzip,ioapi,[--]libz.olb/lib +$ link minizip,zip,ioapi,[--]libz.olb/lib +$ mcr []minizip test minizip_info.txt +$ mcr []miniunz -l test.zip +$ rename minizip_info.txt; minizip_info.txt_old +$ mcr []miniunz test.zip +$ delete test.zip;* +$exit diff --git a/zlib/zlib/contrib/minizip/miniunz.c b/zlib/zlib/contrib/minizip/miniunz.c new file mode 100644 index 00000000..3d65401b --- /dev/null +++ b/zlib/zlib/contrib/minizip/miniunz.c @@ -0,0 +1,660 @@ +/* + miniunz.c + Version 1.1, February 14h, 2010 + sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) +*/ + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif +#endif + +#ifdef __APPLE__ +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +#endif + + +#include "unzip.h" + +#define CASESENSITIVITY (0) +#define WRITEBUFFERSIZE (8192) +#define MAXFILENAME (256) + +#ifdef _WIN32 +#define USEWIN32IOAPI +#include "iowin32.h" +#endif +/* + mini unzip, demo of unzip package + + usage : + Usage : miniunz [-exvlo] file.zip [file_to_extract] [-d extractdir] + + list the file in the zipfile, and print the content of FILE_ID.ZIP or README.TXT + if it exists +*/ + + +/* change_file_date : change the date/time of a file + filename : the filename of the file where date/time must be modified + dosdate : the new date at the MSDos format (4 bytes) + tmu_date : the SAME new date at the tm_unz format */ +void change_file_date(filename,dosdate,tmu_date) + const char *filename; + uLong dosdate; + tm_unz tmu_date; +{ +#ifdef _WIN32 + HANDLE hFile; + FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite; + + hFile = CreateFileA(filename,GENERIC_READ | GENERIC_WRITE, + 0,NULL,OPEN_EXISTING,0,NULL); + GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite); + DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal); + LocalFileTimeToFileTime(&ftLocal,&ftm); + SetFileTime(hFile,&ftm,&ftLastAcc,&ftm); + CloseHandle(hFile); +#else +#ifdef unix || __APPLE__ + struct utimbuf ut; + struct tm newdate; + newdate.tm_sec = tmu_date.tm_sec; + newdate.tm_min=tmu_date.tm_min; + newdate.tm_hour=tmu_date.tm_hour; + newdate.tm_mday=tmu_date.tm_mday; + newdate.tm_mon=tmu_date.tm_mon; + if (tmu_date.tm_year > 1900) + newdate.tm_year=tmu_date.tm_year - 1900; + else + newdate.tm_year=tmu_date.tm_year ; + newdate.tm_isdst=-1; + + ut.actime=ut.modtime=mktime(&newdate); + utime(filename,&ut); +#endif +#endif +} + + +/* mymkdir and change_file_date are not 100 % portable + As I don't know well Unix, I wait feedback for the unix portion */ + +int mymkdir(dirname) + const char* dirname; +{ + int ret=0; +#ifdef _WIN32 + ret = _mkdir(dirname); +#elif unix + ret = mkdir (dirname,0775); +#elif __APPLE__ + ret = mkdir (dirname,0775); +#endif + return ret; +} + +int makedir (newdir) + char *newdir; +{ + char *buffer ; + char *p; + int len = (int)strlen(newdir); + + if (len <= 0) + return 0; + + buffer = (char*)malloc(len+1); + if (buffer==NULL) + { + printf("Error allocating memory\n"); + return UNZ_INTERNALERROR; + } + strcpy(buffer,newdir); + + if (buffer[len-1] == '/') { + buffer[len-1] = '\0'; + } + if (mymkdir(buffer) == 0) + { + free(buffer); + return 1; + } + + p = buffer+1; + while (1) + { + char hold; + + while(*p && *p != '\\' && *p != '/') + p++; + hold = *p; + *p = 0; + if ((mymkdir(buffer) == -1) && (errno == ENOENT)) + { + printf("couldn't create directory %s\n",buffer); + free(buffer); + return 0; + } + if (hold == 0) + break; + *p++ = hold; + } + free(buffer); + return 1; +} + +void do_banner() +{ + printf("MiniUnz 1.01b, demo of zLib + Unz package written by Gilles Vollant\n"); + printf("more info at http://www.winimage.com/zLibDll/unzip.html\n\n"); +} + +void do_help() +{ + printf("Usage : miniunz [-e] [-x] [-v] [-l] [-o] [-p password] file.zip [file_to_extr.] [-d extractdir]\n\n" \ + " -e Extract without pathname (junk paths)\n" \ + " -x Extract with pathname\n" \ + " -v list files\n" \ + " -l list files\n" \ + " -d directory to extract into\n" \ + " -o overwrite files without prompting\n" \ + " -p extract crypted file using password\n\n"); +} + +void Display64BitsSize(ZPOS64_T n, int size_char) +{ + /* to avoid compatibility problem , we do here the conversion */ + char number[21]; + int offset=19; + int pos_string = 19; + number[20]=0; + for (;;) { + number[offset]=(char)((n%10)+'0'); + if (number[offset] != '0') + pos_string=offset; + n/=10; + if (offset==0) + break; + offset--; + } + { + int size_display_string = 19-pos_string; + while (size_char > size_display_string) + { + size_char--; + printf(" "); + } + } + + printf("%s",&number[pos_string]); +} + +int do_list(uf) + unzFile uf; +{ + uLong i; + unz_global_info64 gi; + int err; + + err = unzGetGlobalInfo64(uf,&gi); + if (err!=UNZ_OK) + printf("error %d with zipfile in unzGetGlobalInfo \n",err); + printf(" Length Method Size Ratio Date Time CRC-32 Name\n"); + printf(" ------ ------ ---- ----- ---- ---- ------ ----\n"); + for (i=0;i0) + ratio = (uLong)((file_info.compressed_size*100)/file_info.uncompressed_size); + + /* display a '*' if the file is crypted */ + if ((file_info.flag & 1) != 0) + charCrypt='*'; + + if (file_info.compression_method==0) + string_method="Stored"; + else + if (file_info.compression_method==Z_DEFLATED) + { + uInt iLevel=(uInt)((file_info.flag & 0x6)/2); + if (iLevel==0) + string_method="Defl:N"; + else if (iLevel==1) + string_method="Defl:X"; + else if ((iLevel==2) || (iLevel==3)) + string_method="Defl:F"; /* 2:fast , 3 : extra fast*/ + } + else + if (file_info.compression_method==Z_BZIP2ED) + { + string_method="BZip2 "; + } + else + string_method="Unkn. "; + + Display64BitsSize(file_info.uncompressed_size,7); + printf(" %6s%c",string_method,charCrypt); + Display64BitsSize(file_info.compressed_size,7); + printf(" %3lu%% %2.2lu-%2.2lu-%2.2lu %2.2lu:%2.2lu %8.8lx %s\n", + ratio, + (uLong)file_info.tmu_date.tm_mon + 1, + (uLong)file_info.tmu_date.tm_mday, + (uLong)file_info.tmu_date.tm_year % 100, + (uLong)file_info.tmu_date.tm_hour,(uLong)file_info.tmu_date.tm_min, + (uLong)file_info.crc,filename_inzip); + if ((i+1)='a') && (rep<='z')) + rep -= 0x20; + } + while ((rep!='Y') && (rep!='N') && (rep!='A')); + } + + if (rep == 'N') + skip = 1; + + if (rep == 'A') + *popt_overwrite=1; + } + + if ((skip==0) && (err==UNZ_OK)) + { + fout=FOPEN_FUNC(write_filename,"wb"); + /* some zipfile don't contain directory alone before file */ + if ((fout==NULL) && ((*popt_extract_without_path)==0) && + (filename_withoutpath!=(char*)filename_inzip)) + { + char c=*(filename_withoutpath-1); + *(filename_withoutpath-1)='\0'; + makedir(write_filename); + *(filename_withoutpath-1)=c; + fout=FOPEN_FUNC(write_filename,"wb"); + } + + if (fout==NULL) + { + printf("error opening %s\n",write_filename); + } + } + + if (fout!=NULL) + { + printf(" extracting: %s\n",write_filename); + + do + { + err = unzReadCurrentFile(uf,buf,size_buf); + if (err<0) + { + printf("error %d with zipfile in unzReadCurrentFile\n",err); + break; + } + if (err>0) + if (fwrite(buf,err,1,fout)!=1) + { + printf("error in writing extracted file\n"); + err=UNZ_ERRNO; + break; + } + } + while (err>0); + if (fout) + fclose(fout); + + if (err==0) + change_file_date(write_filename,file_info.dosDate, + file_info.tmu_date); + } + + if (err==UNZ_OK) + { + err = unzCloseCurrentFile (uf); + if (err!=UNZ_OK) + { + printf("error %d with zipfile in unzCloseCurrentFile\n",err); + } + } + else + unzCloseCurrentFile(uf); /* don't lose the error */ + } + + free(buf); + return err; +} + + +int do_extract(uf,opt_extract_without_path,opt_overwrite,password) + unzFile uf; + int opt_extract_without_path; + int opt_overwrite; + const char* password; +{ + uLong i; + unz_global_info64 gi; + int err; + FILE* fout=NULL; + + err = unzGetGlobalInfo64(uf,&gi); + if (err!=UNZ_OK) + printf("error %d with zipfile in unzGetGlobalInfo \n",err); + + for (i=0;i insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +miniunzip - uncompress and examine ZIP archives +.SH SYNOPSIS +.B miniunzip +.RI [ -exvlo ] +zipfile [ files_to_extract ] [-d tempdir] +.SH DESCRIPTION +.B minizip +is a simple tool which allows the extraction of compressed file +archives in the ZIP format used by the MS-DOS utility PKZIP. It was +written as a demonstration of the +.IR zlib (3) +library and therefore lack many of the features of the +.IR unzip (1) +program. +.SH OPTIONS +A number of options are supported. With the exception of +.BI \-d\ tempdir +these must be supplied before any +other arguments and are: +.TP +.BI \-l\ ,\ \-\-v +List the files in the archive without extracting them. +.TP +.B \-o +Overwrite files without prompting for confirmation. +.TP +.B \-x +Extract files (default). +.PP +The +.I zipfile +argument is the name of the archive to process. The next argument can be used +to specify a single file to extract from the archive. + +Lastly, the following option can be specified at the end of the command-line: +.TP +.BI \-d\ tempdir +Extract the archive in the directory +.I tempdir +rather than the current directory. +.SH SEE ALSO +.BR minizip (1), +.BR zlib (3), +.BR unzip (1). +.SH AUTHOR +This program was written by Gilles Vollant. This manual page was +written by Mark Brown . The -d tempdir option +was added by Dirk Eddelbuettel . diff --git a/zlib/zlib/contrib/minizip/minizip.1 b/zlib/zlib/contrib/minizip/minizip.1 new file mode 100644 index 00000000..1154484c --- /dev/null +++ b/zlib/zlib/contrib/minizip/minizip.1 @@ -0,0 +1,46 @@ +.\" Hey, EMACS: -*- nroff -*- +.TH minizip 1 "May 2, 2001" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +minizip - create ZIP archives +.SH SYNOPSIS +.B minizip +.RI [ -o ] +zipfile [ " files" ... ] +.SH DESCRIPTION +.B minizip +is a simple tool which allows the creation of compressed file archives +in the ZIP format used by the MS-DOS utility PKZIP. It was written as +a demonstration of the +.IR zlib (3) +library and therefore lack many of the features of the +.IR zip (1) +program. +.SH OPTIONS +The first argument supplied is the name of the ZIP archive to create or +.RI -o +in which case it is ignored and the second argument treated as the +name of the ZIP file. If the ZIP file already exists it will be +overwritten. +.PP +Subsequent arguments specify a list of files to place in the ZIP +archive. If none are specified then an empty archive will be created. +.SH SEE ALSO +.BR miniunzip (1), +.BR zlib (3), +.BR zip (1). +.SH AUTHOR +This program was written by Gilles Vollant. This manual page was +written by Mark Brown . + diff --git a/zlib/zlib/contrib/minizip/minizip.c b/zlib/zlib/contrib/minizip/minizip.c new file mode 100644 index 00000000..4288962e --- /dev/null +++ b/zlib/zlib/contrib/minizip/minizip.c @@ -0,0 +1,520 @@ +/* + minizip.c + Version 1.1, February 14h, 2010 + sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) +*/ + + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif +#endif + +#ifdef __APPLE__ +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +# include +#endif + +#include "zip.h" + +#ifdef _WIN32 + #define USEWIN32IOAPI + #include "iowin32.h" +#endif + + + +#define WRITEBUFFERSIZE (16384) +#define MAXFILENAME (256) + +#ifdef _WIN32 +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + int ret = 0; + { + FILETIME ftLocal; + HANDLE hFind; + WIN32_FIND_DATAA ff32; + + hFind = FindFirstFileA(f,&ff32); + if (hFind != INVALID_HANDLE_VALUE) + { + FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal); + FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0); + FindClose(hFind); + ret = 1; + } + } + return ret; +} +#else +#ifdef unix || __APPLE__ +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + int ret=0; + struct stat s; /* results of stat() */ + struct tm* filedate; + time_t tm_t=0; + + if (strcmp(f,"-")!=0) + { + char name[MAXFILENAME+1]; + int len = strlen(f); + if (len > MAXFILENAME) + len = MAXFILENAME; + + strncpy(name, f,MAXFILENAME-1); + /* strncpy doesnt append the trailing NULL, of the string is too long. */ + name[ MAXFILENAME ] = '\0'; + + if (name[len - 1] == '/') + name[len - 1] = '\0'; + /* not all systems allow stat'ing a file with / appended */ + if (stat(name,&s)==0) + { + tm_t = s.st_mtime; + ret = 1; + } + } + filedate = localtime(&tm_t); + + tmzip->tm_sec = filedate->tm_sec; + tmzip->tm_min = filedate->tm_min; + tmzip->tm_hour = filedate->tm_hour; + tmzip->tm_mday = filedate->tm_mday; + tmzip->tm_mon = filedate->tm_mon ; + tmzip->tm_year = filedate->tm_year; + + return ret; +} +#else +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + return 0; +} +#endif +#endif + + + + +int check_exist_file(filename) + const char* filename; +{ + FILE* ftestexist; + int ret = 1; + ftestexist = FOPEN_FUNC(filename,"rb"); + if (ftestexist==NULL) + ret = 0; + else + fclose(ftestexist); + return ret; +} + +void do_banner() +{ + printf("MiniZip 1.1, demo of zLib + MiniZip64 package, written by Gilles Vollant\n"); + printf("more info on MiniZip at http://www.winimage.com/zLibDll/minizip.html\n\n"); +} + +void do_help() +{ + printf("Usage : minizip [-o] [-a] [-0 to -9] [-p password] [-j] file.zip [files_to_add]\n\n" \ + " -o Overwrite existing file.zip\n" \ + " -a Append to existing file.zip\n" \ + " -0 Store only\n" \ + " -1 Compress faster\n" \ + " -9 Compress better\n\n" \ + " -j exclude path. store only the file name.\n\n"); +} + +/* calculate the CRC32 of a file, + because to encrypt a file, we need known the CRC32 of the file before */ +int getFileCrc(const char* filenameinzip,void*buf,unsigned long size_buf,unsigned long* result_crc) +{ + unsigned long calculate_crc=0; + int err=ZIP_OK; + FILE * fin = FOPEN_FUNC(filenameinzip,"rb"); + + unsigned long size_read = 0; + unsigned long total_read = 0; + if (fin==NULL) + { + err = ZIP_ERRNO; + } + + if (err == ZIP_OK) + do + { + err = ZIP_OK; + size_read = (int)fread(buf,1,size_buf,fin); + if (size_read < size_buf) + if (feof(fin)==0) + { + printf("error in reading %s\n",filenameinzip); + err = ZIP_ERRNO; + } + + if (size_read>0) + calculate_crc = crc32(calculate_crc,buf,size_read); + total_read += size_read; + + } while ((err == ZIP_OK) && (size_read>0)); + + if (fin) + fclose(fin); + + *result_crc=calculate_crc; + printf("file %s crc %lx\n", filenameinzip, calculate_crc); + return err; +} + +int isLargeFile(const char* filename) +{ + int largeFile = 0; + ZPOS64_T pos = 0; + FILE* pFile = FOPEN_FUNC(filename, "rb"); + + if(pFile != NULL) + { + int n = FSEEKO_FUNC(pFile, 0, SEEK_END); + pos = FTELLO_FUNC(pFile); + + printf("File : %s is %lld bytes\n", filename, pos); + + if(pos >= 0xffffffff) + largeFile = 1; + + fclose(pFile); + } + + return largeFile; +} + +int main(argc,argv) + int argc; + char *argv[]; +{ + int i; + int opt_overwrite=0; + int opt_compress_level=Z_DEFAULT_COMPRESSION; + int opt_exclude_path=0; + int zipfilenamearg = 0; + char filename_try[MAXFILENAME+16]; + int zipok; + int err=0; + int size_buf=0; + void* buf=NULL; + const char* password=NULL; + + + do_banner(); + if (argc==1) + { + do_help(); + return 0; + } + else + { + for (i=1;i='0') && (c<='9')) + opt_compress_level = c-'0'; + if ((c=='j') || (c=='J')) + opt_exclude_path = 1; + + if (((c=='p') || (c=='P')) && (i+1='a') && (rep<='z')) + rep -= 0x20; + } + while ((rep!='Y') && (rep!='N') && (rep!='A')); + if (rep=='N') + zipok = 0; + if (rep=='A') + opt_overwrite = 2; + } + } + + if (zipok==1) + { + zipFile zf; + int errclose; +# ifdef USEWIN32IOAPI + zlib_filefunc64_def ffunc; + fill_win32_filefunc64A(&ffunc); + zf = zipOpen2_64(filename_try,(opt_overwrite==2) ? 2 : 0,NULL,&ffunc); +# else + zf = zipOpen64(filename_try,(opt_overwrite==2) ? 2 : 0); +# endif + + if (zf == NULL) + { + printf("error opening %s\n",filename_try); + err= ZIP_ERRNO; + } + else + printf("creating %s\n",filename_try); + + for (i=zipfilenamearg+1;(i='0') || (argv[i][1]<='9'))) && + (strlen(argv[i]) == 2))) + { + FILE * fin; + int size_read; + const char* filenameinzip = argv[i]; + const char *savefilenameinzip; + zip_fileinfo zi; + unsigned long crcFile=0; + int zip64 = 0; + + zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour = + zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0; + zi.dosDate = 0; + zi.internal_fa = 0; + zi.external_fa = 0; + filetime(filenameinzip,&zi.tmz_date,&zi.dosDate); + +/* + err = zipOpenNewFileInZip(zf,filenameinzip,&zi, + NULL,0,NULL,0,NULL / * comment * /, + (opt_compress_level != 0) ? Z_DEFLATED : 0, + opt_compress_level); +*/ + if ((password != NULL) && (err==ZIP_OK)) + err = getFileCrc(filenameinzip,buf,size_buf,&crcFile); + + zip64 = isLargeFile(filenameinzip); + + /* The path name saved, should not include a leading slash. */ + /*if it did, windows/xp and dynazip couldn't read the zip file. */ + savefilenameinzip = filenameinzip; + while( savefilenameinzip[0] == '\\' || savefilenameinzip[0] == '/' ) + { + savefilenameinzip++; + } + + /*should the zip file contain any path at all?*/ + if( opt_exclude_path ) + { + const char *tmpptr; + const char *lastslash = 0; + for( tmpptr = savefilenameinzip; *tmpptr; tmpptr++) + { + if( *tmpptr == '\\' || *tmpptr == '/') + { + lastslash = tmpptr; + } + } + if( lastslash != NULL ) + { + savefilenameinzip = lastslash+1; // base filename follows last slash. + } + } + + /**/ + err = zipOpenNewFileInZip3_64(zf,savefilenameinzip,&zi, + NULL,0,NULL,0,NULL /* comment*/, + (opt_compress_level != 0) ? Z_DEFLATED : 0, + opt_compress_level,0, + /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */ + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + password,crcFile, zip64); + + if (err != ZIP_OK) + printf("error in opening %s in zipfile\n",filenameinzip); + else + { + fin = FOPEN_FUNC(filenameinzip,"rb"); + if (fin==NULL) + { + err=ZIP_ERRNO; + printf("error in opening %s for reading\n",filenameinzip); + } + } + + if (err == ZIP_OK) + do + { + err = ZIP_OK; + size_read = (int)fread(buf,1,size_buf,fin); + if (size_read < size_buf) + if (feof(fin)==0) + { + printf("error in reading %s\n",filenameinzip); + err = ZIP_ERRNO; + } + + if (size_read>0) + { + err = zipWriteInFileInZip (zf,buf,size_read); + if (err<0) + { + printf("error in writing %s in the zipfile\n", + filenameinzip); + } + + } + } while ((err == ZIP_OK) && (size_read>0)); + + if (fin) + fclose(fin); + + if (err<0) + err=ZIP_ERRNO; + else + { + err = zipCloseFileInZip(zf); + if (err!=ZIP_OK) + printf("error in closing %s in the zipfile\n", + filenameinzip); + } + } + } + errclose = zipClose(zf,NULL); + if (errclose != ZIP_OK) + printf("error in closing %s\n",filename_try); + } + else + { + do_help(); + } + + free(buf); + return 0; +} diff --git a/zlib/zlib/contrib/minizip/minizip.pc.in b/zlib/zlib/contrib/minizip/minizip.pc.in new file mode 100644 index 00000000..69b5b7fd --- /dev/null +++ b/zlib/zlib/contrib/minizip/minizip.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/minizip + +Name: minizip +Description: Minizip zip file manipulation library +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lminizip +Libs.private: -lz +Cflags: -I${includedir} diff --git a/zlib/zlib/contrib/minizip/mztools.c b/zlib/zlib/contrib/minizip/mztools.c new file mode 100644 index 00000000..96891c2e --- /dev/null +++ b/zlib/zlib/contrib/minizip/mztools.c @@ -0,0 +1,291 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +/* Code */ +#include +#include +#include +#include "zlib.h" +#include "unzip.h" + +#define READ_8(adr) ((unsigned char)*(adr)) +#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) ) +#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) ) + +#define WRITE_8(buff, n) do { \ + *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \ +} while(0) +#define WRITE_16(buff, n) do { \ + WRITE_8((unsigned char*)(buff), n); \ + WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \ +} while(0) +#define WRITE_32(buff, n) do { \ + WRITE_16((unsigned char*)(buff), (n) & 0xffff); \ + WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \ +} while(0) + +extern int ZEXPORT unzRepair(file, fileOut, fileOutTmp, nRecovered, bytesRecovered) +const char* file; +const char* fileOut; +const char* fileOutTmp; +uLong* nRecovered; +uLong* bytesRecovered; +{ + int err = Z_OK; + FILE* fpZip = fopen(file, "rb"); + FILE* fpOut = fopen(fileOut, "wb"); + FILE* fpOutCD = fopen(fileOutTmp, "wb"); + if (fpZip != NULL && fpOut != NULL) { + int entries = 0; + uLong totalBytes = 0; + char header[30]; + char filename[1024]; + char extra[1024]; + int offset = 0; + int offsetCD = 0; + while ( fread(header, 1, 30, fpZip) == 30 ) { + int currentOffset = offset; + + /* File entry */ + if (READ_32(header) == 0x04034b50) { + unsigned int version = READ_16(header + 4); + unsigned int gpflag = READ_16(header + 6); + unsigned int method = READ_16(header + 8); + unsigned int filetime = READ_16(header + 10); + unsigned int filedate = READ_16(header + 12); + unsigned int crc = READ_32(header + 14); /* crc */ + unsigned int cpsize = READ_32(header + 18); /* compressed size */ + unsigned int uncpsize = READ_32(header + 22); /* uncompressed sz */ + unsigned int fnsize = READ_16(header + 26); /* file name length */ + unsigned int extsize = READ_16(header + 28); /* extra field length */ + filename[0] = extra[0] = '\0'; + + /* Header */ + if (fwrite(header, 1, 30, fpOut) == 30) { + offset += 30; + } else { + err = Z_ERRNO; + break; + } + + /* Filename */ + if (fnsize > 0) { + if (fnsize < sizeof(filename)) { + if (fread(filename, 1, fnsize, fpZip) == fnsize) { + if (fwrite(filename, 1, fnsize, fpOut) == fnsize) { + offset += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (extsize < sizeof(extra)) { + if (fread(extra, 1, extsize, fpZip) == extsize) { + if (fwrite(extra, 1, extsize, fpOut) == extsize) { + offset += extsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } + + /* Data */ + { + int dataSize = cpsize; + if (dataSize == 0) { + dataSize = uncpsize; + } + if (dataSize > 0) { + char* data = malloc(dataSize); + if (data != NULL) { + if ((int)fread(data, 1, dataSize, fpZip) == dataSize) { + if ((int)fwrite(data, 1, dataSize, fpOut) == dataSize) { + offset += dataSize; + totalBytes += dataSize; + } else { + err = Z_ERRNO; + } + } else { + err = Z_ERRNO; + } + free(data); + if (err != Z_OK) { + break; + } + } else { + err = Z_MEM_ERROR; + break; + } + } + } + + /* Central directory entry */ + { + char header[46]; + char* comment = ""; + int comsize = (int) strlen(comment); + WRITE_32(header, 0x02014b50); + WRITE_16(header + 4, version); + WRITE_16(header + 6, version); + WRITE_16(header + 8, gpflag); + WRITE_16(header + 10, method); + WRITE_16(header + 12, filetime); + WRITE_16(header + 14, filedate); + WRITE_32(header + 16, crc); + WRITE_32(header + 20, cpsize); + WRITE_32(header + 24, uncpsize); + WRITE_16(header + 28, fnsize); + WRITE_16(header + 30, extsize); + WRITE_16(header + 32, comsize); + WRITE_16(header + 34, 0); /* disk # */ + WRITE_16(header + 36, 0); /* int attrb */ + WRITE_32(header + 38, 0); /* ext attrb */ + WRITE_32(header + 42, currentOffset); + /* Header */ + if (fwrite(header, 1, 46, fpOutCD) == 46) { + offsetCD += 46; + + /* Filename */ + if (fnsize > 0) { + if (fwrite(filename, 1, fnsize, fpOutCD) == fnsize) { + offsetCD += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fwrite(extra, 1, extsize, fpOutCD) == extsize) { + offsetCD += extsize; + } else { + err = Z_ERRNO; + break; + } + } + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) == comsize) { + offsetCD += comsize; + } else { + err = Z_ERRNO; + break; + } + } + + + } else { + err = Z_ERRNO; + break; + } + } + + /* Success */ + entries++; + + } else { + break; + } + } + + /* Final central directory */ + { + int entriesZip = entries; + char header[22]; + char* comment = ""; // "ZIP File recovered by zlib/minizip/mztools"; + int comsize = (int) strlen(comment); + if (entriesZip > 0xffff) { + entriesZip = 0xffff; + } + WRITE_32(header, 0x06054b50); + WRITE_16(header + 4, 0); /* disk # */ + WRITE_16(header + 6, 0); /* disk # */ + WRITE_16(header + 8, entriesZip); /* hack */ + WRITE_16(header + 10, entriesZip); /* hack */ + WRITE_32(header + 12, offsetCD); /* size of CD */ + WRITE_32(header + 16, offset); /* offset to CD */ + WRITE_16(header + 20, comsize); /* comment */ + + /* Header */ + if (fwrite(header, 1, 22, fpOutCD) == 22) { + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) != comsize) { + err = Z_ERRNO; + } + } + + } else { + err = Z_ERRNO; + } + } + + /* Final merge (file + central directory) */ + fclose(fpOutCD); + if (err == Z_OK) { + fpOutCD = fopen(fileOutTmp, "rb"); + if (fpOutCD != NULL) { + int nRead; + char buffer[8192]; + while ( (nRead = (int)fread(buffer, 1, sizeof(buffer), fpOutCD)) > 0) { + if ((int)fwrite(buffer, 1, nRead, fpOut) != nRead) { + err = Z_ERRNO; + break; + } + } + fclose(fpOutCD); + } + } + + /* Close */ + fclose(fpZip); + fclose(fpOut); + + /* Wipe temporary file */ + (void)remove(fileOutTmp); + + /* Number of recovered entries */ + if (err == Z_OK) { + if (nRecovered != NULL) { + *nRecovered = entries; + } + if (bytesRecovered != NULL) { + *bytesRecovered = totalBytes; + } + } + } else { + err = Z_STREAM_ERROR; + } + return err; +} diff --git a/zlib/zlib/contrib/minizip/mztools.h b/zlib/zlib/contrib/minizip/mztools.h new file mode 100644 index 00000000..a49a426e --- /dev/null +++ b/zlib/zlib/contrib/minizip/mztools.h @@ -0,0 +1,37 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +#ifndef _zip_tools_H +#define _zip_tools_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#include "unzip.h" + +/* Repair a ZIP file (missing central directory) + file: file to recover + fileOut: output file after recovery + fileOutTmp: temporary file name used for recovery +*/ +extern int ZEXPORT unzRepair(const char* file, + const char* fileOut, + const char* fileOutTmp, + uLong* nRecovered, + uLong* bytesRecovered); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/zlib/zlib/contrib/minizip/unzip.c b/zlib/zlib/contrib/minizip/unzip.c new file mode 100644 index 00000000..90935043 --- /dev/null +++ b/zlib/zlib/contrib/minizip/unzip.c @@ -0,0 +1,2125 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + + ------------------------------------------------------------------------------------ + Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of + compatibility with older software. The following is from the original crypt.c. + Code woven in by Terry Thorsen 1/2003. + + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html + + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + ------------------------------------------------------------------------------------ + + Changes in unzip.c + + 2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos + 2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz* + 2007-2008 - Even Rouault - Remove old C style function prototypes + 2007-2008 - Even Rouault - Add unzip support for ZIP64 + + Copyright (C) 2007-2008 Even Rouault + + + Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again). + Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G + should only read the compressed/uncompressed size from the Zip64 format if + the size from normal header was 0xFFFFFFFF + Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant + Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required) + Patch created by Daniel Borca + + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + + Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson + +*/ + + +#include +#include +#include + +#ifndef NOUNCRYPT + #define NOUNCRYPT +#endif + +#include "zlib.h" +#include "unzip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info64_internal_s +{ + ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ +} unz_file_info64_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + ZPOS64_T offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + ZPOS64_T pos_local_extrafield; /* position in the local extra field in read*/ + ZPOS64_T total_out_64; + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ + ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip64_read_info_s; + + +/* unz64_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + int is64bitOpenFunction; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info64 gi; /* public global information */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + ZPOS64_T num_file; /* number of the current file in the zipfile*/ + ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ + ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ + ZPOS64_T central_pos; /* position of the beginning of the central dir*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info64 cur_file_info; /* public info about the current file in zip*/ + unz_file_info64_internal cur_file_info_internal; /* private info about it*/ + file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; + + int isZip64; + +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const z_crc_t* pcrc_32_tab; +# endif +} unz64_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unz64local_getByte OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unz64local_getShort OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX)); + + +local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX) +{ + ZPOS64_T x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<24; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<32; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<40; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<48; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<56; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, + const char* fileName2, + int iCaseSensitivity) + +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); +local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + + +/* + Locate the Central directory 64 of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream)); + +local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK) + return 0; + + /* total number of disks */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + if (uL != 0x06064b50) + return 0; + + return relativeOffset; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +local unzFile unzOpenInternal (const void *path, + zlib_filefunc64_32_def* pzlib_filefunc64_32_def, + int is64bitOpenFunction) +{ + unz64_s us; + unz64_s *s; + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + us.z_filefunc.zseek32_file = NULL; + us.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&us.z_filefunc.zfile_func64); + else + us.z_filefunc = *pzlib_filefunc64_32_def; + us.is64bitOpenFunction = is64bitOpenFunction; + + + + us.filestream = ZOPEN64(us.z_filefunc, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream); + if (central_pos) + { + uLong uS; + ZPOS64_T uL64; + + us.isZip64 = 1; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* size of zip64 end of central directory record */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version made by */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version needed to extract */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + us.gi.size_comment = 0; + } + else + { + central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + us.isZip64 = 0; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.gi.number_entry = uL; + + /* total number of entries in the central dir */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + number_entry_CD = uL; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.size_central_dir = uL; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.offset_central_dir = uL; + + /* zipfile comment length */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + } + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE64(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + /* to do : check if number_entry is not truncated */ + pglobal_info32->number_entry = (uLong)s->gi.number_entry; + pglobal_info32->size_comment = s->gi.size_comment; + return UNZ_OK; +} +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) +{ + ZPOS64_T uDate; + uDate = (ZPOS64_T)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unz64local_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unz64local_GetCurrentFileInfoInternal (unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz64_s* s; + unz_file_info64 file_info; + unz_file_info64_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + uLong uL; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.compressed_size = uL; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.uncompressed_size = uL; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + // relative offset of local header + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info_internal.offset_curfile = uL; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + // Read extrafield + if ((err==UNZ_OK) && (extraField!=NULL)) + { + ZPOS64_T uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + + lSeek += file_info.size_file_extra - (uLong)uSizeRead; + } + else + lSeek += file_info.size_file_extra; + + + if ((err==UNZ_OK) && (file_info.size_file_extra != 0)) + { + uLong acc = 0; + + // since lSeek now points to after the extra field we need to move back + lSeek -= file_info.size_file_extra; + + if (lSeek!=0) + { + if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + while(acc < file_info.size_file_extra) + { + uLong headerId; + uLong dataSize; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK) + err=UNZ_ERRNO; + + /* ZIP64 extra fields */ + if (headerId == 0x0001) + { + uLong uL; + + if(file_info.uncompressed_size == MAXU32) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.compressed_size == MAXU32) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info_internal.offset_curfile == MAXU32) + { + /* Relative Header offset */ + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.disk_num_start == MAXU32) + { + /* Disk Start Number */ + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + } + + } + else + { + if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0) + err=UNZ_ERRNO; + } + + acc += 2 + 2 + dataSize; + } + } + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file, + unz_file_info64 * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, + unz_file_info * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + int err; + unz_file_info64 file_info64; + err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); + if ((err==UNZ_OK) && (pfile_info != NULL)) + { + pfile_info->version = file_info64.version; + pfile_info->version_needed = file_info64.version_needed; + pfile_info->flag = file_info64.flag; + pfile_info->compression_method = file_info64.compression_method; + pfile_info->dosDate = file_info64.dosDate; + pfile_info->crc = file_info64.crc; + + pfile_info->size_filename = file_info64.size_filename; + pfile_info->size_file_extra = file_info64.size_file_extra; + pfile_info->size_file_comment = file_info64.size_file_comment; + + pfile_info->disk_num_start = file_info64.disk_num_start; + pfile_info->internal_fa = file_info64.internal_fa; + pfile_info->external_fa = file_info64.external_fa; + + pfile_info->tmu_date = file_info64.tmu_date, + + + pfile_info->compressed_size = (uLong)file_info64.compressed_size; + pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; + + } + return err; +} +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (unzFile file) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz64_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info64 cur_file_infoSaved; + unz_file_info64_internal cur_file_info_internalSaved; + ZPOS64_T num_fileSaved; + ZPOS64_T pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo64(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; // offset in file + ZPOS64_T num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) +{ + unz64_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + int err = unzGetFilePos64(file,&file_pos64); + if (err==UNZ_OK) + { + file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; + file_pos->num_of_file = (uLong)file_pos64.num_of_file; + } + return err; +} + +extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) +{ + unz64_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + if (file_pos == NULL) + return UNZ_PARAMERROR; + + file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; + file_pos64.num_of_file = file_pos->num_of_file; + return unzGoToFilePos64(file,&file_pos64); +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar, + ZPOS64_T * poffset_local_extrafield, + uInt * psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method, + int* level, int raw, const char* password) +{ + int err=UNZ_OK; + uInt iSizeVar; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + ZPOS64_T offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->total_out_64=0; + pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw)) + { +#ifdef HAVE_BZIP2 + pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; + pfile_in_zip_read_info->bstream.bzfree = (free_func)0; + pfile_in_zip_read_info->bstream.opaque = (voidpf)0; + pfile_in_zip_read_info->bstream.state = (voidpf)0; + + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } +#else + pfile_in_zip_read_info->raw=1; +#endif + } + else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = 0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_DEFLATED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + s->encrypted = 0; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (unzFile file) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char* password) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw) +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + s=(unz64_s*)file; + if (file==NULL) + return 0; //UNZ_PARAMERROR; + pfile_in_zip_read_info=s->pfile_in_zip_read; + if (pfile_in_zip_read_info==NULL) + return 0; //UNZ_PARAMERROR; + return pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile; +} + +/** Addition for GDAL : END */ + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->read_buffer == NULL) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + + pfile_in_zip_read_info->bstream.next_in = (char*)pfile_in_zip_read_info->stream.next_in; + pfile_in_zip_read_info->bstream.avail_in = pfile_in_zip_read_info->stream.avail_in; + pfile_in_zip_read_info->bstream.total_in_lo32 = pfile_in_zip_read_info->stream.total_in; + pfile_in_zip_read_info->bstream.total_in_hi32 = 0; + pfile_in_zip_read_info->bstream.next_out = (char*)pfile_in_zip_read_info->stream.next_out; + pfile_in_zip_read_info->bstream.avail_out = pfile_in_zip_read_info->stream.avail_out; + pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out; + pfile_in_zip_read_info->bstream.total_out_hi32 = 0; + + uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32; + bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out; + + err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream); + + uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); + pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->bstream.next_in; + pfile_in_zip_read_info->stream.avail_in = pfile_in_zip_read_info->bstream.avail_in; + pfile_in_zip_read_info->stream.total_in = pfile_in_zip_read_info->bstream.total_in_lo32; + pfile_in_zip_read_info->stream.next_out = (Bytef*)pfile_in_zip_read_info->bstream.next_out; + pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out; + pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32; + + if (err==BZ_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=BZ_OK) + break; +#endif + } // end Z_BZIP2ED + else + { + ZPOS64_T uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + ZPOS64_T uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + +extern ZPOS64_T ZEXPORT unztell64 (unzFile file) +{ + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return (ZPOS64_T)-1; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return (ZPOS64_T)-1; + + return pfile_in_zip_read_info->total_out_64; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* +Read extra field from the current file (opened by unzOpenCurrentFile) +This is the local-header version of the extra field (sometimes, there is +more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + uInt read_now; + ZPOS64_T size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) + inflateEnd(&pfile_in_zip_read_info->stream); +#ifdef HAVE_BZIP2 + else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) + BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); +#endif + + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf) +{ + unz64_s* s; + uLong uReadThis ; + if (file==NULL) + return (int)UNZ_PARAMERROR; + s=(unz64_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) +{ + unz64_s* s; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern uLong ZEXPORT unzGetOffset (unzFile file) +{ + ZPOS64_T offset64; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + offset64 = unzGetOffset64(file); + return (uLong)offset64; +} + +extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos) +{ + return unzSetOffset64(file,pos); +} diff --git a/zlib/zlib/contrib/minizip/unzip.h b/zlib/zlib/contrib/minizip/unzip.h new file mode 100644 index 00000000..2104e391 --- /dev/null +++ b/zlib/zlib/contrib/minizip/unzip.h @@ -0,0 +1,437 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------------- + + Changes + + See header of unzip64.c + +*/ + +#ifndef _unz64_H +#define _unz64_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info64_s +{ + ZPOS64_T number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info64; + +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info64_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + ZPOS64_T compressed_size; /* compressed size 8 bytes */ + ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info64; + +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +extern unzFile ZEXPORT unzOpen64 OF((const void *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. + the "64" function take a const void* pointer, because the path is just the + value passed to the open64_file_func callback. + Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path + is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* + does not describe the reality +*/ + + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, + zlib_filefunc64_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unz64Open, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzCloseCurrentFile before call unzClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); + +extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, + unz_global_info64 *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +typedef struct unz64_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ + ZPOS64_T num_of_file; /* # of file */ +} unz64_file_pos; + +extern int ZEXPORT unzGetFilePos64( + unzFile file, + unz64_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos64( + unzFile file, + const unz64_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, + unz_file_info64 *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); + +/** Addition for GDAL : END */ + + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); + +extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file); +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos); +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz64_H */ diff --git a/zlib/zlib/contrib/minizip/zip.c b/zlib/zlib/contrib/minizip/zip.c new file mode 100644 index 00000000..ea54853e --- /dev/null +++ b/zlib/zlib/contrib/minizip/zip.c @@ -0,0 +1,2007 @@ +/* zip.c -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + Oct-2009 - Mathias Svensson - Remove old C style function prototypes + Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives + Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions. + Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data + It is used when recreting zip archive with RAW when deleting items from a zip. + ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed. + Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required) + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + +*/ + + +#include +#include +#include +#include +#include "zlib.h" +#include "zip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#ifndef VERSIONMADEBY +# define VERSIONMADEBY (0x0) /* platform depedent */ +#endif + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (64*1024) //(16384) +#endif + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + + +// NOT sure that this work on ALL platform +#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32)) + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef DEF_MEM_LEVEL +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif +const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + + +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) +#define ZIP64ENDHEADERMAGIC (0x6064b50) +#define ZIP64ENDLOCHEADERMAGIC (0x7064b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +typedef struct linkedlist_datablock_internal_s +{ + struct linkedlist_datablock_internal_s* next_datablock; + uLong avail_in_this_block; + uLong filled_in_this_block; + uLong unused; /* for future use and alignement */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +typedef struct linkedlist_data_s +{ + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + + +typedef struct +{ + z_stream stream; /* zLib stream structure for inflate */ +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + int stream_initialised; /* 1 is stream is initialised */ + uInt pos_in_buffered_data; /* last written byte in buffered_data */ + + ZPOS64_T pos_local_header; /* offset of the local header of the file + currenty writing */ + char* central_header; /* central header data for the current file */ + uLong size_centralExtra; + uLong size_centralheader; /* size of the central header for cur file */ + uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */ + uLong flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + uLong dosDate; + uLong crc32; + int encrypt; + int zip64; /* Add ZIP64 extened information in the extra field */ + ZPOS64_T pos_zip64extrainfo; + ZPOS64_T totalCompressedData; + ZPOS64_T totalUncompressedData; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const z_crc_t* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile64_info; + +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + linkedlist_data central_dir;/* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile64_info ci; /* info on the file curretly writing */ + + ZPOS64_T begin_pos; /* position of the beginning of the zipfile */ + ZPOS64_T add_position_when_writting_offset; + ZPOS64_T number_entry; + +#ifndef NO_ADDFILEINEXISTINGZIP + char *globalcomment; +#endif + +} zip64_internal; + + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +local linkedlist_datablock_internal* allocate_new_datablock() +{ + linkedlist_datablock_internal* ldi; + ldi = (linkedlist_datablock_internal*) + ALLOC(sizeof(linkedlist_datablock_internal)); + if (ldi!=NULL) + { + ldi->next_datablock = NULL ; + ldi->filled_in_this_block = 0 ; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; + } + return ldi; +} + +local void free_datablock(linkedlist_datablock_internal* ldi) +{ + while (ldi!=NULL) + { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE(ldi); + ldi = ldinext; + } +} + +local void init_linkedlist(linkedlist_data* ll) +{ + ll->first_block = ll->last_block = NULL; +} + +local void free_linkedlist(linkedlist_data* ll) +{ + free_datablock(ll->first_block); + ll->first_block = ll->last_block = NULL; +} + + +local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len) +{ + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if (ll==NULL) + return ZIP_INTERNALERROR; + + if (ll->last_block == NULL) + { + ll->first_block = ll->last_block = allocate_new_datablock(); + if (ll->first_block == NULL) + return ZIP_INTERNALERROR; + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while (len>0) + { + uInt copy_this; + uInt i; + unsigned char* to_copy; + + if (ldi->avail_in_this_block==0) + { + ldi->next_datablock = allocate_new_datablock(); + if (ldi->next_datablock == NULL) + return ZIP_INTERNALERROR; + ldi = ldi->next_datablock ; + ll->last_block = ldi; + } + + if (ldi->avail_in_this_block < len) + copy_this = (uInt)ldi->avail_in_this_block; + else + copy_this = (uInt)len; + + to_copy = &(ldi->data[ldi->filled_in_this_block]); + + for (i=0;ifilled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this ; + len -= copy_this; + } + return ZIP_OK; +} + + + +/****************************************************************************/ + +#ifndef NO_ADDFILEINEXISTINGZIP +/* =========================================================================== + Inputs a long in LSB order to the given file + nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T) +*/ + +local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)); +local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte) +{ + unsigned char buf[8]; + int n; + for (n = 0; n < nbByte; n++) + { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + if (x != 0) + { /* data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } + + if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) + return ZIP_ERRNO; + else + return ZIP_OK; +} + +local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte)); +local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte) +{ + unsigned char* buf=(unsigned char*)dest; + int n; + for (n = 0; n < nbByte; n++) { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + + if (x != 0) + { /* data overflow - hack for ZIP64 */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } +} + +/****************************************************************************/ + + +local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm) +{ + uLong year = (uLong)ptm->tm_year; + if (year>=1980) + year-=1980; + else if (year>=80) + year-=80; + return + (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | + ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); +} + + +/****************************************************************************/ + +local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); + +local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return ZIP_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return ZIP_ERRNO; + else + return ZIP_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); + + +local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) +{ + ZPOS64_T x; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<24; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<32; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<40; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<48; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<56; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + + return err; +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* +Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before +the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + { + // Signature "0x07064b50" Zip64 end of central directory locater + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + } + + if (uPosFound!=0) + break; + } + + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK) + return 0; + + /* total number of disks */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto Zip64 end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + if (uL != 0x06064b50) // signature of 'Zip64 end of central directory' + return 0; + + return relativeOffset; +} + +int LoadCentralDirectoryRecord(zip64_internal* pziinit) +{ + int err=ZIP_OK; + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory */ + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry; + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + uLong VersionMadeBy; + uLong VersionNeeded; + uLong size_comment; + + int hasZIP64Record = 0; + + // check first if we find a ZIP64 record + central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream); + if(central_pos > 0) + { + hasZIP64Record = 1; + } + else if(central_pos == 0) + { + central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream); + } + +/* disable to allow appending to empty ZIP archive + if (central_pos==0) + err=ZIP_ERRNO; +*/ + + if(hasZIP64Record) + { + ZPOS64_T sizeEndOfCentralDirectory; + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* size of zip64 end of central directory record */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version made by */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version needed to extract */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + // TODO.. + // read the comment from the standard central header. + size_comment = 0; + } + else + { + // Read End of central Directory info + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir on this disk */ + number_entry = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry = uL; + + /* total number of entries in the central dir */ + number_entry_CD = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry_CD = uL; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + size_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + size_central_dir = uL; + + /* offset of start of central directory with respect to the starting disk number */ + offset_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + offset_central_dir = uL; + + + /* zipfile global comment length */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK) + err=ZIP_ERRNO; + } + + if ((central_posz_filefunc, pziinit->filestream); + return ZIP_ERRNO; + } + + if (size_comment>0) + { + pziinit->globalcomment = (char*)ALLOC(size_comment+1); + if (pziinit->globalcomment) + { + size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment); + pziinit->globalcomment[size_comment]=0; + } + } + + byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir); + pziinit->add_position_when_writting_offset = byte_before_the_zipfile; + + { + ZPOS64_T size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC(buf_size); + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + while ((size_central_dir_to_read>0) && (err==ZIP_OK)) + { + ZPOS64_T read_this = SIZEDATA_INDATABLOCK; + if (read_this > size_central_dir_to_read) + read_this = size_central_dir_to_read; + + if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this) + err=ZIP_ERRNO; + + if (err==ZIP_OK) + err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this); + + size_central_dir_to_read-=read_this; + } + TRYFREE(buf_read); + } + pziinit->begin_pos = byte_before_the_zipfile; + pziinit->number_entry = number_entry_CD; + + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + return err; +} + + +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + + +/************************************************************/ +extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) +{ + zip64_internal ziinit; + zip64_internal* zi; + int err=ZIP_OK; + + ziinit.z_filefunc.zseek32_file = NULL; + ziinit.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64); + else + ziinit.z_filefunc = *pzlib_filefunc64_32_def; + + ziinit.filestream = ZOPEN64(ziinit.z_filefunc, + pathname, + (append == APPEND_STATUS_CREATE) ? + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); + + if (ziinit.filestream == NULL) + return NULL; + + if (append == APPEND_STATUS_CREATEAFTER) + ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END); + + ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writting_offset = 0; + init_linkedlist(&(ziinit.central_dir)); + + + + zi = (zip64_internal*)ALLOC(sizeof(zip64_internal)); + if (zi==NULL) + { + ZCLOSE64(ziinit.z_filefunc,ziinit.filestream); + return NULL; + } + + /* now we add file in a zipfile */ +# ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if (append == APPEND_STATUS_ADDINZIP) + { + // Read and Cache Central Directory Records + err = LoadCentralDirectoryRecord(&ziinit); + } + + if (globalcomment) + { + *globalcomment = ziinit.globalcomment; + } +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if (err != ZIP_OK) + { +# ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(ziinit.globalcomment); +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE(zi); + return NULL; + } + else + { + *zi = ziinit; + return (zipFile)zi; + } +} + +extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def) +{ + if (pzlib_filefunc32_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + +extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def) +{ + if (pzlib_filefunc_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; + zlib_filefunc64_32_def_fill.ztell32_file = NULL; + zlib_filefunc64_32_def_fill.zseek32_file = NULL; + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + + + +extern zipFile ZEXPORT zipOpen (const char* pathname, int append) +{ + return zipOpen3((const void*)pathname,append,NULL,NULL); +} + +extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append) +{ + return zipOpen3(pathname,append,NULL,NULL); +} + +int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local) +{ + /* write the local header */ + int err; + uInt size_filename = (uInt)strlen(filename); + uInt size_extrafield = size_extrafield_local; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4); + + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); + + // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ + } + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); + + if(zi->ci.zip64) + { + size_extrafield += 20; + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2); + + if ((err==ZIP_OK) && (size_filename > 0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) + err = ZIP_ERRNO; + } + + if ((err==ZIP_OK) && (size_extrafield_local > 0)) + { + if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local) + err = ZIP_ERRNO; + } + + + if ((err==ZIP_OK) && (zi->ci.zip64)) + { + // write the Zip64 extended info + short HeaderID = 1; + short DataSize = 16; + ZPOS64_T CompressedSize = 0; + ZPOS64_T UncompressedSize = 0; + + // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file) + zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream); + + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2); + + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8); + } + + return err; +} + +/* + NOTE. + When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped + before calling this function it can be done with zipRemoveExtraInfoBlock + + It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize + unnecessary allocations. + */ +extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase, int zip64) +{ + zip64_internal* zi; + uInt size_filename; + uInt size_comment; + uInt i; + int err = ZIP_OK; + +# ifdef NOCRYPT + (crcForCrypting); + if (password != NULL) + return ZIP_PARAMERROR; +# endif + + if (file == NULL) + return ZIP_PARAMERROR; + +#ifdef HAVE_BZIP2 + if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED)) + return ZIP_PARAMERROR; +#else + if ((method!=0) && (method!=Z_DEFLATED)) + return ZIP_PARAMERROR; +#endif + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + if (err != ZIP_OK) + return err; + } + + if (filename==NULL) + filename="-"; + + if (comment==NULL) + size_comment = 0; + else + size_comment = (uInt)strlen(comment); + + size_filename = (uInt)strlen(filename); + + if (zipfi == NULL) + zi->ci.dosDate = 0; + else + { + if (zipfi->dosDate != 0) + zi->ci.dosDate = zipfi->dosDate; + else + zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date); + } + + zi->ci.flag = flagBase; + if ((level==8) || (level==9)) + zi->ci.flag |= 2; + if (level==2) + zi->ci.flag |= 4; + if (level==1) + zi->ci.flag |= 6; + if (password != NULL) + zi->ci.flag |= 1; + + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream); + + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; + zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data + + zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree); + + zi->ci.size_centralExtra = size_extrafield_global; + zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); + /* version info */ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2); + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2); + zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); + zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); + zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); + zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ + zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); + zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); + zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); + else + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); + + if(zi->ci.pos_local_header >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4); + + for (i=0;ici.central_header+SIZECENTRALHEADER+i) = *(filename+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+i) = + *(((const char*)extrafield_global)+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+ + size_extrafield_global+i) = *(comment+i); + if (zi->ci.central_header == NULL) + return ZIP_INTERNALERROR; + + zi->ci.zip64 = zip64; + zi->ci.totalCompressedData = 0; + zi->ci.totalUncompressedData = 0; + zi->ci.pos_zip64extrainfo = 0; + + err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local); + +#ifdef HAVE_BZIP2 + zi->ci.bstream.avail_in = (uInt)0; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + zi->ci.bstream.total_in_hi32 = 0; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_out_hi32 = 0; + zi->ci.bstream.total_out_lo32 = 0; +#endif + + zi->ci.stream.avail_in = (uInt)0; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + zi->ci.stream.data_type = Z_BINARY; + +#ifdef HAVE_BZIP2 + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) +#else + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) +#endif + { + if(zi->ci.method == Z_DEFLATED) + { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if (windowBits>0) + windowBits = -windowBits; + + err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy); + + if (err==Z_OK) + zi->ci.stream_initialised = Z_DEFLATED; + } + else if(zi->ci.method == Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + // Init BZip stuff here + zi->ci.bstream.bzalloc = 0; + zi->ci.bstream.bzfree = 0; + zi->ci.bstream.opaque = (voidpf)0; + + err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35); + if(err == BZ_OK) + zi->ci.stream_initialised = Z_BZIP2ED; +#endif + } + + } + +# ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ((err==Z_OK) && (password != NULL)) + { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = get_crc_table(); + /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ + + sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); + zi->ci.crypt_header_size = sizeHead; + + if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) + err = ZIP_ERRNO; + } +# endif + + if (err==Z_OK) + zi->in_opened_file_inzip = 1; + return err; +} + +extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, versionMadeBy, flagBase, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +local int zip64FlushWriteBuffer(zip64_internal* zi) +{ + int err=ZIP_OK; + + if (zi->ci.encrypt != 0) + { +#ifndef NOCRYPT + uInt i; + int t; + for (i=0;ici.pos_in_buffered_data;i++) + zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t); +#endif + } + + if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data) + err = ZIP_ERRNO; + + zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data; + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED) + { + zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_in_hi32 = 0; + } + else +#endif + { + zi->ci.totalUncompressedData += zi->ci.stream.total_in; + zi->ci.stream.total_in = 0; + } + + + zi->ci.pos_in_buffered_data = 0; + + return err; +} + +extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len) +{ + zip64_internal* zi; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + + zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len); + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw)) + { + zi->ci.bstream.next_in = (void*)buf; + zi->ci.bstream.avail_in = len; + err = BZ_RUN_OK; + + while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0)) + { + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + + + if(err != BZ_RUN_OK) + break; + + if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32; +// uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_RUN); + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ; + } + } + + if(err == BZ_RUN_OK) + err = ZIP_OK; + } + else +#endif + { + zi->ci.stream.next_in = (Bytef*)buf; + zi->ci.stream.avail_in = len; + + while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) + { + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + + if(err != ZIP_OK) + break; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_NO_FLUSH); + if(uTotalOutBefore > zi->ci.stream.total_out) + { + int bBreak = 0; + bBreak++; + } + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + else + { + uInt copy_this,i; + if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) + copy_this = zi->ci.stream.avail_in; + else + copy_this = zi->ci.stream.avail_out; + + for (i = 0; i < copy_this; i++) + *(((char*)zi->ci.stream.next_out)+i) = + *(((const char*)zi->ci.stream.next_in)+i); + { + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + }// while(...) + } + + return err; +} + +extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32) +{ + return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32); +} + +extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32) +{ + zip64_internal* zi; + ZPOS64_T compressed_size; + uLong invalidValue = 0xffffffff; + short datasize = 0; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + zi->ci.stream.avail_in = 0; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + while (err==ZIP_OK) + { + uLong uTotalOutBefore; + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_FINISH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + } + else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { +#ifdef HAVE_BZIP2 + err = BZ_FINISH_OK; + while (err==BZ_FINISH_OK) + { + uLong uTotalOutBefore; + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.bstream.total_out_lo32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_FINISH); + if(err == BZ_STREAM_END) + err = Z_STREAM_END; + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore); + } + + if(err == BZ_FINISH_OK) + err = ZIP_OK; +#endif + } + + if (err==Z_STREAM_END) + err=ZIP_OK; /* this is normal */ + + if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) + { + if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO) + err = ZIP_ERRNO; + } + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + int tmp_err = deflateEnd(&zi->ci.stream); + if (err == ZIP_OK) + err = tmp_err; + zi->ci.stream_initialised = 0; + } +#ifdef HAVE_BZIP2 + else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream); + if (err==ZIP_OK) + err = tmperr; + zi->ci.stream_initialised = 0; + } +#endif + + if (!zi->ci.raw) + { + crc32 = (uLong)zi->ci.crc32; + uncompressed_size = zi->ci.totalUncompressedData; + } + compressed_size = zi->ci.totalCompressedData; + +# ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +# endif + + // update Current Item crc and sizes, + if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff) + { + /*version Made by*/ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2); + /*version needed*/ + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2); + + } + + zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ + + + if(compressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/ + + /// set internal file attributes field + if (zi->ci.stream.data_type == Z_ASCII) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); + + if(uncompressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/ + + // Add ZIP64 extra info field for uncompressed size + if(uncompressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for compressed size + if(compressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for relative offset to local file header of current file + if(zi->ci.pos_local_header >= 0xffffffff) + datasize += 8; + + if(datasize > 0) + { + char* p = NULL; + + if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) + { + // we can not write more data to the buffer that we have room for. + return ZIP_BADZIPFILE; + } + + p = zi->ci.central_header + zi->ci.size_centralheader; + + // Add Extra Information Header for 'ZIP64 information' + zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID + p += 2; + zip64local_putValue_inmemory(p, datasize, 2); // DataSize + p += 2; + + if(uncompressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, uncompressed_size, 8); + p += 8; + } + + if(compressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, compressed_size, 8); + p += 8; + } + + if(zi->ci.pos_local_header >= 0xffffffff) + { + zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8); + p += 8; + } + + // Update how much extra free space we got in the memory buffer + // and increase the centralheader size so the new ZIP64 fields are included + // ( 4 below is the size of HeaderID and DataSize field ) + zi->ci.size_centralExtraFree -= datasize + 4; + zi->ci.size_centralheader += datasize + 4; + + // Update the extra info size field + zi->ci.size_centralExtra += datasize + 4; + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2); + } + + if (err==ZIP_OK) + err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader); + + free(zi->ci.central_header); + + if (err==ZIP_OK) + { + // Update the LocalFileHeader with the new values. + + ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff ) + { + if(zi->ci.pos_zip64extrainfo > 0) + { + // Update the size in the ZIP64 extended field. + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8); + } + else + err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal + } + else + { + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + } + + if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + } + + zi->number_entry ++; + zi->in_opened_file_inzip = 0; + + return err; +} + +extern int ZEXPORT zipCloseFileInZip (zipFile file) +{ + return zipCloseFileInZipRaw (file,0,0); +} + +int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip) +{ + int err = ZIP_OK; + ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4); + + /*num disks*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + /*relative offset*/ + if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8); + + /*total disks*/ /* Do not support spawning of disk so always say 1 here*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4); + + return err; +} + +int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + uLong Zip64DataSize = 44; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ? + + if (err==ZIP_OK) /* version made by */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* version needed */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* total number of entries in the central dir */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8); + } + return err; +} +int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + /*signature*/ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + { + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + } + + if (err==ZIP_OK) /* total number of entries in the central dir */ + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + if(pos >= 0xffffffff) + { + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4); + } + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); + } + + return err; +} + +int Write_GlobalComment(zip64_internal* zi, const char* global_comment) +{ + int err = ZIP_OK; + uInt size_global_comment = 0; + + if(global_comment != NULL) + size_global_comment = (uInt)strlen(global_comment); + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); + + if (err == ZIP_OK && size_global_comment > 0) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment) + err = ZIP_ERRNO; + } + return err; +} + +extern int ZEXPORT zipClose (zipFile file, const char* global_comment) +{ + zip64_internal* zi; + int err = 0; + uLong size_centraldir = 0; + ZPOS64_T centraldir_pos_inzip; + ZPOS64_T pos; + + if (file == NULL) + return ZIP_PARAMERROR; + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if (global_comment==NULL) + global_comment = zi->globalcomment; +#endif + + centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (err==ZIP_OK) + { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block; + while (ldi!=NULL) + { + if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block) + err = ZIP_ERRNO; + } + + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_linkedlist(&(zi->central_dir)); + + pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + if(pos >= 0xffffffff || zi->number_entry > 0xFFFF) + { + ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); + Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos); + } + + if (err==ZIP_OK) + err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + if(err == ZIP_OK) + err = Write_GlobalComment(zi, global_comment); + + if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0) + if (err == ZIP_OK) + err = ZIP_ERRNO; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(zi->globalcomment); +#endif + TRYFREE(zi); + + return err; +} + +extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader) +{ + char* p = pData; + int size = 0; + char* pNewHeader; + char* pTmp; + short header; + short dataSize; + + int retVal = ZIP_OK; + + if(pData == NULL || *dataLen < 4) + return ZIP_PARAMERROR; + + pNewHeader = (char*)ALLOC(*dataLen); + pTmp = pNewHeader; + + while(p < (pData + *dataLen)) + { + header = *(short*)p; + dataSize = *(((short*)p)+1); + + if( header == sHeader ) // Header found. + { + p += dataSize + 4; // skip it. do not copy to temp buffer + } + else + { + // Extra Info block should not be removed, So copy it to the temp buffer. + memcpy(pTmp, p, dataSize + 4); + p += dataSize + 4; + size += dataSize + 4; + } + + } + + if(size < *dataLen) + { + // clean old extra info block. + memset(pData,0, *dataLen); + + // copy the new extra info block over the old + if(size > 0) + memcpy(pData, pNewHeader, size); + + // set the new extra info size + *dataLen = size; + + retVal = ZIP_OK; + } + else + retVal = ZIP_ERRNO; + + TRYFREE(pNewHeader); + + return retVal; +} diff --git a/zlib/zlib/contrib/minizip/zip.h b/zlib/zlib/contrib/minizip/zip.h new file mode 100644 index 00000000..8aaebb62 --- /dev/null +++ b/zlib/zlib/contrib/minizip/zip.h @@ -0,0 +1,362 @@ +/* zip.h -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------- + + Changes + + See header of zip.h + +*/ + +#ifndef _zip12_H +#define _zip12_H + +#ifdef __cplusplus +extern "C" { +#endif + +//#define HAVE_BZIP2 + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append)); +extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append)); +/* + Create a zipfile. + pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on + an Unix computer "zlib/zlib113.zip". + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((const char *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc64_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); + +extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int zip64)); + +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) + zip64 is set to 1 if a zip64 extended information block should be added to the local file header. + this MUST be '1' if the uncompressed size is >= 0xffffffff. + +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + + +extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int zip64)); +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting)); + +extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + int zip64 + )); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCrypting : crc of file to compress (needed for crypting) + */ + +extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase + )); + + +extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase, + int zip64 + )); +/* + Same than zipOpenNewFileInZip4, except + versionMadeBy : value for Version made by field + flag : value for flag field (compression level info will be added) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); + +extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file, + ZPOS64_T uncompressed_size, + uLong crc32)); + +/* + Close the current file in the zipfile, for file opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + + +extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader)); +/* + zipRemoveExtraInfoBlock - Added by Mathias Svensson + + Remove extra information block from a extra information data for the local file header or central directory header + + It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode. + + 0x0001 is the signature header for the ZIP64 extra information blocks + + usage. + Remove ZIP64 Extra information from a central director extra field data + zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001); + + Remove ZIP64 Extra information from a Local File Header extra field data + zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _zip64_H */ diff --git a/zlib/zlib/contrib/pascal/example.pas b/zlib/zlib/contrib/pascal/example.pas new file mode 100644 index 00000000..5518b36a --- /dev/null +++ b/zlib/zlib/contrib/pascal/example.pas @@ -0,0 +1,599 @@ +(* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Pascal translation + * Copyright (C) 1998 by Jacques Nomssi Nzali. + * For conditions of distribution and use, see copyright notice in readme.txt + * + * Adaptation to the zlibpas interface + * Copyright (C) 2003 by Cosmin Truta. + * For conditions of distribution and use, see copyright notice in readme.txt + *) + +program example; + +{$DEFINE TEST_COMPRESS} +{DO NOT $DEFINE TEST_GZIO} +{$DEFINE TEST_DEFLATE} +{$DEFINE TEST_INFLATE} +{$DEFINE TEST_FLUSH} +{$DEFINE TEST_SYNC} +{$DEFINE TEST_DICT} + +uses SysUtils, zlibpas; + +const TESTFILE = 'foo.gz'; + +(* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + *) +const hello: PChar = 'hello, hello!'; + +const dictionary: PChar = 'hello'; + +var dictId: LongInt; (* Adler32 value of the dictionary *) + +procedure CHECK_ERR(err: Integer; msg: String); +begin + if err <> Z_OK then + begin + WriteLn(msg, ' error: ', err); + Halt(1); + end; +end; + +procedure EXIT_ERR(const msg: String); +begin + WriteLn('Error: ', msg); + Halt(1); +end; + +(* =========================================================================== + * Test compress and uncompress + *) +{$IFDEF TEST_COMPRESS} +procedure test_compress(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + len: LongInt; +begin + len := StrLen(hello)+1; + + err := compress(compr, comprLen, hello, len); + CHECK_ERR(err, 'compress'); + + StrCopy(PChar(uncompr), 'garbage'); + + err := uncompress(uncompr, uncomprLen, compr, comprLen); + CHECK_ERR(err, 'uncompress'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad uncompress') + else + WriteLn('uncompress(): ', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test read/write of .gz files + *) +{$IFDEF TEST_GZIO} +procedure test_gzio(const fname: PChar; (* compressed file name *) + uncompr: Pointer; + uncomprLen: LongInt); +var err: Integer; + len: Integer; + zfile: gzFile; + pos: LongInt; +begin + len := StrLen(hello)+1; + + zfile := gzopen(fname, 'wb'); + if zfile = NIL then + begin + WriteLn('gzopen error'); + Halt(1); + end; + gzputc(zfile, 'h'); + if gzputs(zfile, 'ello') <> 4 then + begin + WriteLn('gzputs err: ', gzerror(zfile, err)); + Halt(1); + end; + {$IFDEF GZ_FORMAT_STRING} + if gzprintf(zfile, ', %s!', 'hello') <> 8 then + begin + WriteLn('gzprintf err: ', gzerror(zfile, err)); + Halt(1); + end; + {$ELSE} + if gzputs(zfile, ', hello!') <> 8 then + begin + WriteLn('gzputs err: ', gzerror(zfile, err)); + Halt(1); + end; + {$ENDIF} + gzseek(zfile, 1, SEEK_CUR); (* add one zero byte *) + gzclose(zfile); + + zfile := gzopen(fname, 'rb'); + if zfile = NIL then + begin + WriteLn('gzopen error'); + Halt(1); + end; + + StrCopy(PChar(uncompr), 'garbage'); + + if gzread(zfile, uncompr, uncomprLen) <> len then + begin + WriteLn('gzread err: ', gzerror(zfile, err)); + Halt(1); + end; + if StrComp(PChar(uncompr), hello) <> 0 then + begin + WriteLn('bad gzread: ', PChar(uncompr)); + Halt(1); + end + else + WriteLn('gzread(): ', PChar(uncompr)); + + pos := gzseek(zfile, -8, SEEK_CUR); + if (pos <> 6) or (gztell(zfile) <> pos) then + begin + WriteLn('gzseek error, pos=', pos, ', gztell=', gztell(zfile)); + Halt(1); + end; + + if gzgetc(zfile) <> ' ' then + begin + WriteLn('gzgetc error'); + Halt(1); + end; + + if gzungetc(' ', zfile) <> ' ' then + begin + WriteLn('gzungetc error'); + Halt(1); + end; + + gzgets(zfile, PChar(uncompr), uncomprLen); + uncomprLen := StrLen(PChar(uncompr)); + if uncomprLen <> 7 then (* " hello!" *) + begin + WriteLn('gzgets err after gzseek: ', gzerror(zfile, err)); + Halt(1); + end; + if StrComp(PChar(uncompr), hello + 6) <> 0 then + begin + WriteLn('bad gzgets after gzseek'); + Halt(1); + end + else + WriteLn('gzgets() after gzseek: ', PChar(uncompr)); + + gzclose(zfile); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with small buffers + *) +{$IFDEF TEST_DEFLATE} +procedure test_deflate(compr: Pointer; comprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; + len: LongInt; +begin + len := StrLen(hello)+1; + + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_in := hello; + c_stream.next_out := compr; + + while (c_stream.total_in <> len) and + (c_stream.total_out < comprLen) do + begin + c_stream.avail_out := 1; { force small buffers } + c_stream.avail_in := 1; + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + end; + + (* Finish the stream, still forcing small buffers: *) + while TRUE do + begin + c_stream.avail_out := 1; + err := deflate(c_stream, Z_FINISH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'deflate'); + end; + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with small buffers + *) +{$IFDEF TEST_INFLATE} +procedure test_inflate(compr: Pointer; comprLen : LongInt; + uncompr: Pointer; uncomprLen : LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := 0; + d_stream.next_out := uncompr; + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + while (d_stream.total_out < uncomprLen) and + (d_stream.total_in < comprLen) do + begin + d_stream.avail_out := 1; (* force small buffers *) + d_stream.avail_in := 1; + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'inflate'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad inflate') + else + WriteLn('inflate(): ', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with large buffers and dynamic change of compression level + *) +{$IFDEF TEST_DEFLATE} +procedure test_large_deflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; +begin + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_BEST_SPEED); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_out := compr; + c_stream.avail_out := Integer(comprLen); + + (* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + *) + c_stream.next_in := uncompr; + c_stream.avail_in := Integer(uncomprLen); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + if c_stream.avail_in <> 0 then + EXIT_ERR('deflate not greedy'); + + (* Feed in already compressed data and switch to no compression: *) + deflateParams(c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in := compr; + c_stream.avail_in := Integer(comprLen div 2); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + + (* Switch back to compressing mode: *) + deflateParams(c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in := uncompr; + c_stream.avail_in := Integer(uncomprLen); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + EXIT_ERR('deflate should report Z_STREAM_END'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with large buffers + *) +{$IFDEF TEST_INFLATE} +procedure test_large_inflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := Integer(comprLen); + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + while TRUE do + begin + d_stream.next_out := uncompr; (* discard the output *) + d_stream.avail_out := Integer(uncomprLen); + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'large inflate'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if d_stream.total_out <> 2 * uncomprLen + comprLen div 2 then + begin + WriteLn('bad large inflate: ', d_stream.total_out); + Halt(1); + end + else + WriteLn('large_inflate(): OK'); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with full flush + *) +{$IFDEF TEST_FLUSH} +procedure test_flush(compr: Pointer; var comprLen : LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; + len: Integer; +begin + len := StrLen(hello)+1; + + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_in := hello; + c_stream.next_out := compr; + c_stream.avail_in := 3; + c_stream.avail_out := Integer(comprLen); + err := deflate(c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, 'deflate'); + + Inc(PByteArray(compr)^[3]); (* force an error in first compressed block *) + c_stream.avail_in := len - 3; + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + CHECK_ERR(err, 'deflate'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); + + comprLen := c_stream.total_out; +end; +{$ENDIF} + +(* =========================================================================== + * Test inflateSync() + *) +{$IFDEF TEST_SYNC} +procedure test_sync(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen : LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := 2; (* just read the zlib header *) + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + d_stream.next_out := uncompr; + d_stream.avail_out := Integer(uncomprLen); + + inflate(d_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'inflate'); + + d_stream.avail_in := Integer(comprLen-2); (* read all compressed data *) + err := inflateSync(d_stream); (* but skip the damaged part *) + CHECK_ERR(err, 'inflateSync'); + + err := inflate(d_stream, Z_FINISH); + if err <> Z_DATA_ERROR then + EXIT_ERR('inflate should report DATA_ERROR'); + (* Because of incorrect adler32 *) + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + WriteLn('after inflateSync(): hel', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with preset dictionary + *) +{$IFDEF TEST_DICT} +procedure test_dict_deflate(compr: Pointer; comprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; +begin + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + err := deflateSetDictionary(c_stream, dictionary, StrLen(dictionary)); + CHECK_ERR(err, 'deflateSetDictionary'); + + dictId := c_stream.adler; + c_stream.next_out := compr; + c_stream.avail_out := Integer(comprLen); + + c_stream.next_in := hello; + c_stream.avail_in := StrLen(hello)+1; + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + EXIT_ERR('deflate should report Z_STREAM_END'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with a preset dictionary + *) +{$IFDEF TEST_DICT} +procedure test_dict_inflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := Integer(comprLen); + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + d_stream.next_out := uncompr; + d_stream.avail_out := Integer(uncomprLen); + + while TRUE do + begin + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + if err = Z_NEED_DICT then + begin + if d_stream.adler <> dictId then + EXIT_ERR('unexpected dictionary'); + err := inflateSetDictionary(d_stream, dictionary, StrLen(dictionary)); + end; + CHECK_ERR(err, 'inflate with dict'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad inflate with dict') + else + WriteLn('inflate with dictionary: ', PChar(uncompr)); +end; +{$ENDIF} + +var compr, uncompr: Pointer; + comprLen, uncomprLen: LongInt; + +begin + if zlibVersion^ <> ZLIB_VERSION[1] then + EXIT_ERR('Incompatible zlib version'); + + WriteLn('zlib version: ', zlibVersion); + WriteLn('zlib compile flags: ', Format('0x%x', [zlibCompileFlags])); + + comprLen := 10000 * SizeOf(Integer); (* don't overflow on MSDOS *) + uncomprLen := comprLen; + GetMem(compr, comprLen); + GetMem(uncompr, uncomprLen); + if (compr = NIL) or (uncompr = NIL) then + EXIT_ERR('Out of memory'); + (* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + *) + FillChar(compr^, comprLen, 0); + FillChar(uncompr^, uncomprLen, 0); + + {$IFDEF TEST_COMPRESS} + WriteLn('** Testing compress'); + test_compress(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_GZIO} + WriteLn('** Testing gzio'); + if ParamCount >= 1 then + test_gzio(ParamStr(1), uncompr, uncomprLen) + else + test_gzio(TESTFILE, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_DEFLATE} + WriteLn('** Testing deflate with small buffers'); + test_deflate(compr, comprLen); + {$ENDIF} + {$IFDEF TEST_INFLATE} + WriteLn('** Testing inflate with small buffers'); + test_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_DEFLATE} + WriteLn('** Testing deflate with large buffers'); + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + {$IFDEF TEST_INFLATE} + WriteLn('** Testing inflate with large buffers'); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_FLUSH} + WriteLn('** Testing deflate with full flush'); + test_flush(compr, comprLen); + {$ENDIF} + {$IFDEF TEST_SYNC} + WriteLn('** Testing inflateSync'); + test_sync(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + comprLen := uncomprLen; + + {$IFDEF TEST_DICT} + WriteLn('** Testing deflate and inflate with preset dictionary'); + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + FreeMem(compr, comprLen); + FreeMem(uncompr, uncomprLen); +end. diff --git a/zlib/zlib/contrib/pascal/readme.txt b/zlib/zlib/contrib/pascal/readme.txt new file mode 100644 index 00000000..60e87c8a --- /dev/null +++ b/zlib/zlib/contrib/pascal/readme.txt @@ -0,0 +1,76 @@ + +This directory contains a Pascal (Delphi, Kylix) interface to the +zlib data compression library. + + +Directory listing +================= + +zlibd32.mak makefile for Borland C++ +example.pas usage example of zlib +zlibpas.pas the Pascal interface to zlib +readme.txt this file + + +Compatibility notes +=================== + +- Although the name "zlib" would have been more normal for the + zlibpas unit, this name is already taken by Borland's ZLib unit. + This is somehow unfortunate, because that unit is not a genuine + interface to the full-fledged zlib functionality, but a suite of + class wrappers around zlib streams. Other essential features, + such as checksums, are missing. + It would have been more appropriate for that unit to have a name + like "ZStreams", or something similar. + +- The C and zlib-supplied types int, uInt, long, uLong, etc. are + translated directly into Pascal types of similar sizes (Integer, + LongInt, etc.), to avoid namespace pollution. In particular, + there is no conversion of unsigned int into a Pascal unsigned + integer. The Word type is non-portable and has the same size + (16 bits) both in a 16-bit and in a 32-bit environment, unlike + Integer. Even if there is a 32-bit Cardinal type, there is no + real need for unsigned int in zlib under a 32-bit environment. + +- Except for the callbacks, the zlib function interfaces are + assuming the calling convention normally used in Pascal + (__pascal for DOS and Windows16, __fastcall for Windows32). + Since the cdecl keyword is used, the old Turbo Pascal does + not work with this interface. + +- The gz* function interfaces are not translated, to avoid + interfacing problems with the C runtime library. Besides, + gzprintf(gzFile file, const char *format, ...) + cannot be translated into Pascal. + + +Legal issues +============ + +The zlibpas interface is: + Copyright (C) 1995-2003 Jean-loup Gailly and Mark Adler. + Copyright (C) 1998 by Bob Dellaca. + Copyright (C) 2003 by Cosmin Truta. + +The example program is: + Copyright (C) 1995-2003 by Jean-loup Gailly. + Copyright (C) 1998,1999,2000 by Jacques Nomssi Nzali. + Copyright (C) 2003 by Cosmin Truta. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + diff --git a/zlib/zlib/contrib/pascal/zlibd32.mak b/zlib/zlib/contrib/pascal/zlibd32.mak new file mode 100644 index 00000000..9bb00b7c --- /dev/null +++ b/zlib/zlib/contrib/pascal/zlibd32.mak @@ -0,0 +1,99 @@ +# Makefile for zlib +# For use with Delphi and C++ Builder under Win32 +# Updated for zlib 1.2.x by Cosmin Truta + +# ------------ Borland C++ ------------ + +# This project uses the Delphi (fastcall/register) calling convention: +LOC = -DZEXPORT=__fastcall -DZEXPORTVA=__cdecl + +CC = bcc32 +LD = bcc32 +AR = tlib +# do not use "-pr" in CFLAGS +CFLAGS = -a -d -k- -O2 $(LOC) +LDFLAGS = + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del *.obj + -del *.exe + -del *.lib + -del *.tds + -del zlib.bak + -del foo.gz + diff --git a/zlib/zlib/contrib/pascal/zlibpas.pas b/zlib/zlib/contrib/pascal/zlibpas.pas new file mode 100644 index 00000000..e6a0782b --- /dev/null +++ b/zlib/zlib/contrib/pascal/zlibpas.pas @@ -0,0 +1,276 @@ +(* zlibpas -- Pascal interface to the zlib data compression library + * + * Copyright (C) 2003 Cosmin Truta. + * Derived from original sources by Bob Dellaca. + * For conditions of distribution and use, see copyright notice in readme.txt + *) + +unit zlibpas; + +interface + +const + ZLIB_VERSION = '1.2.8'; + ZLIB_VERNUM = $1280; + +type + alloc_func = function(opaque: Pointer; items, size: Integer): Pointer; + cdecl; + free_func = procedure(opaque, address: Pointer); + cdecl; + + in_func = function(opaque: Pointer; var buf: PByte): Integer; + cdecl; + out_func = function(opaque: Pointer; buf: PByte; size: Integer): Integer; + cdecl; + + z_streamp = ^z_stream; + z_stream = packed record + next_in: PChar; (* next input byte *) + avail_in: Integer; (* number of bytes available at next_in *) + total_in: LongInt; (* total nb of input bytes read so far *) + + next_out: PChar; (* next output byte should be put there *) + avail_out: Integer; (* remaining free space at next_out *) + total_out: LongInt; (* total nb of bytes output so far *) + + msg: PChar; (* last error message, NULL if no error *) + state: Pointer; (* not visible by applications *) + + zalloc: alloc_func; (* used to allocate the internal state *) + zfree: free_func; (* used to free the internal state *) + opaque: Pointer; (* private data object passed to zalloc and zfree *) + + data_type: Integer; (* best guess about the data type: ascii or binary *) + adler: LongInt; (* adler32 value of the uncompressed data *) + reserved: LongInt; (* reserved for future use *) + end; + + gz_headerp = ^gz_header; + gz_header = packed record + text: Integer; (* true if compressed data believed to be text *) + time: LongInt; (* modification time *) + xflags: Integer; (* extra flags (not used when writing a gzip file) *) + os: Integer; (* operating system *) + extra: PChar; (* pointer to extra field or Z_NULL if none *) + extra_len: Integer; (* extra field length (valid if extra != Z_NULL) *) + extra_max: Integer; (* space at extra (only when reading header) *) + name: PChar; (* pointer to zero-terminated file name or Z_NULL *) + name_max: Integer; (* space at name (only when reading header) *) + comment: PChar; (* pointer to zero-terminated comment or Z_NULL *) + comm_max: Integer; (* space at comment (only when reading header) *) + hcrc: Integer; (* true if there was or will be a header crc *) + done: Integer; (* true when done reading gzip header *) + end; + +(* constants *) +const + Z_NO_FLUSH = 0; + Z_PARTIAL_FLUSH = 1; + Z_SYNC_FLUSH = 2; + Z_FULL_FLUSH = 3; + Z_FINISH = 4; + Z_BLOCK = 5; + Z_TREES = 6; + + Z_OK = 0; + Z_STREAM_END = 1; + Z_NEED_DICT = 2; + Z_ERRNO = -1; + Z_STREAM_ERROR = -2; + Z_DATA_ERROR = -3; + Z_MEM_ERROR = -4; + Z_BUF_ERROR = -5; + Z_VERSION_ERROR = -6; + + Z_NO_COMPRESSION = 0; + Z_BEST_SPEED = 1; + Z_BEST_COMPRESSION = 9; + Z_DEFAULT_COMPRESSION = -1; + + Z_FILTERED = 1; + Z_HUFFMAN_ONLY = 2; + Z_RLE = 3; + Z_FIXED = 4; + Z_DEFAULT_STRATEGY = 0; + + Z_BINARY = 0; + Z_TEXT = 1; + Z_ASCII = 1; + Z_UNKNOWN = 2; + + Z_DEFLATED = 8; + +(* basic functions *) +function zlibVersion: PChar; +function deflateInit(var strm: z_stream; level: Integer): Integer; +function deflate(var strm: z_stream; flush: Integer): Integer; +function deflateEnd(var strm: z_stream): Integer; +function inflateInit(var strm: z_stream): Integer; +function inflate(var strm: z_stream; flush: Integer): Integer; +function inflateEnd(var strm: z_stream): Integer; + +(* advanced functions *) +function deflateInit2(var strm: z_stream; level, method, windowBits, + memLevel, strategy: Integer): Integer; +function deflateSetDictionary(var strm: z_stream; const dictionary: PChar; + dictLength: Integer): Integer; +function deflateCopy(var dest, source: z_stream): Integer; +function deflateReset(var strm: z_stream): Integer; +function deflateParams(var strm: z_stream; level, strategy: Integer): Integer; +function deflateTune(var strm: z_stream; good_length, max_lazy, nice_length, max_chain: Integer): Integer; +function deflateBound(var strm: z_stream; sourceLen: LongInt): LongInt; +function deflatePending(var strm: z_stream; var pending: Integer; var bits: Integer): Integer; +function deflatePrime(var strm: z_stream; bits, value: Integer): Integer; +function deflateSetHeader(var strm: z_stream; head: gz_header): Integer; +function inflateInit2(var strm: z_stream; windowBits: Integer): Integer; +function inflateSetDictionary(var strm: z_stream; const dictionary: PChar; + dictLength: Integer): Integer; +function inflateSync(var strm: z_stream): Integer; +function inflateCopy(var dest, source: z_stream): Integer; +function inflateReset(var strm: z_stream): Integer; +function inflateReset2(var strm: z_stream; windowBits: Integer): Integer; +function inflatePrime(var strm: z_stream; bits, value: Integer): Integer; +function inflateMark(var strm: z_stream): LongInt; +function inflateGetHeader(var strm: z_stream; var head: gz_header): Integer; +function inflateBackInit(var strm: z_stream; + windowBits: Integer; window: PChar): Integer; +function inflateBack(var strm: z_stream; in_fn: in_func; in_desc: Pointer; + out_fn: out_func; out_desc: Pointer): Integer; +function inflateBackEnd(var strm: z_stream): Integer; +function zlibCompileFlags: LongInt; + +(* utility functions *) +function compress(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt): Integer; +function compress2(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt; + level: Integer): Integer; +function compressBound(sourceLen: LongInt): LongInt; +function uncompress(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt): Integer; + +(* checksum functions *) +function adler32(adler: LongInt; const buf: PChar; len: Integer): LongInt; +function adler32_combine(adler1, adler2, len2: LongInt): LongInt; +function crc32(crc: LongInt; const buf: PChar; len: Integer): LongInt; +function crc32_combine(crc1, crc2, len2: LongInt): LongInt; + +(* various hacks, don't look :) *) +function deflateInit_(var strm: z_stream; level: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateInit_(var strm: z_stream; const version: PChar; + stream_size: Integer): Integer; +function deflateInit2_(var strm: z_stream; + level, method, windowBits, memLevel, strategy: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateInit2_(var strm: z_stream; windowBits: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateBackInit_(var strm: z_stream; + windowBits: Integer; window: PChar; + const version: PChar; stream_size: Integer): Integer; + + +implementation + +{$L adler32.obj} +{$L compress.obj} +{$L crc32.obj} +{$L deflate.obj} +{$L infback.obj} +{$L inffast.obj} +{$L inflate.obj} +{$L inftrees.obj} +{$L trees.obj} +{$L uncompr.obj} +{$L zutil.obj} + +function adler32; external; +function adler32_combine; external; +function compress; external; +function compress2; external; +function compressBound; external; +function crc32; external; +function crc32_combine; external; +function deflate; external; +function deflateBound; external; +function deflateCopy; external; +function deflateEnd; external; +function deflateInit_; external; +function deflateInit2_; external; +function deflateParams; external; +function deflatePending; external; +function deflatePrime; external; +function deflateReset; external; +function deflateSetDictionary; external; +function deflateSetHeader; external; +function deflateTune; external; +function inflate; external; +function inflateBack; external; +function inflateBackEnd; external; +function inflateBackInit_; external; +function inflateCopy; external; +function inflateEnd; external; +function inflateGetHeader; external; +function inflateInit_; external; +function inflateInit2_; external; +function inflateMark; external; +function inflatePrime; external; +function inflateReset; external; +function inflateReset2; external; +function inflateSetDictionary; external; +function inflateSync; external; +function uncompress; external; +function zlibCompileFlags; external; +function zlibVersion; external; + +function deflateInit(var strm: z_stream; level: Integer): Integer; +begin + Result := deflateInit_(strm, level, ZLIB_VERSION, sizeof(z_stream)); +end; + +function deflateInit2(var strm: z_stream; level, method, windowBits, memLevel, + strategy: Integer): Integer; +begin + Result := deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateInit(var strm: z_stream): Integer; +begin + Result := inflateInit_(strm, ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateInit2(var strm: z_stream; windowBits: Integer): Integer; +begin + Result := inflateInit2_(strm, windowBits, ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateBackInit(var strm: z_stream; + windowBits: Integer; window: PChar): Integer; +begin + Result := inflateBackInit_(strm, windowBits, window, + ZLIB_VERSION, sizeof(z_stream)); +end; + +function _malloc(Size: Integer): Pointer; cdecl; +begin + GetMem(Result, Size); +end; + +procedure _free(Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl; +begin + FillChar(P^, count, B); +end; + +procedure _memcpy(dest, source: Pointer; count: Integer); cdecl; +begin + Move(source^, dest^, count); +end; + +end. diff --git a/zlib/zlib/contrib/puff/Makefile b/zlib/zlib/contrib/puff/Makefile new file mode 100644 index 00000000..0e2594c8 --- /dev/null +++ b/zlib/zlib/contrib/puff/Makefile @@ -0,0 +1,42 @@ +CFLAGS=-O + +puff: puff.o pufftest.o + +puff.o: puff.h + +pufftest.o: puff.h + +test: puff + puff zeros.raw + +puft: puff.c puff.h pufftest.o + cc -fprofile-arcs -ftest-coverage -o puft puff.c pufftest.o + +# puff full coverage test (should say 100%) +cov: puft + @rm -f *.gcov *.gcda + @puft -w zeros.raw 2>&1 | cat > /dev/null + @echo '04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '00 00 00 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 254 + @echo '00 01 00 fe ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '01 01 00 fe ff 0a' | xxd -r -p | puft -f 2>&1 | cat > /dev/null + @echo '02 7e ff ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '02' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '04 80 49 92 24 49 92 24 0f b4 ff ff c3 04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '04 80 49 92 24 49 92 24 71 ff ff 93 11 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 249 + @echo '04 c0 81 08 00 00 00 00 20 7f eb 0b 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '0b 00 00' | xxd -r -p | puft -f 2>&1 | cat > /dev/null + @echo '1a 07' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '0c c0 81 00 00 00 00 00 90 ff 6b 04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 245 + @puft -f zeros.raw 2>&1 | cat > /dev/null + @echo 'fc 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 253 + @echo '04 00 fe ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 252 + @echo '04 00 24 49' | xxd -r -p | puft 2> /dev/null || test $$? -eq 251 + @echo '04 80 49 92 24 49 92 24 0f b4 ff ff c3 84' | xxd -r -p | puft 2> /dev/null || test $$? -eq 248 + @echo '04 00 24 e9 ff ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 250 + @echo '04 00 24 e9 ff 6d' | xxd -r -p | puft 2> /dev/null || test $$? -eq 247 + @gcov -n puff.c + +clean: + rm -f puff puft *.o *.gc* diff --git a/zlib/zlib/contrib/puff/README b/zlib/zlib/contrib/puff/README new file mode 100644 index 00000000..bbc4cb59 --- /dev/null +++ b/zlib/zlib/contrib/puff/README @@ -0,0 +1,63 @@ +Puff -- A Simple Inflate +3 Mar 2003 +Mark Adler +madler@alumni.caltech.edu + +What this is -- + +puff.c provides the routine puff() to decompress the deflate data format. It +does so more slowly than zlib, but the code is about one-fifth the size of the +inflate code in zlib, and written to be very easy to read. + +Why I wrote this -- + +puff.c was written to document the deflate format unambiguously, by virtue of +being working C code. It is meant to supplement RFC 1951, which formally +describes the deflate format. I have received many questions on details of the +deflate format, and I hope that reading this code will answer those questions. +puff.c is heavily commented with details of the deflate format, especially +those little nooks and cranies of the format that might not be obvious from a +specification. + +puff.c may also be useful in applications where code size or memory usage is a +very limited resource, and speed is not as important. + +How to use it -- + +Well, most likely you should just be reading puff.c and using zlib for actual +applications, but if you must ... + +Include puff.h in your code, which provides this prototype: + +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ + +Then you can call puff() to decompress a deflate stream that is in memory in +its entirety at source, to a sufficiently sized block of memory for the +decompressed data at dest. puff() is the only external symbol in puff.c The +only C library functions that puff.c needs are setjmp() and longjmp(), which +are used to simplify error checking in the code to improve readabilty. puff.c +does no memory allocation, and uses less than 2K bytes off of the stack. + +If destlen is not enough space for the uncompressed data, then inflate will +return an error without writing more than destlen bytes. Note that this means +that in order to decompress the deflate data successfully, you need to know +the size of the uncompressed data ahead of time. + +If needed, puff() can determine the size of the uncompressed data with no +output space. This is done by passing dest equal to (unsigned char *)0. Then +the initial value of *destlen is ignored and *destlen is set to the length of +the uncompressed data. So if the size of the uncompressed data is not known, +then two passes of puff() can be used--first to determine the size, and second +to do the actual inflation after allocating the appropriate memory. Not +pretty, but it works. (This is one of the reasons you should be using zlib.) + +The deflate format is self-terminating. If the deflate stream does not end +in *sourcelen bytes, puff() will return an error without reading at or past +endsource. + +On return, *sourcelen is updated to the amount of input data consumed, and +*destlen is updated to the size of the uncompressed data. See the comments +in puff.c for the possible return codes for puff(). diff --git a/zlib/zlib/contrib/puff/puff.c b/zlib/zlib/contrib/puff/puff.c new file mode 100644 index 00000000..ba58483d --- /dev/null +++ b/zlib/zlib/contrib/puff/puff.c @@ -0,0 +1,840 @@ +/* + * puff.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readabilty + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Gailly] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland] + * - Catch missing end-of-block symbol error + * 2.0 25 Jul 2008 - Add #define to permit distance too far back + * - Add option in TEST code for puff to write the data + * - Add option in TEST code to skip input bytes + * - Allow TEST code to read from piped stdin + * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers + * - Avoid unsigned comparisons for even happier compilers + * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer] + * - Add const where appropriate [Oberhumer] + * - Split if's and ?'s for coverage testing + * - Break out test code to separate file + * - Move NIL to puff.h + * - Allow incomplete code only if single code length is 1 + * - Add full code coverage test to Makefile + * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks + */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "puff.h" /* prototype for puff() */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + unsigned char *out; /* output buffer */ + unsigned long outlen; /* available space at out */ + unsigned long outcnt; /* bytes written to out so far */ + + /* input state */ + const unsigned char *in; /* input buffer */ + unsigned long inlen; /* available input at in */ + unsigned long incnt; /* bytes read so far */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + long val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return (int)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int stored(struct state *s) +{ + unsigned len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) + return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) + return 2; /* not enough input */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -10 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +#ifdef SLOW +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + + code = first = index = 0; + for (len = 1; len <= MAXBITS; len++) { + code |= bits(s, 1); /* get next bit */ + count = h->count[len]; + if (code - count < first) /* if length len, return symbol */ + return h->symbol[index + (code - first)]; + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + } + return -10; /* ran out of codes */ +} + +/* + * A faster version of decode() for real applications of this code. It's not + * as readable, but it makes puff() twice as fast. And it only makes the code + * a few percent larger. + */ +#else /* !SLOW */ +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code - count < first) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) + break; + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) + left = 8; + } + return -10; /* ran out of codes */ +} +#endif /* SLOW */ + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int construct(struct huffman *h, const short *length, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) + return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int codes(struct state *s, + const struct huffman *lencode, + const struct huffman *distcode) +{ + int symbol; /* decoded symbol */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + static const short lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const short lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const short dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const short dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != NIL) { + if (s->outcnt == s->outlen) + return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) + return -10; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) + return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); +#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (dist > s->outcnt) + return -11; /* distance too far back */ +#endif + + /* copy length bytes from distance bytes back */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; + while (len--) { + s->out[s->outcnt] = +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + dist > s->outcnt ? + 0 : +#endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int fixed(struct state *s) +{ + static int virgin = 1; + static short lencnt[MAXBITS+1], lensym[FIXLCODES]; + static short distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode, distcode; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int symbol; + short lengths[FIXLCODES]; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + * as the code length. This does not mean a zero-length code, but rather + * that no code should be created for this symbol. There is no way in the + * deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are follwed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int dynamic(struct state *s) +{ + int nlen, ndist, ncode; /* number of lengths in descriptor */ + int index; /* index of lengths[] */ + int err; /* construct() return value */ + short lengths[MAXCODES]; /* descriptor code lengths */ + short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode, distcode; /* length and distance codes */ + static const short order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) /* require complete code set here */ + return -4; + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int symbol; /* decoded value */ + int len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) + return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* check for end-of-block code -- there better be one! */ + if (lengths[256] == 0) + return -9; + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1])) + return -7; /* incomplete code ok only for single length 1 code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1])) + return -8; /* incomplete code ok only for single length 1 code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (unsigned char *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: dynamic block code description: missing end-of-block code + * -10: invalid literal/length or distance code in fixed or dynamic block + * -11: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int last, type; /* block information */ + int err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NIL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? + stored(&s) : + (type == 1 ? + fixed(&s) : + (type == 2 ? + dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) + break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} diff --git a/zlib/zlib/contrib/puff/puff.h b/zlib/zlib/contrib/puff/puff.h new file mode 100644 index 00000000..e23a2454 --- /dev/null +++ b/zlib/zlib/contrib/puff/puff.h @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002-2013 Mark Adler, all rights reserved + version 2.3, 21 Jan 2013 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + + +/* + * See puff.c for purpose and usage. + */ +#ifndef NIL +# define NIL ((unsigned char *)0) /* for no output option */ +#endif + +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ diff --git a/zlib/zlib/contrib/puff/pufftest.c b/zlib/zlib/contrib/puff/pufftest.c new file mode 100644 index 00000000..77648148 --- /dev/null +++ b/zlib/zlib/contrib/puff/pufftest.c @@ -0,0 +1,165 @@ +/* + * pufftest.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + */ + +/* Example of how to use puff(). + + Usage: puff [-w] [-f] [-nnn] file + ... | puff [-w] [-f] [-nnn] + + where file is the input file with deflate data, nnn is the number of bytes + of input to skip before inflating (e.g. to skip a zlib or gzip header), and + -w is used to write the decompressed data to stdout. -f is for coverage + testing, and causes pufftest to fail with not enough output space (-f does + a write like -w, so -w is not required). */ + +#include +#include +#include "puff.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define local static + +/* Return size times approximately the cube root of 2, keeping the result as 1, + 3, or 5 times a power of 2 -- the result is always > size, until the result + is the maximum value of an unsigned long, where it remains. This is useful + to keep reallocations less than ~33% over the actual data. */ +local size_t bythirds(size_t size) +{ + int n; + size_t m; + + m = size; + for (n = 0; m; n++) + m >>= 1; + if (n < 3) + return size + 1; + n -= 3; + m = size >> n; + m += m == 6 ? 2 : 1; + m <<= n; + return m > size ? m : (size_t)(-1); +} + +/* Read the input file *name, or stdin if name is NULL, into allocated memory. + Reallocate to larger buffers until the entire file is read in. Return a + pointer to the allocated data, or NULL if there was a memory allocation + failure. *len is the number of bytes of data read from the input file (even + if load() returns NULL). If the input file was empty or could not be opened + or read, *len is zero. */ +local void *load(const char *name, size_t *len) +{ + size_t size; + void *buf, *swap; + FILE *in; + + *len = 0; + buf = malloc(size = 4096); + if (buf == NULL) + return NULL; + in = name == NULL ? stdin : fopen(name, "rb"); + if (in != NULL) { + for (;;) { + *len += fread((char *)buf + *len, 1, size - *len, in); + if (*len < size) break; + size = bythirds(size); + if (size == *len || (swap = realloc(buf, size)) == NULL) { + free(buf); + buf = NULL; + break; + } + buf = swap; + } + fclose(in); + } + return buf; +} + +int main(int argc, char **argv) +{ + int ret, put = 0, fail = 0; + unsigned skip = 0; + char *arg, *name = NULL; + unsigned char *source = NULL, *dest; + size_t len = 0; + unsigned long sourcelen, destlen; + + /* process arguments */ + while (arg = *++argv, --argc) + if (arg[0] == '-') { + if (arg[1] == 'w' && arg[2] == 0) + put = 1; + else if (arg[1] == 'f' && arg[2] == 0) + fail = 1, put = 1; + else if (arg[1] >= '0' && arg[1] <= '9') + skip = (unsigned)atoi(arg + 1); + else { + fprintf(stderr, "invalid option %s\n", arg); + return 3; + } + } + else if (name != NULL) { + fprintf(stderr, "only one file name allowed\n"); + return 3; + } + else + name = arg; + source = load(name, &len); + if (source == NULL) { + fprintf(stderr, "memory allocation failure\n"); + return 4; + } + if (len == 0) { + fprintf(stderr, "could not read %s, or it was empty\n", + name == NULL ? "" : name); + free(source); + return 3; + } + if (skip >= len) { + fprintf(stderr, "skip request of %d leaves no input\n", skip); + free(source); + return 3; + } + + /* test inflate data with offset skip */ + len -= skip; + sourcelen = (unsigned long)len; + ret = puff(NIL, &destlen, source + skip, &sourcelen); + if (ret) + fprintf(stderr, "puff() failed with return code %d\n", ret); + else { + fprintf(stderr, "puff() succeeded uncompressing %lu bytes\n", destlen); + if (sourcelen < len) fprintf(stderr, "%lu compressed bytes unused\n", + len - sourcelen); + } + + /* if requested, inflate again and write decompressd data to stdout */ + if (put && ret == 0) { + if (fail) + destlen >>= 1; + dest = malloc(destlen); + if (dest == NULL) { + fprintf(stderr, "memory allocation failure\n"); + free(source); + return 4; + } + puff(dest, &destlen, source + skip, &sourcelen); + SET_BINARY_MODE(stdout); + fwrite(dest, 1, destlen, stdout); + free(dest); + } + + /* clean up */ + free(source); + return ret; +} diff --git a/zlib/zlib/contrib/puff/zeros.raw b/zlib/zlib/contrib/puff/zeros.raw new file mode 100644 index 00000000..0a90e76b Binary files /dev/null and b/zlib/zlib/contrib/puff/zeros.raw differ diff --git a/zlib/zlib/contrib/testzlib/testzlib.c b/zlib/zlib/contrib/testzlib/testzlib.c new file mode 100644 index 00000000..8626c92a --- /dev/null +++ b/zlib/zlib/contrib/testzlib/testzlib.c @@ -0,0 +1,275 @@ +#include +#include +#include + +#include "zlib.h" + + +void MyDoMinus64(LARGE_INTEGER *R,LARGE_INTEGER A,LARGE_INTEGER B) +{ + R->HighPart = A.HighPart - B.HighPart; + if (A.LowPart >= B.LowPart) + R->LowPart = A.LowPart - B.LowPart; + else + { + R->LowPart = A.LowPart - B.LowPart; + R->HighPart --; + } +} + +#ifdef _M_X64 +// see http://msdn2.microsoft.com/library/twchhe95(en-us,vs.80).aspx for __rdtsc +unsigned __int64 __rdtsc(void); +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ + // printf("rdtsc = %I64x\n",__rdtsc()); + pbeginTime64->QuadPart=__rdtsc(); +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER LIres; + unsigned _int64 res=__rdtsc()-((unsigned _int64)(beginTime64.QuadPart)); + LIres.QuadPart=res; + // printf("rdtsc = %I64x\n",__rdtsc()); + return LIres; +} +#else +#ifdef _M_IX86 +void myGetRDTSC32(LARGE_INTEGER * pbeginTime64) +{ + DWORD dwEdx,dwEax; + _asm + { + rdtsc + mov dwEax,eax + mov dwEdx,edx + } + pbeginTime64->LowPart=dwEax; + pbeginTime64->HighPart=dwEdx; +} + +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ + myGetRDTSC32(pbeginTime64); +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER LIres,endTime64; + myGetRDTSC32(&endTime64); + + LIres.LowPart=LIres.HighPart=0; + MyDoMinus64(&LIres,endTime64,beginTime64); + return LIres; +} +#else +void myGetRDTSC32(LARGE_INTEGER * pbeginTime64) +{ +} + +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER lr; + lr.QuadPart=0; + return lr; +} +#endif +#endif + +void BeginCountPerfCounter(LARGE_INTEGER * pbeginTime64,BOOL fComputeTimeQueryPerf) +{ + if ((!fComputeTimeQueryPerf) || (!QueryPerformanceCounter(pbeginTime64))) + { + pbeginTime64->LowPart = GetTickCount(); + pbeginTime64->HighPart = 0; + } +} + +DWORD GetMsecSincePerfCounter(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER endTime64,ticksPerSecond,ticks; + DWORDLONG ticksShifted,tickSecShifted; + DWORD dwLog=16+0; + DWORD dwRet; + if ((!fComputeTimeQueryPerf) || (!QueryPerformanceCounter(&endTime64))) + dwRet = (GetTickCount() - beginTime64.LowPart)*1; + else + { + MyDoMinus64(&ticks,endTime64,beginTime64); + QueryPerformanceFrequency(&ticksPerSecond); + + + { + ticksShifted = Int64ShrlMod32(*(DWORDLONG*)&ticks,dwLog); + tickSecShifted = Int64ShrlMod32(*(DWORDLONG*)&ticksPerSecond,dwLog); + + } + + dwRet = (DWORD)((((DWORD)ticksShifted)*1000)/(DWORD)(tickSecShifted)); + dwRet *=1; + } + return dwRet; +} + +int ReadFileMemory(const char* filename,long* plFileSize,unsigned char** pFilePtr) +{ + FILE* stream; + unsigned char* ptr; + int retVal=1; + stream=fopen(filename, "rb"); + if (stream==NULL) + return 0; + + fseek(stream,0,SEEK_END); + + *plFileSize=ftell(stream); + fseek(stream,0,SEEK_SET); + ptr=malloc((*plFileSize)+1); + if (ptr==NULL) + retVal=0; + else + { + if (fread(ptr, 1, *plFileSize,stream) != (*plFileSize)) + retVal=0; + } + fclose(stream); + *pFilePtr=ptr; + return retVal; +} + +int main(int argc, char *argv[]) +{ + int BlockSizeCompress=0x8000; + int BlockSizeUncompress=0x8000; + int cprLevel=Z_DEFAULT_COMPRESSION ; + long lFileSize; + unsigned char* FilePtr; + long lBufferSizeCpr; + long lBufferSizeUncpr; + long lCompressedSize=0; + unsigned char* CprPtr; + unsigned char* UncprPtr; + long lSizeCpr,lSizeUncpr; + DWORD dwGetTick,dwMsecQP; + LARGE_INTEGER li_qp,li_rdtsc,dwResRdtsc; + + if (argc<=1) + { + printf("run TestZlib [BlockSizeCompress] [BlockSizeUncompress] [compres. level]\n"); + return 0; + } + + if (ReadFileMemory(argv[1],&lFileSize,&FilePtr)==0) + { + printf("error reading %s\n",argv[1]); + return 1; + } + else printf("file %s read, %u bytes\n",argv[1],lFileSize); + + if (argc>=3) + BlockSizeCompress=atol(argv[2]); + + if (argc>=4) + BlockSizeUncompress=atol(argv[3]); + + if (argc>=5) + cprLevel=(int)atol(argv[4]); + + lBufferSizeCpr = lFileSize + (lFileSize/0x10) + 0x200; + lBufferSizeUncpr = lBufferSizeCpr; + + CprPtr=(unsigned char*)malloc(lBufferSizeCpr + BlockSizeCompress); + + BeginCountPerfCounter(&li_qp,TRUE); + dwGetTick=GetTickCount(); + BeginCountRdtsc(&li_rdtsc); + { + z_stream zcpr; + int ret=Z_OK; + long lOrigToDo = lFileSize; + long lOrigDone = 0; + int step=0; + memset(&zcpr,0,sizeof(z_stream)); + deflateInit(&zcpr,cprLevel); + + zcpr.next_in = FilePtr; + zcpr.next_out = CprPtr; + + + do + { + long all_read_before = zcpr.total_in; + zcpr.avail_in = min(lOrigToDo,BlockSizeCompress); + zcpr.avail_out = BlockSizeCompress; + ret=deflate(&zcpr,(zcpr.avail_in==lOrigToDo) ? Z_FINISH : Z_SYNC_FLUSH); + lOrigDone += (zcpr.total_in-all_read_before); + lOrigToDo -= (zcpr.total_in-all_read_before); + step++; + } while (ret==Z_OK); + + lSizeCpr=zcpr.total_out; + deflateEnd(&zcpr); + dwGetTick=GetTickCount()-dwGetTick; + dwMsecQP=GetMsecSincePerfCounter(li_qp,TRUE); + dwResRdtsc=GetResRdtsc(li_rdtsc,TRUE); + printf("total compress size = %u, in %u step\n",lSizeCpr,step); + printf("time = %u msec = %f sec\n",dwGetTick,dwGetTick/(double)1000.); + printf("defcpr time QP = %u msec = %f sec\n",dwMsecQP,dwMsecQP/(double)1000.); + printf("defcpr result rdtsc = %I64x\n\n",dwResRdtsc.QuadPart); + } + + CprPtr=(unsigned char*)realloc(CprPtr,lSizeCpr); + UncprPtr=(unsigned char*)malloc(lBufferSizeUncpr + BlockSizeUncompress); + + BeginCountPerfCounter(&li_qp,TRUE); + dwGetTick=GetTickCount(); + BeginCountRdtsc(&li_rdtsc); + { + z_stream zcpr; + int ret=Z_OK; + long lOrigToDo = lSizeCpr; + long lOrigDone = 0; + int step=0; + memset(&zcpr,0,sizeof(z_stream)); + inflateInit(&zcpr); + + zcpr.next_in = CprPtr; + zcpr.next_out = UncprPtr; + + + do + { + long all_read_before = zcpr.total_in; + zcpr.avail_in = min(lOrigToDo,BlockSizeUncompress); + zcpr.avail_out = BlockSizeUncompress; + ret=inflate(&zcpr,Z_SYNC_FLUSH); + lOrigDone += (zcpr.total_in-all_read_before); + lOrigToDo -= (zcpr.total_in-all_read_before); + step++; + } while (ret==Z_OK); + + lSizeUncpr=zcpr.total_out; + inflateEnd(&zcpr); + dwGetTick=GetTickCount()-dwGetTick; + dwMsecQP=GetMsecSincePerfCounter(li_qp,TRUE); + dwResRdtsc=GetResRdtsc(li_rdtsc,TRUE); + printf("total uncompress size = %u, in %u step\n",lSizeUncpr,step); + printf("time = %u msec = %f sec\n",dwGetTick,dwGetTick/(double)1000.); + printf("uncpr time QP = %u msec = %f sec\n",dwMsecQP,dwMsecQP/(double)1000.); + printf("uncpr result rdtsc = %I64x\n\n",dwResRdtsc.QuadPart); + } + + if (lSizeUncpr==lFileSize) + { + if (memcmp(FilePtr,UncprPtr,lFileSize)==0) + printf("compare ok\n"); + + } + + return 0; +} diff --git a/zlib/zlib/contrib/testzlib/testzlib.txt b/zlib/zlib/contrib/testzlib/testzlib.txt new file mode 100644 index 00000000..e508bb22 --- /dev/null +++ b/zlib/zlib/contrib/testzlib/testzlib.txt @@ -0,0 +1,10 @@ +To build testzLib with Visual Studio 2005: + +copy to a directory file from : +- root of zLib tree +- contrib/testzlib +- contrib/masmx86 +- contrib/masmx64 +- contrib/vstudio/vc7 + +and open testzlib8.sln \ No newline at end of file diff --git a/zlib/zlib/contrib/untgz/Makefile b/zlib/zlib/contrib/untgz/Makefile new file mode 100644 index 00000000..b54266fb --- /dev/null +++ b/zlib/zlib/contrib/untgz/Makefile @@ -0,0 +1,14 @@ +CC=cc +CFLAGS=-g + +untgz: untgz.o ../../libz.a + $(CC) $(CFLAGS) -o untgz untgz.o -L../.. -lz + +untgz.o: untgz.c ../../zlib.h + $(CC) $(CFLAGS) -c -I../.. untgz.c + +../../libz.a: + cd ../..; ./configure; make + +clean: + rm -f untgz untgz.o *~ diff --git a/zlib/zlib/contrib/untgz/Makefile.msc b/zlib/zlib/contrib/untgz/Makefile.msc new file mode 100644 index 00000000..77b86022 --- /dev/null +++ b/zlib/zlib/contrib/untgz/Makefile.msc @@ -0,0 +1,17 @@ +CC=cl +CFLAGS=-MD + +untgz.exe: untgz.obj ..\..\zlib.lib + $(CC) $(CFLAGS) untgz.obj ..\..\zlib.lib + +untgz.obj: untgz.c ..\..\zlib.h + $(CC) $(CFLAGS) -c -I..\.. untgz.c + +..\..\zlib.lib: + cd ..\.. + $(MAKE) -f win32\makefile.msc + cd contrib\untgz + +clean: + -del untgz.obj + -del untgz.exe diff --git a/zlib/zlib/contrib/untgz/untgz.c b/zlib/zlib/contrib/untgz/untgz.c new file mode 100644 index 00000000..2c391e59 --- /dev/null +++ b/zlib/zlib/contrib/untgz/untgz.c @@ -0,0 +1,674 @@ +/* + * untgz.c -- Display contents and extract files from a gzip'd TAR file + * + * written by Pedro A. Aranda Gutierrez + * adaptation to Unix by Jean-loup Gailly + * various fixes by Cosmin Truta + */ + +#include +#include +#include +#include +#include + +#include "zlib.h" + +#ifdef unix +# include +#else +# include +# include +#endif + +#ifdef WIN32 +#include +# ifndef F_OK +# define F_OK 0 +# endif +# define mkdir(dirname,mode) _mkdir(dirname) +# ifdef _MSC_VER +# define access(path,mode) _access(path,mode) +# define chmod(path,mode) _chmod(path,mode) +# define strdup(str) _strdup(str) +# endif +#else +# include +#endif + + +/* values used in typeflag field */ + +#define REGTYPE '0' /* regular file */ +#define AREGTYPE '\0' /* regular file */ +#define LNKTYPE '1' /* link */ +#define SYMTYPE '2' /* reserved */ +#define CHRTYPE '3' /* character special */ +#define BLKTYPE '4' /* block special */ +#define DIRTYPE '5' /* directory */ +#define FIFOTYPE '6' /* FIFO special */ +#define CONTTYPE '7' /* reserved */ + +/* GNU tar extensions */ + +#define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */ +#define GNUTYPE_LONGLINK 'K' /* long link name */ +#define GNUTYPE_LONGNAME 'L' /* long file name */ +#define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */ +#define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */ +#define GNUTYPE_SPARSE 'S' /* sparse file */ +#define GNUTYPE_VOLHDR 'V' /* tape/volume header */ + + +/* tar header */ + +#define BLOCKSIZE 512 +#define SHORTNAMESIZE 100 + +struct tar_header +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +union tar_buffer +{ + char buffer[BLOCKSIZE]; + struct tar_header header; +}; + +struct attr_item +{ + struct attr_item *next; + char *fname; + int mode; + time_t time; +}; + +enum { TGZ_EXTRACT, TGZ_LIST, TGZ_INVALID }; + +char *TGZfname OF((const char *)); +void TGZnotfound OF((const char *)); + +int getoct OF((char *, int)); +char *strtime OF((time_t *)); +int setfiletime OF((char *, time_t)); +void push_attr OF((struct attr_item **, char *, int, time_t)); +void restore_attr OF((struct attr_item **)); + +int ExprMatch OF((char *, char *)); + +int makedir OF((char *)); +int matchname OF((int, int, char **, char *)); + +void error OF((const char *)); +int tar OF((gzFile, int, int, int, char **)); + +void help OF((int)); +int main OF((int, char **)); + +char *prog; + +const char *TGZsuffix[] = { "\0", ".tar", ".tar.gz", ".taz", ".tgz", NULL }; + +/* return the file name of the TGZ archive */ +/* or NULL if it does not exist */ + +char *TGZfname (const char *arcname) +{ + static char buffer[1024]; + int origlen,i; + + strcpy(buffer,arcname); + origlen = strlen(buffer); + + for (i=0; TGZsuffix[i]; i++) + { + strcpy(buffer+origlen,TGZsuffix[i]); + if (access(buffer,F_OK) == 0) + return buffer; + } + return NULL; +} + + +/* error message for the filename */ + +void TGZnotfound (const char *arcname) +{ + int i; + + fprintf(stderr,"%s: Couldn't find ",prog); + for (i=0;TGZsuffix[i];i++) + fprintf(stderr,(TGZsuffix[i+1]) ? "%s%s, " : "or %s%s\n", + arcname, + TGZsuffix[i]); + exit(1); +} + + +/* convert octal digits to int */ +/* on error return -1 */ + +int getoct (char *p,int width) +{ + int result = 0; + char c; + + while (width--) + { + c = *p++; + if (c == 0) + break; + if (c == ' ') + continue; + if (c < '0' || c > '7') + return -1; + result = result * 8 + (c - '0'); + } + return result; +} + + +/* convert time_t to string */ +/* use the "YYYY/MM/DD hh:mm:ss" format */ + +char *strtime (time_t *t) +{ + struct tm *local; + static char result[32]; + + local = localtime(t); + sprintf(result,"%4d/%02d/%02d %02d:%02d:%02d", + local->tm_year+1900, local->tm_mon+1, local->tm_mday, + local->tm_hour, local->tm_min, local->tm_sec); + return result; +} + + +/* set file time */ + +int setfiletime (char *fname,time_t ftime) +{ +#ifdef WIN32 + static int isWinNT = -1; + SYSTEMTIME st; + FILETIME locft, modft; + struct tm *loctm; + HANDLE hFile; + int result; + + loctm = localtime(&ftime); + if (loctm == NULL) + return -1; + + st.wYear = (WORD)loctm->tm_year + 1900; + st.wMonth = (WORD)loctm->tm_mon + 1; + st.wDayOfWeek = (WORD)loctm->tm_wday; + st.wDay = (WORD)loctm->tm_mday; + st.wHour = (WORD)loctm->tm_hour; + st.wMinute = (WORD)loctm->tm_min; + st.wSecond = (WORD)loctm->tm_sec; + st.wMilliseconds = 0; + if (!SystemTimeToFileTime(&st, &locft) || + !LocalFileTimeToFileTime(&locft, &modft)) + return -1; + + if (isWinNT < 0) + isWinNT = (GetVersion() < 0x80000000) ? 1 : 0; + hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + (isWinNT ? FILE_FLAG_BACKUP_SEMANTICS : 0), + NULL); + if (hFile == INVALID_HANDLE_VALUE) + return -1; + result = SetFileTime(hFile, NULL, NULL, &modft) ? 0 : -1; + CloseHandle(hFile); + return result; +#else + struct utimbuf settime; + + settime.actime = settime.modtime = ftime; + return utime(fname,&settime); +#endif +} + + +/* push file attributes */ + +void push_attr(struct attr_item **list,char *fname,int mode,time_t time) +{ + struct attr_item *item; + + item = (struct attr_item *)malloc(sizeof(struct attr_item)); + if (item == NULL) + error("Out of memory"); + item->fname = strdup(fname); + item->mode = mode; + item->time = time; + item->next = *list; + *list = item; +} + + +/* restore file attributes */ + +void restore_attr(struct attr_item **list) +{ + struct attr_item *item, *prev; + + for (item = *list; item != NULL; ) + { + setfiletime(item->fname,item->time); + chmod(item->fname,item->mode); + prev = item; + item = item->next; + free(prev); + } + *list = NULL; +} + + +/* match regular expression */ + +#define ISSPECIAL(c) (((c) == '*') || ((c) == '/')) + +int ExprMatch (char *string,char *expr) +{ + while (1) + { + if (ISSPECIAL(*expr)) + { + if (*expr == '/') + { + if (*string != '\\' && *string != '/') + return 0; + string ++; expr++; + } + else if (*expr == '*') + { + if (*expr ++ == 0) + return 1; + while (*++string != *expr) + if (*string == 0) + return 0; + } + } + else + { + if (*string != *expr) + return 0; + if (*expr++ == 0) + return 1; + string++; + } + } +} + + +/* recursive mkdir */ +/* abort on ENOENT; ignore other errors like "directory already exists" */ +/* return 1 if OK */ +/* 0 on error */ + +int makedir (char *newdir) +{ + char *buffer = strdup(newdir); + char *p; + int len = strlen(buffer); + + if (len <= 0) { + free(buffer); + return 0; + } + if (buffer[len-1] == '/') { + buffer[len-1] = '\0'; + } + if (mkdir(buffer, 0755) == 0) + { + free(buffer); + return 1; + } + + p = buffer+1; + while (1) + { + char hold; + + while(*p && *p != '\\' && *p != '/') + p++; + hold = *p; + *p = 0; + if ((mkdir(buffer, 0755) == -1) && (errno == ENOENT)) + { + fprintf(stderr,"%s: Couldn't create directory %s\n",prog,buffer); + free(buffer); + return 0; + } + if (hold == 0) + break; + *p++ = hold; + } + free(buffer); + return 1; +} + + +int matchname (int arg,int argc,char **argv,char *fname) +{ + if (arg == argc) /* no arguments given (untgz tgzarchive) */ + return 1; + + while (arg < argc) + if (ExprMatch(fname,argv[arg++])) + return 1; + + return 0; /* ignore this for the moment being */ +} + + +/* tar file list or extract */ + +int tar (gzFile in,int action,int arg,int argc,char **argv) +{ + union tar_buffer buffer; + int len; + int err; + int getheader = 1; + int remaining = 0; + FILE *outfile = NULL; + char fname[BLOCKSIZE]; + int tarmode; + time_t tartime; + struct attr_item *attributes = NULL; + + if (action == TGZ_LIST) + printf(" date time size file\n" + " ---------- -------- --------- -------------------------------------\n"); + while (1) + { + len = gzread(in, &buffer, BLOCKSIZE); + if (len < 0) + error(gzerror(in, &err)); + /* + * Always expect complete blocks to process + * the tar information. + */ + if (len != BLOCKSIZE) + { + action = TGZ_INVALID; /* force error exit */ + remaining = 0; /* force I/O cleanup */ + } + + /* + * If we have to get a tar header + */ + if (getheader >= 1) + { + /* + * if we met the end of the tar + * or the end-of-tar block, + * we are done + */ + if (len == 0 || buffer.header.name[0] == 0) + break; + + tarmode = getoct(buffer.header.mode,8); + tartime = (time_t)getoct(buffer.header.mtime,12); + if (tarmode == -1 || tartime == (time_t)-1) + { + buffer.header.name[0] = 0; + action = TGZ_INVALID; + } + + if (getheader == 1) + { + strncpy(fname,buffer.header.name,SHORTNAMESIZE); + if (fname[SHORTNAMESIZE-1] != 0) + fname[SHORTNAMESIZE] = 0; + } + else + { + /* + * The file name is longer than SHORTNAMESIZE + */ + if (strncmp(fname,buffer.header.name,SHORTNAMESIZE-1) != 0) + error("bad long name"); + getheader = 1; + } + + /* + * Act according to the type flag + */ + switch (buffer.header.typeflag) + { + case DIRTYPE: + if (action == TGZ_LIST) + printf(" %s %s\n",strtime(&tartime),fname); + if (action == TGZ_EXTRACT) + { + makedir(fname); + push_attr(&attributes,fname,tarmode,tartime); + } + break; + case REGTYPE: + case AREGTYPE: + remaining = getoct(buffer.header.size,12); + if (remaining == -1) + { + action = TGZ_INVALID; + break; + } + if (action == TGZ_LIST) + printf(" %s %9d %s\n",strtime(&tartime),remaining,fname); + else if (action == TGZ_EXTRACT) + { + if (matchname(arg,argc,argv,fname)) + { + outfile = fopen(fname,"wb"); + if (outfile == NULL) { + /* try creating directory */ + char *p = strrchr(fname, '/'); + if (p != NULL) { + *p = '\0'; + makedir(fname); + *p = '/'; + outfile = fopen(fname,"wb"); + } + } + if (outfile != NULL) + printf("Extracting %s\n",fname); + else + fprintf(stderr, "%s: Couldn't create %s",prog,fname); + } + else + outfile = NULL; + } + getheader = 0; + break; + case GNUTYPE_LONGLINK: + case GNUTYPE_LONGNAME: + remaining = getoct(buffer.header.size,12); + if (remaining < 0 || remaining >= BLOCKSIZE) + { + action = TGZ_INVALID; + break; + } + len = gzread(in, fname, BLOCKSIZE); + if (len < 0) + error(gzerror(in, &err)); + if (fname[BLOCKSIZE-1] != 0 || (int)strlen(fname) > remaining) + { + action = TGZ_INVALID; + break; + } + getheader = 2; + break; + default: + if (action == TGZ_LIST) + printf(" %s <---> %s\n",strtime(&tartime),fname); + break; + } + } + else + { + unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining; + + if (outfile != NULL) + { + if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes) + { + fprintf(stderr, + "%s: Error writing %s -- skipping\n",prog,fname); + fclose(outfile); + outfile = NULL; + remove(fname); + } + } + remaining -= bytes; + } + + if (remaining == 0) + { + getheader = 1; + if (outfile != NULL) + { + fclose(outfile); + outfile = NULL; + if (action != TGZ_INVALID) + push_attr(&attributes,fname,tarmode,tartime); + } + } + + /* + * Abandon if errors are found + */ + if (action == TGZ_INVALID) + { + error("broken archive"); + break; + } + } + + /* + * Restore file modes and time stamps + */ + restore_attr(&attributes); + + if (gzclose(in) != Z_OK) + error("failed gzclose"); + + return 0; +} + + +/* ============================================================ */ + +void help(int exitval) +{ + printf("untgz version 0.2.1\n" + " using zlib version %s\n\n", + zlibVersion()); + printf("Usage: untgz file.tgz extract all files\n" + " untgz file.tgz fname ... extract selected files\n" + " untgz -l file.tgz list archive contents\n" + " untgz -h display this help\n"); + exit(exitval); +} + +void error(const char *msg) +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + + +/* ============================================================ */ + +#if defined(WIN32) && defined(__GNUC__) +int _CRT_glob = 0; /* disable argument globbing in MinGW */ +#endif + +int main(int argc,char **argv) +{ + int action = TGZ_EXTRACT; + int arg = 1; + char *TGZfile; + gzFile *f; + + prog = strrchr(argv[0],'\\'); + if (prog == NULL) + { + prog = strrchr(argv[0],'/'); + if (prog == NULL) + { + prog = strrchr(argv[0],':'); + if (prog == NULL) + prog = argv[0]; + else + prog++; + } + else + prog++; + } + else + prog++; + + if (argc == 1) + help(0); + + if (strcmp(argv[arg],"-l") == 0) + { + action = TGZ_LIST; + if (argc == ++arg) + help(0); + } + else if (strcmp(argv[arg],"-h") == 0) + { + help(0); + } + + if ((TGZfile = TGZfname(argv[arg])) == NULL) + TGZnotfound(argv[arg]); + + ++arg; + if ((action == TGZ_LIST) && (arg != argc)) + help(1); + +/* + * Process the TGZ file + */ + switch(action) + { + case TGZ_LIST: + case TGZ_EXTRACT: + f = gzopen(TGZfile,"rb"); + if (f == NULL) + { + fprintf(stderr,"%s: Couldn't gzopen %s\n",prog,TGZfile); + return 1; + } + exit(tar(f, action, arg, argc, argv)); + break; + + default: + error("Unknown option"); + exit(1); + } + + return 0; +} diff --git a/zlib/zlib/contrib/vstudio/readme.txt b/zlib/zlib/contrib/vstudio/readme.txt new file mode 100644 index 00000000..04b8baac --- /dev/null +++ b/zlib/zlib/contrib/vstudio/readme.txt @@ -0,0 +1,65 @@ +Building instructions for the DLL versions of Zlib 1.2.8 +======================================================== + +This directory contains projects that build zlib and minizip using +Microsoft Visual C++ 9.0/10.0. + +You don't need to build these projects yourself. You can download the +binaries from: + http://www.winimage.com/zLibDll + +More information can be found at this site. + + + + + +Build instructions for Visual Studio 2008 (32 bits or 64 bits) +-------------------------------------------------------------- +- Uncompress current zlib, including all contrib/* files +- Compile assembly code (with Visual Studio Command Prompt) by running: + bld_ml64.bat (in contrib\masmx64) + bld_ml32.bat (in contrib\masmx86) +- Open contrib\vstudio\vc9\zlibvc.sln with Microsoft Visual C++ 2008 +- Or run: vcbuild /rebuild contrib\vstudio\vc9\zlibvc.sln "Release|Win32" + +Build instructions for Visual Studio 2010 (32 bits or 64 bits) +-------------------------------------------------------------- +- Uncompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc10\zlibvc.sln with Microsoft Visual C++ 2010 + +Build instructions for Visual Studio 2012 (32 bits or 64 bits) +-------------------------------------------------------------- +- Uncompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc11\zlibvc.sln with Microsoft Visual C++ 2012 + + +Important +--------- +- To use zlibwapi.dll in your application, you must define the + macro ZLIB_WINAPI when compiling your application's source files. + + +Additional notes +---------------- +- This DLL, named zlibwapi.dll, is compatible to the old zlib.dll built + by Gilles Vollant from the zlib 1.1.x sources, and distributed at + http://www.winimage.com/zLibDll + It uses the WINAPI calling convention for the exported functions, and + includes the minizip functionality. If your application needs that + particular build of zlib.dll, you can rename zlibwapi.dll to zlib.dll. + +- The new DLL was renamed because there exist several incompatible + versions of zlib.dll on the Internet. + +- There is also an official DLL build of zlib, named zlib1.dll. This one + is exporting the functions using the CDECL convention. See the file + win32\DLL_FAQ.txt found in this zlib distribution. + +- There used to be a ZLIB_DLL macro in zlib 1.1.x, but now this symbol + has a slightly different effect. To avoid compatibility problems, do + not define it here. + + +Gilles Vollant +info@winimage.com diff --git a/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj b/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj new file mode 100644 index 00000000..74e15c90 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters new file mode 100644 index 00000000..0b2a3de2 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {048af943-022b-4db6-beeb-a54c34774ee2} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {c1d600d2-888f-4aea-b73e-8b0dd9befa0c} + h;hpp;hxx;hm;inl;inc + + + {0844199a-966b-4f19-81db-1e0125e141b9} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj b/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj new file mode 100644 index 00000000..917e1565 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj @@ -0,0 +1,307 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters new file mode 100644 index 00000000..dd73cd31 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {c0419b40-bf50-40da-b153-ff74215b79de} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {bb87b070-735b-478e-92ce-7383abb2f36c} + h;hpp;hxx;hm;inl;inc + + + {f46ab6a6-548f-43cb-ae96-681abb5bd5db} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj b/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj new file mode 100644 index 00000000..9088d176 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj @@ -0,0 +1,420 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + true + + + Application + true + + + Application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + EditAndContinue + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters new file mode 100644 index 00000000..249daa89 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters @@ -0,0 +1,58 @@ + + + + + {c1f6a2e3-5da5-4955-8653-310d3efe05a9} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {c2aaffdc-2c95-4d6f-8466-4bec5890af2c} + h;hpp;hxx;hm;inl;inc + + + {c274fe07-05f2-461c-964b-f6341e4e7eb5} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj b/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj new file mode 100644 index 00000000..bcb08ff9 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters new file mode 100644 index 00000000..53a8693b --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {fa61a89f-93fc-4c89-b29e-36224b7592f4} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {d4b85da0-2ba2-4934-b57f-e2584e3848ee} + h;hpp;hxx;hm;inl;inc + + + {e573e075-00bd-4a7d-bd67-a8cc9bfc5aca} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/zlib.rc b/zlib/zlib/contrib/vstudio/vc10/zlib.rc new file mode 100644 index 00000000..73f6476d --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1,2,8,0 + PRODUCTVERSION 1,2,8,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.8\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2013 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj b/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj new file mode 100644 index 00000000..b9f2bbe5 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj @@ -0,0 +1,473 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters new file mode 100644 index 00000000..c8c7f7ea --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {174213f6-7f66-4ae8-a3a8-a1e0a1e6ffdd} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibvc.def b/zlib/zlib/contrib/vstudio/vc10/zlibvc.def new file mode 100644 index 00000000..63670467 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibvc.def @@ -0,0 +1,143 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2.8 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibvc.sln b/zlib/zlib/contrib/vstudio/vc10/zlibvc.sln new file mode 100644 index 00000000..6f6ffd5e --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibvc.sln @@ -0,0 +1,135 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.Build.0 = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.Build.0 = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.Build.0 = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.Build.0 = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj b/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj new file mode 100644 index 00000000..6ff9ddb0 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj @@ -0,0 +1,657 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapid + zlibwapi + zlibwapi + zlibwapid + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebug + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + true + .\zlibvc.def + true + true + Windows + false + + + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + true + false + .\zlibvc.def + true + Windows + false + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + true + false + .\zlibvc.def + true + Windows + false + + + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + true + .\zlibvc.def + true + true + Windows + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + true + false + .\zlibvc.def + true + Windows + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + true + false + .\zlibvc.def + true + Windows + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters b/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters new file mode 100644 index 00000000..180b71cd --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters @@ -0,0 +1,118 @@ + + + + + {07934a85-8b61-443d-a0ee-b2eedb74f3cd} + cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 + + + {1d99675b-433d-4a21-9e50-ed4ab8b19762} + h;hpp;hxx;hm;inl;fi;fd + + + {431c0958-fa71-44d0-9084-2d19d100c0cc} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/miniunz.vcxproj b/zlib/zlib/contrib/vstudio/vc11/miniunz.vcxproj new file mode 100644 index 00000000..8f9f20bd --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/miniunz.vcxproj @@ -0,0 +1,314 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/minizip.vcxproj b/zlib/zlib/contrib/vstudio/vc11/minizip.vcxproj new file mode 100644 index 00000000..c93d9e6f --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/minizip.vcxproj @@ -0,0 +1,311 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/testzlib.vcxproj b/zlib/zlib/contrib/vstudio/vc11/testzlib.vcxproj new file mode 100644 index 00000000..6d559540 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/testzlib.vcxproj @@ -0,0 +1,426 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + v110 + + + Application + MultiByte + true + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + true + v110 + + + Application + true + v110 + + + Application + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj b/zlib/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj new file mode 100644 index 00000000..9f20c78f --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj @@ -0,0 +1,314 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/zlib.rc b/zlib/zlib/contrib/vstudio/vc11/zlib.rc new file mode 100644 index 00000000..73f6476d --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1,2,8,0 + PRODUCTVERSION 1,2,8,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.8\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2013 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/zlib/zlib/contrib/vstudio/vc11/zlibstat.vcxproj b/zlib/zlib/contrib/vstudio/vc11/zlibstat.vcxproj new file mode 100644 index 00000000..806b76a8 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/zlibstat.vcxproj @@ -0,0 +1,464 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + Unicode + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc11/zlibvc.def b/zlib/zlib/contrib/vstudio/vc11/zlibvc.def new file mode 100644 index 00000000..63670467 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/zlibvc.def @@ -0,0 +1,143 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2.8 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 diff --git a/zlib/zlib/contrib/vstudio/vc11/zlibvc.sln b/zlib/zlib/contrib/vstudio/vc11/zlibvc.sln new file mode 100644 index 00000000..9fcbafdd --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/zlibvc.sln @@ -0,0 +1,117 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/zlib/zlib/contrib/vstudio/vc11/zlibvc.vcxproj b/zlib/zlib/contrib/vstudio/vc11/zlibvc.vcxproj new file mode 100644 index 00000000..c65b95fd --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc11/zlibvc.vcxproj @@ -0,0 +1,688 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + v110 + Unicode + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\contrib\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zlib/zlib/contrib/vstudio/vc9/miniunz.vcproj b/zlib/zlib/contrib/vstudio/vc9/miniunz.vcproj new file mode 100644 index 00000000..7da32b91 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/miniunz.vcproj @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/vstudio/vc9/minizip.vcproj b/zlib/zlib/contrib/vstudio/vc9/minizip.vcproj new file mode 100644 index 00000000..e57e07d9 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/minizip.vcproj @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/vstudio/vc9/testzlib.vcproj b/zlib/zlib/contrib/vstudio/vc9/testzlib.vcproj new file mode 100644 index 00000000..9cb0bf87 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/testzlib.vcproj @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/vstudio/vc9/testzlibdll.vcproj b/zlib/zlib/contrib/vstudio/vc9/testzlibdll.vcproj new file mode 100644 index 00000000..b1ddde05 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/testzlibdll.vcproj @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/vstudio/vc9/zlib.rc b/zlib/zlib/contrib/vstudio/vc9/zlib.rc new file mode 100644 index 00000000..73f6476d --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1,2,8,0 + PRODUCTVERSION 1,2,8,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.8\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2013 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/zlib/zlib/contrib/vstudio/vc9/zlibstat.vcproj b/zlib/zlib/contrib/vstudio/vc9/zlibstat.vcproj new file mode 100644 index 00000000..61c76c7c --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/zlibstat.vcproj @@ -0,0 +1,835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/contrib/vstudio/vc9/zlibvc.def b/zlib/zlib/contrib/vstudio/vc9/zlibvc.def new file mode 100644 index 00000000..63670467 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/zlibvc.def @@ -0,0 +1,143 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2.8 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 diff --git a/zlib/zlib/contrib/vstudio/vc9/zlibvc.sln b/zlib/zlib/contrib/vstudio/vc9/zlibvc.sln new file mode 100644 index 00000000..b4829671 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/zlibvc.sln @@ -0,0 +1,144 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestZlibDll", "testzlibdll.vcproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.Build.0 = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.Build.0 = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.Build.0 = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.Build.0 = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/zlib/zlib/contrib/vstudio/vc9/zlibvc.vcproj b/zlib/zlib/contrib/vstudio/vc9/zlibvc.vcproj new file mode 100644 index 00000000..c9a89471 --- /dev/null +++ b/zlib/zlib/contrib/vstudio/vc9/zlibvc.vcproj @@ -0,0 +1,1156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/crc32.c b/zlib/zlib/crc32.c new file mode 100644 index 00000000..979a7190 --- /dev/null +++ b/zlib/zlib/crc32.c @@ -0,0 +1,425 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + + DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Definitions for doing the crc four data bytes at a time. */ +#if !defined(NOBYFOUR) && defined(Z_U4) +# define BYFOUR +#endif +#ifdef BYFOUR + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); +local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); + + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local z_crc_t FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const z_crc_t FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + z_crc_t c; + int n, k; + z_crc_t poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0; + for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) + poly |= (z_crc_t)1 << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (z_crc_t)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = ZSWAP32(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = ZSWAP32(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const z_crc_t FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const z_crc_t FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + (unsigned long)(table[n]), + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const z_crc_t FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const z_crc_t FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + uInt len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + z_crc_t endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register z_crc_t c; + register const z_crc_t FAR *buf4; + + c = (z_crc_t)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register z_crc_t c; + register const z_crc_t FAR *buf4; + + c = ZSWAP32((z_crc_t)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(ZSWAP32(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +local uLong crc32_combine_(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case (also disallow negative lengths) */ + if (len2 <= 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} + +uLong ZEXPORT crc32_combine64(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} diff --git a/zlib/zlib/crc32.h b/zlib/zlib/crc32.h new file mode 100644 index 00000000..9e0c7781 --- /dev/null +++ b/zlib/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const z_crc_t FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/zlib/zlib/deflate.c b/zlib/zlib/deflate.c new file mode 100644 index 00000000..69695770 --- /dev/null +++ b/zlib/zlib/deflate.c @@ -0,0 +1,1967 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in http://tools.ietf.org/html/rfc1951 + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id$ */ + +#include "deflate.h" + +const char deflate_copyright[] = + " deflate 1.2.8 Copyright 1995-2013 Jean-loup Gailly and Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST +local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local block_state deflate_rle OF((deflate_state *s, int flush)); +local block_state deflate_huff OF((deflate_state *s, int flush)); +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif + +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +#ifndef NO_DUMMY_DECL +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ +#endif + +/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */ +#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0)) + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to to INSERT_STRING are made with consecutive + * input characters and the first MIN_MATCH bytes of str are valid + * (except for the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* ========================================================================= */ +int ZEXPORT deflateInit_(strm, level, version, stream_size) + z_streamp strm; + int level; + const char *version; + int stream_size; +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + version, stream_size) + z_streamp strm; + int level; + int method; + int windowBits; + int memLevel; + int strategy; + const char *version; + int stream_size; +{ + deflate_state *s; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + + s->wrap = wrap; + s->gzhead = Z_NULL; + s->w_bits = windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->high_water = 0; /* nothing written to s->window yet */ + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + s->status = FINISH_STATE; + strm->msg = ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) + z_streamp strm; + const Bytef *dictionary; + uInt dictLength; +{ + deflate_state *s; + uInt str, n; + int wrap; + unsigned avail; + z_const unsigned char *next; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL) + return Z_STREAM_ERROR; + s = strm->state; + wrap = s->wrap; + if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead) + return Z_STREAM_ERROR; + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap == 1) + strm->adler = adler32(strm->adler, dictionary, dictLength); + s->wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s->w_size) { + if (wrap == 0) { /* already empty otherwise */ + CLEAR_HASH(s); + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + dictionary += dictLength - s->w_size; /* use the tail */ + dictLength = s->w_size; + } + + /* insert dictionary into window and hash */ + avail = strm->avail_in; + next = strm->next_in; + strm->avail_in = dictLength; + strm->next_in = (z_const Bytef *)dictionary; + fill_window(s); + while (s->lookahead >= MIN_MATCH) { + str = s->strstart; + n = s->lookahead - (MIN_MATCH-1); + do { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + } while (--n); + s->strstart = str; + s->lookahead = MIN_MATCH-1; + fill_window(s); + } + s->strstart += s->lookahead; + s->block_start = (long)s->strstart; + s->insert = s->lookahead; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + strm->next_in = next; + strm->avail_in = avail; + s->wrap = wrap; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateResetKeep (strm) + z_streamp strm; +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) { + return Z_STREAM_ERROR; + } + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ + } + s->status = s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + int ret; + + ret = deflateResetKeep(strm); + if (ret == Z_OK) + lm_init(strm->state); + return ret; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetHeader (strm, head) + z_streamp strm; + gz_headerp head; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (strm->state->wrap != 2) return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePending (strm, pending, bits) + unsigned *pending; + int *bits; + z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (pending != Z_NULL) + *pending = strm->state->pending; + if (bits != Z_NULL) + *bits = strm->state->bi_valid; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime (strm, bits, value) + z_streamp strm; + int bits; + int value; +{ + deflate_state *s; + int put; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; + do { + put = Buf_size - s->bi_valid; + if (put > bits) + put = bits; + s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid); + s->bi_valid += put; + _tr_flush_bits(s); + value >>= put; + bits -= put; + } while (bits); + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams(strm, level, strategy) + z_streamp strm; + int level; + int strategy; +{ + deflate_state *s; + compress_func func; + int err = Z_OK; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if ((strategy != s->strategy || func != configuration_table[level].func) && + strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_BLOCK); + if (err == Z_BUF_ERROR && s->pending == 0) + err = Z_OK; + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} + +/* ========================================================================= */ +int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) + z_streamp strm; + int good_length; + int max_lazy; + int nice_length; + int max_chain; +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = good_length; + s->max_lazy_match = max_lazy; + s->nice_match = nice_length; + s->max_chain_length = max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds for + * every combination of windowBits and memLevel. But even the conservative + * upper bound of about 14% expansion does not seem onerous for output buffer + * allocation. + */ +uLong ZEXPORT deflateBound(strm, sourceLen) + z_streamp strm; + uLong sourceLen; +{ + deflate_state *s; + uLong complen, wraplen; + Bytef *str; + + /* conservative upper bound for compressed data */ + complen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; + + /* if can't get parameters, return conservative bound plus zlib wrapper */ + if (strm == Z_NULL || strm->state == Z_NULL) + return complen + 6; + + /* compute wrapper length */ + s = strm->state; + switch (s->wrap) { + case 0: /* raw deflate */ + wraplen = 0; + break; + case 1: /* zlib wrapper */ + wraplen = 6 + (s->strstart ? 4 : 0); + break; + case 2: /* gzip wrapper */ + wraplen = 18; + if (s->gzhead != Z_NULL) { /* user-supplied gzip header */ + if (s->gzhead->extra != Z_NULL) + wraplen += 2 + s->gzhead->extra_len; + str = s->gzhead->name; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + str = s->gzhead->comment; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + if (s->gzhead->hcrc) + wraplen += 2; + } + break; + default: /* for compiler happiness */ + wraplen = 6; + } + + /* if not default parameters, return conservative bound */ + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return complen + wraplen; + + /* default settings: return tight bound for that case */ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13 - 6 + wraplen; +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (s, b) + deflate_state *s; + uInt b; +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending(strm) + z_streamp strm; +{ + unsigned len; + deflate_state *s = strm->state; + + _tr_flush_bits(s); + len = s->pending; + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, s->pending_out, len); + strm->next_out += len; + s->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + s->pending -= len; + if (s->pending == 0) { + s->pending_out = s->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate (strm, flush) + z_streamp strm; + int flush; +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_BLOCK || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; + + /* Write the header */ + if (s->status == INIT_STATE) { +#ifdef GZIP + if (s->wrap == 2) { + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == Z_NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != Z_NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + else +#endif + { + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + } + } +#ifdef GZIP + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + + while (s->gzindex < (s->gzhead->extra_len & 0xffff)) { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) + break; + } + put_byte(s, s->gzhead->extra[s->gzindex]); + s->gzindex++; + } + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (s->gzindex == s->gzhead->extra_len) { + s->gzindex = 0; + s->status = NAME_STATE; + } + } + else + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) { + s->gzindex = 0; + s->status = COMMENT_STATE; + } + } + else + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) + s->status = HCRC_STATE; + } + else + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) + flush_pending(strm); + if (s->pending + 2 <= s->pending_buf_size) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + } + } + else + s->status = BUSY_STATE; + } +#endif + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : + (s->strategy == Z_RLE ? deflate_rle(s, flush) : + (*(configuration_table[s->level].func))(s, flush)); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + if (s->lookahead == 0) { + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + Assert(strm->avail_out > 0, "bug2"); + + if (flush != Z_FINISH) return Z_OK; + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (strm) + z_streamp strm; +{ + int status; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + + status = strm->state->status; + if (status != INIT_STATE && + status != EXTRA_STATE && + status != NAME_STATE && + status != COMMENT_STATE && + status != HCRC_STATE && + status != BUSY_STATE && + status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (dest, source) + z_streamp dest; + z_streamp source; +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local int read_buf(strm, buf, size) + z_streamp strm; + Bytef *buf; + unsigned size; +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + zmemcpy(buf, strm->next_in, len); + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, buf, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, buf, len); + } +#endif + strm->next_in += len; + strm->total_in += len; + + return (int)len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (s) + deflate_state *s; +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->insert = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif +} + +#ifndef FASTEST +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} +#endif /* ASMV */ + +#else /* FASTEST */ + +/* --------------------------------------------------------------------------- + * Optimized version for FASTEST only + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; +} + +#endif /* FASTEST */ + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(s, start, match, length) + deflate_state *s; + IPos start, match; + int length; +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif /* DEBUG */ + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(s) + deflate_state *s; +{ + register unsigned n, m; + register Posf *p; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif + more += wsize; + } + if (s->strm->avail_in == 0) break; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + if (s->high_water < s->window_size) { + ulg curr = s->strstart + (ulg)(s->lookahead); + ulg init; + + if (s->high_water < curr) { + /* Previous high water mark below current data -- zero WIN_INIT + * bytes or up to end of window, whichever is less. + */ + init = s->window_size - curr; + if (init > WIN_INIT) + init = WIN_INIT; + zmemzero(s->window + curr, (unsigned)init); + s->high_water = curr + init; + } + else if (s->high_water < (ulg)curr + WIN_INIT) { + /* High water mark at or above current data, but below current data + * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + * to end of window, whichever is less. + */ + init = (ulg)curr + WIN_INIT - s->high_water; + if (init > s->window_size - s->high_water) + init = s->window_size - s->high_water; + zmemzero(s->window + s->high_water, (unsigned)init); + s->high_water += init; + } + } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, last) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (last)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, last) { \ + FLUSH_BLOCK_ONLY(s, last); \ + if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \ +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +local block_state deflate_stored(s, flush) + deflate_state *s; + int flush; +{ + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + ulg max_block_size = 0xffff; + ulg max_start; + + if (max_block_size > s->pending_buf_size - 5) { + max_block_size = s->pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s->lookahead <= 1) { + + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); + + fill_window(s); + if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; + + if (s->lookahead == 0) break; /* flush the current block */ + } + Assert(s->block_start >= 0L, "block gone"); + + s->strstart += s->lookahead; + s->lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; + if (s->strstart == 0 || (ulg)s->strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; + FLUSH_BLOCK(s, 0); + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { + FLUSH_BLOCK(s, 0); + } + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if ((long)s->strstart > s->block_start) + FLUSH_BLOCK(s, 0); + return block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = NIL; + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s->match_length = longest_match (s, hash_head); + /* longest_match() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} + +#ifndef FASTEST +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = NIL; + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s->match_length = longest_match (s, hash_head); + /* longest_match() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} +#endif /* FASTEST */ + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + uInt prev; /* byte at distance one to match */ + Bytef *scan, *strend; /* scan goes up to strend for length of run */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s->lookahead <= MAX_MATCH) { + fill_window(s); + if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s->match_length = 0; + if (s->lookahead >= MIN_MATCH && s->strstart > 0) { + scan = s->window + s->strstart - 1; + prev = *scan; + if (prev == *++scan && prev == *++scan && prev == *++scan) { + strend = s->window + s->strstart + MAX_MATCH; + do { + } while (prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + scan < strend); + s->match_length = MAX_MATCH - (int)(strend - scan); + if (s->match_length > s->lookahead) + s->match_length = s->lookahead; + } + Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, s->match_length); + + _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + s->strstart += s->match_length; + s->match_length = 0; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +local block_state deflate_huff(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s->lookahead == 0) { + fill_window(s); + if (s->lookahead == 0) { + if (flush == Z_NO_FLUSH) + return need_more; + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s->match_length = 0; + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} diff --git a/zlib/zlib/deflate.h b/zlib/zlib/deflate.h new file mode 100644 index 00000000..ce0299ed --- /dev/null +++ b/zlib/zlib/deflate.h @@ -0,0 +1,346 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-2012 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +#include "zutil.h" + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define Buf_size 16 +/* size of bit buffer in bi_buf */ + +#define INIT_STATE 42 +#define EXTRA_STATE 69 +#define NAME_STATE 73 +#define COMMENT_STATE 91 +#define HCRC_STATE 103 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + uInt pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + uInt gzindex; /* where in extra, name, or comment */ + Byte method; /* can only be DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to suppress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + uInt insert; /* bytes at end of window left to insert */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + ulg high_water; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + +#define WIN_INIT MAX_MATCH +/* Number of bytes after end of data in window to initialize in order to avoid + memory checker errors from longest match routines */ + + /* in trees.c */ +void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); +int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch ZLIB_INTERNAL _length_code[]; + extern uch ZLIB_INTERNAL _dist_code[]; +#else + extern const uch ZLIB_INTERNAL _length_code[]; + extern const uch ZLIB_INTERNAL _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif /* DEFLATE_H */ diff --git a/zlib/zlib/doc/algorithm.txt b/zlib/zlib/doc/algorithm.txt new file mode 100644 index 00000000..c97f4950 --- /dev/null +++ b/zlib/zlib/doc/algorithm.txt @@ -0,0 +1,209 @@ +1. Compression algorithm (deflate) + +The deflation algorithm used by gzip (also zip and zlib) is a variation of +LZ77 (Lempel-Ziv 1977, see reference below). It finds duplicated strings in +the input data. The second occurrence of a string is replaced by a +pointer to the previous string, in the form of a pair (distance, +length). Distances are limited to 32K bytes, and lengths are limited +to 258 bytes. When a string does not occur anywhere in the previous +32K bytes, it is emitted as a sequence of literal bytes. (In this +description, `string' must be taken as an arbitrary sequence of bytes, +and is not restricted to printable characters.) + +Literals or match lengths are compressed with one Huffman tree, and +match distances are compressed with another tree. The trees are stored +in a compact form at the start of each block. The blocks can have any +size (except that the compressed data for one block must fit in +available memory). A block is terminated when deflate() determines that +it would be useful to start another block with fresh trees. (This is +somewhat similar to the behavior of LZW-based _compress_.) + +Duplicated strings are found using a hash table. All input strings of +length 3 are inserted in the hash table. A hash index is computed for +the next 3 bytes. If the hash chain for this index is not empty, all +strings in the chain are compared with the current input string, and +the longest match is selected. + +The hash chains are searched starting with the most recent strings, to +favor small distances and thus take advantage of the Huffman encoding. +The hash chains are singly linked. There are no deletions from the +hash chains, the algorithm simply discards matches that are too old. + +To avoid a worst-case situation, very long hash chains are arbitrarily +truncated at a certain length, determined by a runtime option (level +parameter of deflateInit). So deflate() does not always find the longest +possible match but generally finds a match which is long enough. + +deflate() also defers the selection of matches with a lazy evaluation +mechanism. After a match of length N has been found, deflate() searches for +a longer match at the next input byte. If a longer match is found, the +previous match is truncated to a length of one (thus producing a single +literal byte) and the process of lazy evaluation begins again. Otherwise, +the original match is kept, and the next match search is attempted only N +steps later. + +The lazy match evaluation is also subject to a runtime parameter. If +the current match is long enough, deflate() reduces the search for a longer +match, thus speeding up the whole process. If compression ratio is more +important than speed, deflate() attempts a complete second search even if +the first match is already long enough. + +The lazy match evaluation is not performed for the fastest compression +modes (level parameter 1 to 3). For these fast modes, new strings +are inserted in the hash table only when no match was found, or +when the match is not too long. This degrades the compression ratio +but saves time since there are both fewer insertions and fewer searches. + + +2. Decompression algorithm (inflate) + +2.1 Introduction + +The key question is how to represent a Huffman code (or any prefix code) so +that you can decode fast. The most important characteristic is that shorter +codes are much more common than longer codes, so pay attention to decoding the +short codes fast, and let the long codes take longer to decode. + +inflate() sets up a first level table that covers some number of bits of +input less than the length of longest code. It gets that many bits from the +stream, and looks it up in the table. The table will tell if the next +code is that many bits or less and how many, and if it is, it will tell +the value, else it will point to the next level table for which inflate() +grabs more bits and tries to decode a longer code. + +How many bits to make the first lookup is a tradeoff between the time it +takes to decode and the time it takes to build the table. If building the +table took no time (and if you had infinite memory), then there would only +be a first level table to cover all the way to the longest code. However, +building the table ends up taking a lot longer for more bits since short +codes are replicated many times in such a table. What inflate() does is +simply to make the number of bits in the first table a variable, and then +to set that variable for the maximum speed. + +For inflate, which has 286 possible codes for the literal/length tree, the size +of the first table is nine bits. Also the distance trees have 30 possible +values, and the size of the first table is six bits. Note that for each of +those cases, the table ended up one bit longer than the ``average'' code +length, i.e. the code length of an approximately flat code which would be a +little more than eight bits for 286 symbols and a little less than five bits +for 30 symbols. + + +2.2 More details on the inflate table lookup + +Ok, you want to know what this cleverly obfuscated inflate tree actually +looks like. You are correct that it's not a Huffman tree. It is simply a +lookup table for the first, let's say, nine bits of a Huffman symbol. The +symbol could be as short as one bit or as long as 15 bits. If a particular +symbol is shorter than nine bits, then that symbol's translation is duplicated +in all those entries that start with that symbol's bits. For example, if the +symbol is four bits, then it's duplicated 32 times in a nine-bit table. If a +symbol is nine bits long, it appears in the table once. + +If the symbol is longer than nine bits, then that entry in the table points +to another similar table for the remaining bits. Again, there are duplicated +entries as needed. The idea is that most of the time the symbol will be short +and there will only be one table look up. (That's whole idea behind data +compression in the first place.) For the less frequent long symbols, there +will be two lookups. If you had a compression method with really long +symbols, you could have as many levels of lookups as is efficient. For +inflate, two is enough. + +So a table entry either points to another table (in which case nine bits in +the above example are gobbled), or it contains the translation for the symbol +and the number of bits to gobble. Then you start again with the next +ungobbled bit. + +You may wonder: why not just have one lookup table for how ever many bits the +longest symbol is? The reason is that if you do that, you end up spending +more time filling in duplicate symbol entries than you do actually decoding. +At least for deflate's output that generates new trees every several 10's of +kbytes. You can imagine that filling in a 2^15 entry table for a 15-bit code +would take too long if you're only decoding several thousand symbols. At the +other extreme, you could make a new table for every bit in the code. In fact, +that's essentially a Huffman tree. But then you spend too much time +traversing the tree while decoding, even for short symbols. + +So the number of bits for the first lookup table is a trade of the time to +fill out the table vs. the time spent looking at the second level and above of +the table. + +Here is an example, scaled down: + +The code being decoded, with 10 symbols, from 1 to 6 bits long: + +A: 0 +B: 10 +C: 1100 +D: 11010 +E: 11011 +F: 11100 +G: 11101 +H: 11110 +I: 111110 +J: 111111 + +Let's make the first table three bits long (eight entries): + +000: A,1 +001: A,1 +010: A,1 +011: A,1 +100: B,2 +101: B,2 +110: -> table X (gobble 3 bits) +111: -> table Y (gobble 3 bits) + +Each entry is what the bits decode as and how many bits that is, i.e. how +many bits to gobble. Or the entry points to another table, with the number of +bits to gobble implicit in the size of the table. + +Table X is two bits long since the longest code starting with 110 is five bits +long: + +00: C,1 +01: C,1 +10: D,2 +11: E,2 + +Table Y is three bits long since the longest code starting with 111 is six +bits long: + +000: F,2 +001: F,2 +010: G,2 +011: G,2 +100: H,2 +101: H,2 +110: I,3 +111: J,3 + +So what we have here are three tables with a total of 20 entries that had to +be constructed. That's compared to 64 entries for a single table. Or +compared to 16 entries for a Huffman tree (six two entry tables and one four +entry table). Assuming that the code ideally represents the probability of +the symbols, it takes on the average 1.25 lookups per symbol. That's compared +to one lookup for the single table, or 1.66 lookups per symbol for the +Huffman tree. + +There, I think that gives you a picture of what's going on. For inflate, the +meaning of a particular symbol is often more than just a letter. It can be a +byte (a "literal"), or it can be either a length or a distance which +indicates a base value and a number of bits to fetch after the code that is +added to the base value. Or it might be the special end-of-block code. The +data structures created in inftrees.c try to encode all that information +compactly in the tables. + + +Jean-loup Gailly Mark Adler +jloup@gzip.org madler@alumni.caltech.edu + + +References: + +[LZ77] Ziv J., Lempel A., ``A Universal Algorithm for Sequential Data +Compression,'' IEEE Transactions on Information Theory, Vol. 23, No. 3, +pp. 337-343. + +``DEFLATE Compressed Data Format Specification'' available in +http://tools.ietf.org/html/rfc1951 diff --git a/zlib/zlib/doc/rfc1950.txt b/zlib/zlib/doc/rfc1950.txt new file mode 100644 index 00000000..ce6428a0 --- /dev/null +++ b/zlib/zlib/doc/rfc1950.txt @@ -0,0 +1,619 @@ + + + + + + +Network Working Group P. Deutsch +Request for Comments: 1950 Aladdin Enterprises +Category: Informational J-L. Gailly + Info-ZIP + May 1996 + + + ZLIB Compressed Data Format Specification version 3.3 + +Status of This Memo + + This memo provides information for the Internet community. This memo + does not specify an Internet standard of any kind. Distribution of + this memo is unlimited. + +IESG Note: + + The IESG takes no position on the validity of any Intellectual + Property Rights statements contained in this document. + +Notices + + Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly + + Permission is granted to copy and distribute this document for any + purpose and without charge, including translations into other + languages and incorporation into compilations, provided that the + copyright notice and this notice are preserved, and that any + substantive changes or deletions from the original are clearly + marked. + + A pointer to the latest version of this and related documentation in + HTML format can be found at the URL + . + +Abstract + + This specification defines a lossless compressed data format. The + data can be produced or consumed, even for an arbitrarily long + sequentially presented input data stream, using only an a priori + bounded amount of intermediate storage. The format presently uses + the DEFLATE compression method but can be easily extended to use + other compression methods. It can be implemented readily in a manner + not covered by patents. This specification also defines the ADLER-32 + checksum (an extension and improvement of the Fletcher checksum), + used for detection of data corruption, and provides an algorithm for + computing it. + + + + +Deutsch & Gailly Informational [Page 1] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + +Table of Contents + + 1. Introduction ................................................... 2 + 1.1. Purpose ................................................... 2 + 1.2. Intended audience ......................................... 3 + 1.3. Scope ..................................................... 3 + 1.4. Compliance ................................................ 3 + 1.5. Definitions of terms and conventions used ................ 3 + 1.6. Changes from previous versions ............................ 3 + 2. Detailed specification ......................................... 3 + 2.1. Overall conventions ....................................... 3 + 2.2. Data format ............................................... 4 + 2.3. Compliance ................................................ 7 + 3. References ..................................................... 7 + 4. Source code .................................................... 8 + 5. Security Considerations ........................................ 8 + 6. Acknowledgements ............................................... 8 + 7. Authors' Addresses ............................................. 8 + 8. Appendix: Rationale ............................................ 9 + 9. Appendix: Sample code ..........................................10 + +1. Introduction + + 1.1. Purpose + + The purpose of this specification is to define a lossless + compressed data format that: + + * Is independent of CPU type, operating system, file system, + and character set, and hence can be used for interchange; + + * Can be produced or consumed, even for an arbitrarily long + sequentially presented input data stream, using only an a + priori bounded amount of intermediate storage, and hence can + be used in data communications or similar structures such as + Unix filters; + + * Can use a number of different compression methods; + + * Can be implemented readily in a manner not covered by + patents, and hence can be practiced freely. + + The data format defined by this specification does not attempt to + allow random access to compressed data. + + + + + + + +Deutsch & Gailly Informational [Page 2] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + 1.2. Intended audience + + This specification is intended for use by implementors of software + to compress data into zlib format and/or decompress data from zlib + format. + + The text of the specification assumes a basic background in + programming at the level of bits and other primitive data + representations. + + 1.3. Scope + + The specification specifies a compressed data format that can be + used for in-memory compression of a sequence of arbitrary bytes. + + 1.4. Compliance + + Unless otherwise indicated below, a compliant decompressor must be + able to accept and decompress any data set that conforms to all + the specifications presented here; a compliant compressor must + produce data sets that conform to all the specifications presented + here. + + 1.5. Definitions of terms and conventions used + + byte: 8 bits stored or transmitted as a unit (same as an octet). + (For this specification, a byte is exactly 8 bits, even on + machines which store a character on a number of bits different + from 8.) See below, for the numbering of bits within a byte. + + 1.6. Changes from previous versions + + Version 3.1 was the first public release of this specification. + In version 3.2, some terminology was changed and the Adler-32 + sample code was rewritten for clarity. In version 3.3, the + support for a preset dictionary was introduced, and the + specification was converted to RFC style. + +2. Detailed specification + + 2.1. Overall conventions + + In the diagrams below, a box like this: + + +---+ + | | <-- the vertical bars might be missing + +---+ + + + + +Deutsch & Gailly Informational [Page 3] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + represents one byte; a box like this: + + +==============+ + | | + +==============+ + + represents a variable number of bytes. + + Bytes stored within a computer do not have a "bit order", since + they are always treated as a unit. However, a byte considered as + an integer between 0 and 255 does have a most- and least- + significant bit, and since we write numbers with the most- + significant digit on the left, we also write bytes with the most- + significant bit on the left. In the diagrams below, we number the + bits of a byte so that bit 0 is the least-significant bit, i.e., + the bits are numbered: + + +--------+ + |76543210| + +--------+ + + Within a computer, a number may occupy multiple bytes. All + multi-byte numbers in the format described here are stored with + the MOST-significant byte first (at the lower memory address). + For example, the decimal number 520 is stored as: + + 0 1 + +--------+--------+ + |00000010|00001000| + +--------+--------+ + ^ ^ + | | + | + less significant byte = 8 + + more significant byte = 2 x 256 + + 2.2. Data format + + A zlib stream has the following structure: + + 0 1 + +---+---+ + |CMF|FLG| (more-->) + +---+---+ + + + + + + + + +Deutsch & Gailly Informational [Page 4] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + (if FLG.FDICT set) + + 0 1 2 3 + +---+---+---+---+ + | DICTID | (more-->) + +---+---+---+---+ + + +=====================+---+---+---+---+ + |...compressed data...| ADLER32 | + +=====================+---+---+---+---+ + + Any data which may appear after ADLER32 are not part of the zlib + stream. + + CMF (Compression Method and flags) + This byte is divided into a 4-bit compression method and a 4- + bit information field depending on the compression method. + + bits 0 to 3 CM Compression method + bits 4 to 7 CINFO Compression info + + CM (Compression method) + This identifies the compression method used in the file. CM = 8 + denotes the "deflate" compression method with a window size up + to 32K. This is the method used by gzip and PNG (see + references [1] and [2] in Chapter 3, below, for the reference + documents). CM = 15 is reserved. It might be used in a future + version of this specification to indicate the presence of an + extra field before the compressed data. + + CINFO (Compression info) + For CM = 8, CINFO is the base-2 logarithm of the LZ77 window + size, minus eight (CINFO=7 indicates a 32K window size). Values + of CINFO above 7 are not allowed in this version of the + specification. CINFO is not defined in this specification for + CM not equal to 8. + + FLG (FLaGs) + This flag byte is divided as follows: + + bits 0 to 4 FCHECK (check bits for CMF and FLG) + bit 5 FDICT (preset dictionary) + bits 6 to 7 FLEVEL (compression level) + + The FCHECK value must be such that CMF and FLG, when viewed as + a 16-bit unsigned integer stored in MSB order (CMF*256 + FLG), + is a multiple of 31. + + + + +Deutsch & Gailly Informational [Page 5] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + FDICT (Preset dictionary) + If FDICT is set, a DICT dictionary identifier is present + immediately after the FLG byte. The dictionary is a sequence of + bytes which are initially fed to the compressor without + producing any compressed output. DICT is the Adler-32 checksum + of this sequence of bytes (see the definition of ADLER32 + below). The decompressor can use this identifier to determine + which dictionary has been used by the compressor. + + FLEVEL (Compression level) + These flags are available for use by specific compression + methods. The "deflate" method (CM = 8) sets these flags as + follows: + + 0 - compressor used fastest algorithm + 1 - compressor used fast algorithm + 2 - compressor used default algorithm + 3 - compressor used maximum compression, slowest algorithm + + The information in FLEVEL is not needed for decompression; it + is there to indicate if recompression might be worthwhile. + + compressed data + For compression method 8, the compressed data is stored in the + deflate compressed data format as described in the document + "DEFLATE Compressed Data Format Specification" by L. Peter + Deutsch. (See reference [3] in Chapter 3, below) + + Other compressed data formats are not specified in this version + of the zlib specification. + + ADLER32 (Adler-32 checksum) + This contains a checksum value of the uncompressed data + (excluding any dictionary data) computed according to Adler-32 + algorithm. This algorithm is a 32-bit extension and improvement + of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + standard. See references [4] and [5] in Chapter 3, below) + + Adler-32 is composed of two sums accumulated per byte: s1 is + the sum of all bytes, s2 is the sum of all s1 values. Both sums + are done modulo 65521. s1 is initialized to 1, s2 to zero. The + Adler-32 checksum is stored as s2*65536 + s1 in most- + significant-byte first (network) order. + + + + + + + + +Deutsch & Gailly Informational [Page 6] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + 2.3. Compliance + + A compliant compressor must produce streams with correct CMF, FLG + and ADLER32, but need not support preset dictionaries. When the + zlib data format is used as part of another standard data format, + the compressor may use only preset dictionaries that are specified + by this other data format. If this other format does not use the + preset dictionary feature, the compressor must not set the FDICT + flag. + + A compliant decompressor must check CMF, FLG, and ADLER32, and + provide an error indication if any of these have incorrect values. + A compliant decompressor must give an error indication if CM is + not one of the values defined in this specification (only the + value 8 is permitted in this version), since another value could + indicate the presence of new features that would cause subsequent + data to be interpreted incorrectly. A compliant decompressor must + give an error indication if FDICT is set and DICTID is not the + identifier of a known preset dictionary. A decompressor may + ignore FLEVEL and still be compliant. When the zlib data format + is being used as a part of another standard format, a compliant + decompressor must support all the preset dictionaries specified by + the other format. When the other format does not use the preset + dictionary feature, a compliant decompressor must reject any + stream in which the FDICT flag is set. + +3. References + + [1] Deutsch, L.P.,"GZIP Compressed Data Format Specification", + available in ftp://ftp.uu.net/pub/archiving/zip/doc/ + + [2] Thomas Boutell, "PNG (Portable Network Graphics) specification", + available in ftp://ftp.uu.net/graphics/png/documents/ + + [3] Deutsch, L.P.,"DEFLATE Compressed Data Format Specification", + available in ftp://ftp.uu.net/pub/archiving/zip/doc/ + + [4] Fletcher, J. G., "An Arithmetic Checksum for Serial + Transmissions," IEEE Transactions on Communications, Vol. COM-30, + No. 1, January 1982, pp. 247-252. + + [5] ITU-T Recommendation X.224, Annex D, "Checksum Algorithms," + November, 1993, pp. 144, 145. (Available from + gopher://info.itu.ch). ITU-T X.244 is also the same as ISO 8073. + + + + + + + +Deutsch & Gailly Informational [Page 7] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + +4. Source code + + Source code for a C language implementation of a "zlib" compliant + library is available at ftp://ftp.uu.net/pub/archiving/zip/zlib/. + +5. Security Considerations + + A decoder that fails to check the ADLER32 checksum value may be + subject to undetected data corruption. + +6. Acknowledgements + + Trademarks cited in this document are the property of their + respective owners. + + Jean-Loup Gailly and Mark Adler designed the zlib format and wrote + the related software described in this specification. Glenn + Randers-Pehrson converted this document to RFC and HTML format. + +7. Authors' Addresses + + L. Peter Deutsch + Aladdin Enterprises + 203 Santa Margarita Ave. + Menlo Park, CA 94025 + + Phone: (415) 322-0103 (AM only) + FAX: (415) 322-1734 + EMail: + + + Jean-Loup Gailly + + EMail: + + Questions about the technical content of this specification can be + sent by email to + + Jean-Loup Gailly and + Mark Adler + + Editorial comments on this specification can be sent by email to + + L. Peter Deutsch and + Glenn Randers-Pehrson + + + + + + +Deutsch & Gailly Informational [Page 8] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + +8. Appendix: Rationale + + 8.1. Preset dictionaries + + A preset dictionary is specially useful to compress short input + sequences. The compressor can take advantage of the dictionary + context to encode the input in a more compact manner. The + decompressor can be initialized with the appropriate context by + virtually decompressing a compressed version of the dictionary + without producing any output. However for certain compression + algorithms such as the deflate algorithm this operation can be + achieved without actually performing any decompression. + + The compressor and the decompressor must use exactly the same + dictionary. The dictionary may be fixed or may be chosen among a + certain number of predefined dictionaries, according to the kind + of input data. The decompressor can determine which dictionary has + been chosen by the compressor by checking the dictionary + identifier. This document does not specify the contents of + predefined dictionaries, since the optimal dictionaries are + application specific. Standard data formats using this feature of + the zlib specification must precisely define the allowed + dictionaries. + + 8.2. The Adler-32 algorithm + + The Adler-32 algorithm is much faster than the CRC32 algorithm yet + still provides an extremely low probability of undetected errors. + + The modulo on unsigned long accumulators can be delayed for 5552 + bytes, so the modulo operation time is negligible. If the bytes + are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + and order sensitive, unlike the first sum, which is just a + checksum. That 65521 is prime is important to avoid a possible + large class of two-byte errors that leave the check unchanged. + (The Fletcher checksum uses 255, which is not prime and which also + makes the Fletcher check insensitive to single byte changes 0 <-> + 255.) + + The sum s1 is initialized to 1 instead of zero to make the length + of the sequence part of s2, so that the length does not have to be + checked separately. (Any sequence of zeroes has a Fletcher + checksum of zero.) + + + + + + + + +Deutsch & Gailly Informational [Page 9] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + +9. Appendix: Sample code + + The following C code computes the Adler-32 checksum of a data buffer. + It is written for clarity, not for speed. The sample code is in the + ANSI C programming language. Non C users may find it easier to read + with these hints: + + & Bitwise AND operator. + >> Bitwise right shift operator. When applied to an + unsigned quantity, as here, right shift inserts zero bit(s) + at the left. + << Bitwise left shift operator. Left shift inserts zero + bit(s) at the right. + ++ "n++" increments the variable n. + % modulo operator: a % b is the remainder of a divided by b. + + #define BASE 65521 /* largest prime smaller than 65536 */ + + /* + Update a running Adler-32 checksum with the bytes buf[0..len-1] + and return the updated checksum. The Adler-32 checksum should be + initialized to 1. + + Usage example: + + unsigned long adler = 1L; + + while (read_buffer(buffer, length) != EOF) { + adler = update_adler32(adler, buffer, length); + } + if (adler != original_adler) error(); + */ + unsigned long update_adler32(unsigned long adler, + unsigned char *buf, int len) + { + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int n; + + for (n = 0; n < len; n++) { + s1 = (s1 + buf[n]) % BASE; + s2 = (s2 + s1) % BASE; + } + return (s2 << 16) + s1; + } + + /* Return the adler32 of the bytes buf[0..len-1] */ + + + + +Deutsch & Gailly Informational [Page 10] + +RFC 1950 ZLIB Compressed Data Format Specification May 1996 + + + unsigned long adler32(unsigned char *buf, int len) + { + return update_adler32(1L, buf, len); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Deutsch & Gailly Informational [Page 11] + diff --git a/zlib/zlib/doc/rfc1951.txt b/zlib/zlib/doc/rfc1951.txt new file mode 100644 index 00000000..403c8c72 --- /dev/null +++ b/zlib/zlib/doc/rfc1951.txt @@ -0,0 +1,955 @@ + + + + + + +Network Working Group P. Deutsch +Request for Comments: 1951 Aladdin Enterprises +Category: Informational May 1996 + + + DEFLATE Compressed Data Format Specification version 1.3 + +Status of This Memo + + This memo provides information for the Internet community. This memo + does not specify an Internet standard of any kind. Distribution of + this memo is unlimited. + +IESG Note: + + The IESG takes no position on the validity of any Intellectual + Property Rights statements contained in this document. + +Notices + + Copyright (c) 1996 L. Peter Deutsch + + Permission is granted to copy and distribute this document for any + purpose and without charge, including translations into other + languages and incorporation into compilations, provided that the + copyright notice and this notice are preserved, and that any + substantive changes or deletions from the original are clearly + marked. + + A pointer to the latest version of this and related documentation in + HTML format can be found at the URL + . + +Abstract + + This specification defines a lossless compressed data format that + compresses data using a combination of the LZ77 algorithm and Huffman + coding, with efficiency comparable to the best currently available + general-purpose compression methods. The data can be produced or + consumed, even for an arbitrarily long sequentially presented input + data stream, using only an a priori bounded amount of intermediate + storage. The format can be implemented readily in a manner not + covered by patents. + + + + + + + + +Deutsch Informational [Page 1] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + +Table of Contents + + 1. Introduction ................................................... 2 + 1.1. Purpose ................................................... 2 + 1.2. Intended audience ......................................... 3 + 1.3. Scope ..................................................... 3 + 1.4. Compliance ................................................ 3 + 1.5. Definitions of terms and conventions used ................ 3 + 1.6. Changes from previous versions ............................ 4 + 2. Compressed representation overview ............................. 4 + 3. Detailed specification ......................................... 5 + 3.1. Overall conventions ....................................... 5 + 3.1.1. Packing into bytes .................................. 5 + 3.2. Compressed block format ................................... 6 + 3.2.1. Synopsis of prefix and Huffman coding ............... 6 + 3.2.2. Use of Huffman coding in the "deflate" format ....... 7 + 3.2.3. Details of block format ............................. 9 + 3.2.4. Non-compressed blocks (BTYPE=00) ................... 11 + 3.2.5. Compressed blocks (length and distance codes) ...... 11 + 3.2.6. Compression with fixed Huffman codes (BTYPE=01) .... 12 + 3.2.7. Compression with dynamic Huffman codes (BTYPE=10) .. 13 + 3.3. Compliance ............................................... 14 + 4. Compression algorithm details ................................. 14 + 5. References .................................................... 16 + 6. Security Considerations ....................................... 16 + 7. Source code ................................................... 16 + 8. Acknowledgements .............................................. 16 + 9. Author's Address .............................................. 17 + +1. Introduction + + 1.1. Purpose + + The purpose of this specification is to define a lossless + compressed data format that: + * Is independent of CPU type, operating system, file system, + and character set, and hence can be used for interchange; + * Can be produced or consumed, even for an arbitrarily long + sequentially presented input data stream, using only an a + priori bounded amount of intermediate storage, and hence + can be used in data communications or similar structures + such as Unix filters; + * Compresses data with efficiency comparable to the best + currently available general-purpose compression methods, + and in particular considerably better than the "compress" + program; + * Can be implemented readily in a manner not covered by + patents, and hence can be practiced freely; + + + +Deutsch Informational [Page 2] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + * Is compatible with the file format produced by the current + widely used gzip utility, in that conforming decompressors + will be able to read data produced by the existing gzip + compressor. + + The data format defined by this specification does not attempt to: + + * Allow random access to compressed data; + * Compress specialized data (e.g., raster graphics) as well + as the best currently available specialized algorithms. + + A simple counting argument shows that no lossless compression + algorithm can compress every possible input data set. For the + format defined here, the worst case expansion is 5 bytes per 32K- + byte block, i.e., a size increase of 0.015% for large data sets. + English text usually compresses by a factor of 2.5 to 3; + executable files usually compress somewhat less; graphical data + such as raster images may compress much more. + + 1.2. Intended audience + + This specification is intended for use by implementors of software + to compress data into "deflate" format and/or decompress data from + "deflate" format. + + The text of the specification assumes a basic background in + programming at the level of bits and other primitive data + representations. Familiarity with the technique of Huffman coding + is helpful but not required. + + 1.3. Scope + + The specification specifies a method for representing a sequence + of bytes as a (usually shorter) sequence of bits, and a method for + packing the latter bit sequence into bytes. + + 1.4. Compliance + + Unless otherwise indicated below, a compliant decompressor must be + able to accept and decompress any data set that conforms to all + the specifications presented here; a compliant compressor must + produce data sets that conform to all the specifications presented + here. + + 1.5. Definitions of terms and conventions used + + Byte: 8 bits stored or transmitted as a unit (same as an octet). + For this specification, a byte is exactly 8 bits, even on machines + + + +Deutsch Informational [Page 3] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + which store a character on a number of bits different from eight. + See below, for the numbering of bits within a byte. + + String: a sequence of arbitrary bytes. + + 1.6. Changes from previous versions + + There have been no technical changes to the deflate format since + version 1.1 of this specification. In version 1.2, some + terminology was changed. Version 1.3 is a conversion of the + specification to RFC style. + +2. Compressed representation overview + + A compressed data set consists of a series of blocks, corresponding + to successive blocks of input data. The block sizes are arbitrary, + except that non-compressible blocks are limited to 65,535 bytes. + + Each block is compressed using a combination of the LZ77 algorithm + and Huffman coding. The Huffman trees for each block are independent + of those for previous or subsequent blocks; the LZ77 algorithm may + use a reference to a duplicated string occurring in a previous block, + up to 32K input bytes before. + + Each block consists of two parts: a pair of Huffman code trees that + describe the representation of the compressed data part, and a + compressed data part. (The Huffman trees themselves are compressed + using Huffman encoding.) The compressed data consists of a series of + elements of two types: literal bytes (of strings that have not been + detected as duplicated within the previous 32K input bytes), and + pointers to duplicated strings, where a pointer is represented as a + pair . The representation used in the + "deflate" format limits distances to 32K bytes and lengths to 258 + bytes, but does not limit the size of a block, except for + uncompressible blocks, which are limited as noted above. + + Each type of value (literals, distances, and lengths) in the + compressed data is represented using a Huffman code, using one code + tree for literals and lengths and a separate code tree for distances. + The code trees for each block appear in a compact form just before + the compressed data for that block. + + + + + + + + + + +Deutsch Informational [Page 4] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + +3. Detailed specification + + 3.1. Overall conventions In the diagrams below, a box like this: + + +---+ + | | <-- the vertical bars might be missing + +---+ + + represents one byte; a box like this: + + +==============+ + | | + +==============+ + + represents a variable number of bytes. + + Bytes stored within a computer do not have a "bit order", since + they are always treated as a unit. However, a byte considered as + an integer between 0 and 255 does have a most- and least- + significant bit, and since we write numbers with the most- + significant digit on the left, we also write bytes with the most- + significant bit on the left. In the diagrams below, we number the + bits of a byte so that bit 0 is the least-significant bit, i.e., + the bits are numbered: + + +--------+ + |76543210| + +--------+ + + Within a computer, a number may occupy multiple bytes. All + multi-byte numbers in the format described here are stored with + the least-significant byte first (at the lower memory address). + For example, the decimal number 520 is stored as: + + 0 1 + +--------+--------+ + |00001000|00000010| + +--------+--------+ + ^ ^ + | | + | + more significant byte = 2 x 256 + + less significant byte = 8 + + 3.1.1. Packing into bytes + + This document does not address the issue of the order in which + bits of a byte are transmitted on a bit-sequential medium, + since the final data format described here is byte- rather than + + + +Deutsch Informational [Page 5] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + bit-oriented. However, we describe the compressed block format + in below, as a sequence of data elements of various bit + lengths, not a sequence of bytes. We must therefore specify + how to pack these data elements into bytes to form the final + compressed byte sequence: + + * Data elements are packed into bytes in order of + increasing bit number within the byte, i.e., starting + with the least-significant bit of the byte. + * Data elements other than Huffman codes are packed + starting with the least-significant bit of the data + element. + * Huffman codes are packed starting with the most- + significant bit of the code. + + In other words, if one were to print out the compressed data as + a sequence of bytes, starting with the first byte at the + *right* margin and proceeding to the *left*, with the most- + significant bit of each byte on the left as usual, one would be + able to parse the result from right to left, with fixed-width + elements in the correct MSB-to-LSB order and Huffman codes in + bit-reversed order (i.e., with the first bit of the code in the + relative LSB position). + + 3.2. Compressed block format + + 3.2.1. Synopsis of prefix and Huffman coding + + Prefix coding represents symbols from an a priori known + alphabet by bit sequences (codes), one code for each symbol, in + a manner such that different symbols may be represented by bit + sequences of different lengths, but a parser can always parse + an encoded string unambiguously symbol-by-symbol. + + We define a prefix code in terms of a binary tree in which the + two edges descending from each non-leaf node are labeled 0 and + 1 and in which the leaf nodes correspond one-for-one with (are + labeled with) the symbols of the alphabet; then the code for a + symbol is the sequence of 0's and 1's on the edges leading from + the root to the leaf labeled with that symbol. For example: + + + + + + + + + + + +Deutsch Informational [Page 6] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + /\ Symbol Code + 0 1 ------ ---- + / \ A 00 + /\ B B 1 + 0 1 C 011 + / \ D 010 + A /\ + 0 1 + / \ + D C + + A parser can decode the next symbol from an encoded input + stream by walking down the tree from the root, at each step + choosing the edge corresponding to the next input bit. + + Given an alphabet with known symbol frequencies, the Huffman + algorithm allows the construction of an optimal prefix code + (one which represents strings with those symbol frequencies + using the fewest bits of any possible prefix codes for that + alphabet). Such a code is called a Huffman code. (See + reference [1] in Chapter 5, references for additional + information on Huffman codes.) + + Note that in the "deflate" format, the Huffman codes for the + various alphabets must not exceed certain maximum code lengths. + This constraint complicates the algorithm for computing code + lengths from symbol frequencies. Again, see Chapter 5, + references for details. + + 3.2.2. Use of Huffman coding in the "deflate" format + + The Huffman codes used for each alphabet in the "deflate" + format have two additional rules: + + * All codes of a given bit length have lexicographically + consecutive values, in the same order as the symbols + they represent; + + * Shorter codes lexicographically precede longer codes. + + + + + + + + + + + + +Deutsch Informational [Page 7] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + We could recode the example above to follow this rule as + follows, assuming that the order of the alphabet is ABCD: + + Symbol Code + ------ ---- + A 10 + B 0 + C 110 + D 111 + + I.e., 0 precedes 10 which precedes 11x, and 110 and 111 are + lexicographically consecutive. + + Given this rule, we can define the Huffman code for an alphabet + just by giving the bit lengths of the codes for each symbol of + the alphabet in order; this is sufficient to determine the + actual codes. In our example, the code is completely defined + by the sequence of bit lengths (2, 1, 3, 3). The following + algorithm generates the codes as integers, intended to be read + from most- to least-significant bit. The code lengths are + initially in tree[I].Len; the codes are produced in + tree[I].Code. + + 1) Count the number of codes for each code length. Let + bl_count[N] be the number of codes of length N, N >= 1. + + 2) Find the numerical value of the smallest code for each + code length: + + code = 0; + bl_count[0] = 0; + for (bits = 1; bits <= MAX_BITS; bits++) { + code = (code + bl_count[bits-1]) << 1; + next_code[bits] = code; + } + + 3) Assign numerical values to all codes, using consecutive + values for all codes of the same length with the base + values determined at step 2. Codes that are never used + (which have a bit length of zero) must not be assigned a + value. + + for (n = 0; n <= max_code; n++) { + len = tree[n].Len; + if (len != 0) { + tree[n].Code = next_code[len]; + next_code[len]++; + } + + + +Deutsch Informational [Page 8] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + } + + Example: + + Consider the alphabet ABCDEFGH, with bit lengths (3, 3, 3, 3, + 3, 2, 4, 4). After step 1, we have: + + N bl_count[N] + - ----------- + 2 1 + 3 5 + 4 2 + + Step 2 computes the following next_code values: + + N next_code[N] + - ------------ + 1 0 + 2 0 + 3 2 + 4 14 + + Step 3 produces the following code values: + + Symbol Length Code + ------ ------ ---- + A 3 010 + B 3 011 + C 3 100 + D 3 101 + E 3 110 + F 2 00 + G 4 1110 + H 4 1111 + + 3.2.3. Details of block format + + Each block of compressed data begins with 3 header bits + containing the following data: + + first bit BFINAL + next 2 bits BTYPE + + Note that the header bits do not necessarily begin on a byte + boundary, since a block does not necessarily occupy an integral + number of bytes. + + + + + +Deutsch Informational [Page 9] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + BFINAL is set if and only if this is the last block of the data + set. + + BTYPE specifies how the data are compressed, as follows: + + 00 - no compression + 01 - compressed with fixed Huffman codes + 10 - compressed with dynamic Huffman codes + 11 - reserved (error) + + The only difference between the two compressed cases is how the + Huffman codes for the literal/length and distance alphabets are + defined. + + In all cases, the decoding algorithm for the actual data is as + follows: + + do + read block header from input stream. + if stored with no compression + skip any remaining bits in current partially + processed byte + read LEN and NLEN (see next section) + copy LEN bytes of data to output + otherwise + if compressed with dynamic Huffman codes + read representation of code trees (see + subsection below) + loop (until end of block code recognized) + decode literal/length value from input stream + if value < 256 + copy value (literal byte) to output stream + otherwise + if value = end of block (256) + break from loop + otherwise (value = 257..285) + decode distance from input stream + + move backwards distance bytes in the output + stream, and copy length bytes from this + position to the output stream. + end loop + while not last block + + Note that a duplicated string reference may refer to a string + in a previous block; i.e., the backward distance may cross one + or more block boundaries. However a distance cannot refer past + the beginning of the output stream. (An application using a + + + +Deutsch Informational [Page 10] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + preset dictionary might discard part of the output stream; a + distance can refer to that part of the output stream anyway) + Note also that the referenced string may overlap the current + position; for example, if the last 2 bytes decoded have values + X and Y, a string reference with + adds X,Y,X,Y,X to the output stream. + + We now specify each compression method in turn. + + 3.2.4. Non-compressed blocks (BTYPE=00) + + Any bits of input up to the next byte boundary are ignored. + The rest of the block consists of the following information: + + 0 1 2 3 4... + +---+---+---+---+================================+ + | LEN | NLEN |... LEN bytes of literal data...| + +---+---+---+---+================================+ + + LEN is the number of data bytes in the block. NLEN is the + one's complement of LEN. + + 3.2.5. Compressed blocks (length and distance codes) + + As noted above, encoded data blocks in the "deflate" format + consist of sequences of symbols drawn from three conceptually + distinct alphabets: either literal bytes, from the alphabet of + byte values (0..255), or pairs, + where the length is drawn from (3..258) and the distance is + drawn from (1..32,768). In fact, the literal and length + alphabets are merged into a single alphabet (0..285), where + values 0..255 represent literal bytes, the value 256 indicates + end-of-block, and values 257..285 represent length codes + (possibly in conjunction with extra bits following the symbol + code) as follows: + + + + + + + + + + + + + + + + +Deutsch Informational [Page 11] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + Extra Extra Extra + Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 + + The extra bits should be interpreted as a machine integer + stored with the most-significant bit first, e.g., bits 1110 + represent the value 14. + + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 + + 3.2.6. Compression with fixed Huffman codes (BTYPE=01) + + The Huffman codes for the two alphabets are fixed, and are not + represented explicitly in the data. The Huffman code lengths + for the literal/length alphabet are: + + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 + + + +Deutsch Informational [Page 12] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + The code lengths are sufficient to generate the actual codes, + as described above; we show the codes in the table for added + clarity. Literal/length values 286-287 will never actually + occur in the compressed data, but participate in the code + construction. + + Distance codes 0-31 are represented by (fixed-length) 5-bit + codes, with possible additional bits as shown in the table + shown in Paragraph 3.2.5, above. Note that distance codes 30- + 31 will never actually occur in the compressed data. + + 3.2.7. Compression with dynamic Huffman codes (BTYPE=10) + + The Huffman codes for the two alphabets appear in the block + immediately after the header bits and before the actual + compressed data, first the literal/length code and then the + distance code. Each code is defined by a sequence of code + lengths, as discussed in Paragraph 3.2.2, above. For even + greater compactness, the code length sequences themselves are + compressed using a Huffman code. The alphabet for code lengths + is as follows: + + 0 - 15: Represent code lengths of 0 - 15 + 16: Copy the previous code length 3 - 6 times. + The next 2 bits indicate repeat length + (0 = 3, ... , 3 = 6) + Example: Codes 8, 16 (+2 bits 11), + 16 (+2 bits 10) will expand to + 12 code lengths of 8 (1 + 6 + 5) + 17: Repeat a code length of 0 for 3 - 10 times. + (3 bits of length) + 18: Repeat a code length of 0 for 11 - 138 times + (7 bits of length) + + A code length of 0 indicates that the corresponding symbol in + the literal/length or distance alphabet will not occur in the + block, and should not participate in the Huffman code + construction algorithm given earlier. If only one distance + code is used, it is encoded using one bit, not zero bits; in + this case there is a single code length of one, with one unused + code. One distance code of zero bits means that there are no + distance codes used at all (the data is all literals). + + We can now define the format of the block: + + 5 Bits: HLIT, # of Literal/Length codes - 257 (257 - 286) + 5 Bits: HDIST, # of Distance codes - 1 (1 - 32) + 4 Bits: HCLEN, # of Code Length codes - 4 (4 - 19) + + + +Deutsch Informational [Page 13] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + (HCLEN + 4) x 3 bits: code lengths for the code length + alphabet given just above, in the order: 16, 17, 18, + 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + + These code lengths are interpreted as 3-bit integers + (0-7); as above, a code length of 0 means the + corresponding symbol (literal/length or distance code + length) is not used. + + HLIT + 257 code lengths for the literal/length alphabet, + encoded using the code length Huffman code + + HDIST + 1 code lengths for the distance alphabet, + encoded using the code length Huffman code + + The actual compressed data of the block, + encoded using the literal/length and distance Huffman + codes + + The literal/length symbol 256 (end of data), + encoded using the literal/length Huffman code + + The code length repeat codes can cross from HLIT + 257 to the + HDIST + 1 code lengths. In other words, all code lengths form + a single sequence of HLIT + HDIST + 258 values. + + 3.3. Compliance + + A compressor may limit further the ranges of values specified in + the previous section and still be compliant; for example, it may + limit the range of backward pointers to some value smaller than + 32K. Similarly, a compressor may limit the size of blocks so that + a compressible block fits in memory. + + A compliant decompressor must accept the full range of possible + values defined in the previous section, and must accept blocks of + arbitrary size. + +4. Compression algorithm details + + While it is the intent of this document to define the "deflate" + compressed data format without reference to any particular + compression algorithm, the format is related to the compressed + formats produced by LZ77 (Lempel-Ziv 1977, see reference [2] below); + since many variations of LZ77 are patented, it is strongly + recommended that the implementor of a compressor follow the general + algorithm presented here, which is known not to be patented per se. + The material in this section is not part of the definition of the + + + +Deutsch Informational [Page 14] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + + specification per se, and a compressor need not follow it in order to + be compliant. + + The compressor terminates a block when it determines that starting a + new block with fresh trees would be useful, or when the block size + fills up the compressor's block buffer. + + The compressor uses a chained hash table to find duplicated strings, + using a hash function that operates on 3-byte sequences. At any + given point during compression, let XYZ be the next 3 input bytes to + be examined (not necessarily all different, of course). First, the + compressor examines the hash chain for XYZ. If the chain is empty, + the compressor simply writes out X as a literal byte and advances one + byte in the input. If the hash chain is not empty, indicating that + the sequence XYZ (or, if we are unlucky, some other 3 bytes with the + same hash function value) has occurred recently, the compressor + compares all strings on the XYZ hash chain with the actual input data + sequence starting at the current point, and selects the longest + match. + + The compressor searches the hash chains starting with the most recent + strings, to favor small distances and thus take advantage of the + Huffman encoding. The hash chains are singly linked. There are no + deletions from the hash chains; the algorithm simply discards matches + that are too old. To avoid a worst-case situation, very long hash + chains are arbitrarily truncated at a certain length, determined by a + run-time parameter. + + To improve overall compression, the compressor optionally defers the + selection of matches ("lazy matching"): after a match of length N has + been found, the compressor searches for a longer match starting at + the next input byte. If it finds a longer match, it truncates the + previous match to a length of one (thus producing a single literal + byte) and then emits the longer match. Otherwise, it emits the + original match, and, as described above, advances N bytes before + continuing. + + Run-time parameters also control this "lazy match" procedure. If + compression ratio is most important, the compressor attempts a + complete second search regardless of the length of the first match. + In the normal case, if the current match is "long enough", the + compressor reduces the search for a longer match, thus speeding up + the process. If speed is most important, the compressor inserts new + strings in the hash table only when no match was found, or when the + match is not "too long". This degrades the compression ratio but + saves time since there are both fewer insertions and fewer searches. + + + + + +Deutsch Informational [Page 15] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + +5. References + + [1] Huffman, D. A., "A Method for the Construction of Minimum + Redundancy Codes", Proceedings of the Institute of Radio + Engineers, September 1952, Volume 40, Number 9, pp. 1098-1101. + + [2] Ziv J., Lempel A., "A Universal Algorithm for Sequential Data + Compression", IEEE Transactions on Information Theory, Vol. 23, + No. 3, pp. 337-343. + + [3] Gailly, J.-L., and Adler, M., ZLIB documentation and sources, + available in ftp://ftp.uu.net/pub/archiving/zip/doc/ + + [4] Gailly, J.-L., and Adler, M., GZIP documentation and sources, + available as gzip-*.tar in ftp://prep.ai.mit.edu/pub/gnu/ + + [5] Schwartz, E. S., and Kallick, B. "Generating a canonical prefix + encoding." Comm. ACM, 7,3 (Mar. 1964), pp. 166-169. + + [6] Hirschberg and Lelewer, "Efficient decoding of prefix codes," + Comm. ACM, 33,4, April 1990, pp. 449-459. + +6. Security Considerations + + Any data compression method involves the reduction of redundancy in + the data. Consequently, any corruption of the data is likely to have + severe effects and be difficult to correct. Uncompressed text, on + the other hand, will probably still be readable despite the presence + of some corrupted bytes. + + It is recommended that systems using this data format provide some + means of validating the integrity of the compressed data. See + reference [3], for example. + +7. Source code + + Source code for a C language implementation of a "deflate" compliant + compressor and decompressor is available within the zlib package at + ftp://ftp.uu.net/pub/archiving/zip/zlib/. + +8. Acknowledgements + + Trademarks cited in this document are the property of their + respective owners. + + Phil Katz designed the deflate format. Jean-Loup Gailly and Mark + Adler wrote the related software described in this specification. + Glenn Randers-Pehrson converted this document to RFC and HTML format. + + + +Deutsch Informational [Page 16] + +RFC 1951 DEFLATE Compressed Data Format Specification May 1996 + + +9. Author's Address + + L. Peter Deutsch + Aladdin Enterprises + 203 Santa Margarita Ave. + Menlo Park, CA 94025 + + Phone: (415) 322-0103 (AM only) + FAX: (415) 322-1734 + EMail: + + Questions about the technical content of this specification can be + sent by email to: + + Jean-Loup Gailly and + Mark Adler + + Editorial comments on this specification can be sent by email to: + + L. Peter Deutsch and + Glenn Randers-Pehrson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Deutsch Informational [Page 17] + diff --git a/zlib/zlib/doc/rfc1952.txt b/zlib/zlib/doc/rfc1952.txt new file mode 100644 index 00000000..a8e51b45 --- /dev/null +++ b/zlib/zlib/doc/rfc1952.txt @@ -0,0 +1,675 @@ + + + + + + +Network Working Group P. Deutsch +Request for Comments: 1952 Aladdin Enterprises +Category: Informational May 1996 + + + GZIP file format specification version 4.3 + +Status of This Memo + + This memo provides information for the Internet community. This memo + does not specify an Internet standard of any kind. Distribution of + this memo is unlimited. + +IESG Note: + + The IESG takes no position on the validity of any Intellectual + Property Rights statements contained in this document. + +Notices + + Copyright (c) 1996 L. Peter Deutsch + + Permission is granted to copy and distribute this document for any + purpose and without charge, including translations into other + languages and incorporation into compilations, provided that the + copyright notice and this notice are preserved, and that any + substantive changes or deletions from the original are clearly + marked. + + A pointer to the latest version of this and related documentation in + HTML format can be found at the URL + . + +Abstract + + This specification defines a lossless compressed data format that is + compatible with the widely used GZIP utility. The format includes a + cyclic redundancy check value for detecting data corruption. The + format presently uses the DEFLATE method of compression but can be + easily extended to use other compression methods. The format can be + implemented readily in a manner not covered by patents. + + + + + + + + + + +Deutsch Informational [Page 1] + +RFC 1952 GZIP File Format Specification May 1996 + + +Table of Contents + + 1. Introduction ................................................... 2 + 1.1. Purpose ................................................... 2 + 1.2. Intended audience ......................................... 3 + 1.3. Scope ..................................................... 3 + 1.4. Compliance ................................................ 3 + 1.5. Definitions of terms and conventions used ................. 3 + 1.6. Changes from previous versions ............................ 3 + 2. Detailed specification ......................................... 4 + 2.1. Overall conventions ....................................... 4 + 2.2. File format ............................................... 5 + 2.3. Member format ............................................. 5 + 2.3.1. Member header and trailer ........................... 6 + 2.3.1.1. Extra field ................................... 8 + 2.3.1.2. Compliance .................................... 9 + 3. References .................................................. 9 + 4. Security Considerations .................................... 10 + 5. Acknowledgements ........................................... 10 + 6. Author's Address ........................................... 10 + 7. Appendix: Jean-Loup Gailly's gzip utility .................. 11 + 8. Appendix: Sample CRC Code .................................. 11 + +1. Introduction + + 1.1. Purpose + + The purpose of this specification is to define a lossless + compressed data format that: + + * Is independent of CPU type, operating system, file system, + and character set, and hence can be used for interchange; + * Can compress or decompress a data stream (as opposed to a + randomly accessible file) to produce another data stream, + using only an a priori bounded amount of intermediate + storage, and hence can be used in data communications or + similar structures such as Unix filters; + * Compresses data with efficiency comparable to the best + currently available general-purpose compression methods, + and in particular considerably better than the "compress" + program; + * Can be implemented readily in a manner not covered by + patents, and hence can be practiced freely; + * Is compatible with the file format produced by the current + widely used gzip utility, in that conforming decompressors + will be able to read data produced by the existing gzip + compressor. + + + + +Deutsch Informational [Page 2] + +RFC 1952 GZIP File Format Specification May 1996 + + + The data format defined by this specification does not attempt to: + + * Provide random access to compressed data; + * Compress specialized data (e.g., raster graphics) as well as + the best currently available specialized algorithms. + + 1.2. Intended audience + + This specification is intended for use by implementors of software + to compress data into gzip format and/or decompress data from gzip + format. + + The text of the specification assumes a basic background in + programming at the level of bits and other primitive data + representations. + + 1.3. Scope + + The specification specifies a compression method and a file format + (the latter assuming only that a file can store a sequence of + arbitrary bytes). It does not specify any particular interface to + a file system or anything about character sets or encodings + (except for file names and comments, which are optional). + + 1.4. Compliance + + Unless otherwise indicated below, a compliant decompressor must be + able to accept and decompress any file that conforms to all the + specifications presented here; a compliant compressor must produce + files that conform to all the specifications presented here. The + material in the appendices is not part of the specification per se + and is not relevant to compliance. + + 1.5. Definitions of terms and conventions used + + byte: 8 bits stored or transmitted as a unit (same as an octet). + (For this specification, a byte is exactly 8 bits, even on + machines which store a character on a number of bits different + from 8.) See below for the numbering of bits within a byte. + + 1.6. Changes from previous versions + + There have been no technical changes to the gzip format since + version 4.1 of this specification. In version 4.2, some + terminology was changed, and the sample CRC code was rewritten for + clarity and to eliminate the requirement for the caller to do pre- + and post-conditioning. Version 4.3 is a conversion of the + specification to RFC style. + + + +Deutsch Informational [Page 3] + +RFC 1952 GZIP File Format Specification May 1996 + + +2. Detailed specification + + 2.1. Overall conventions + + In the diagrams below, a box like this: + + +---+ + | | <-- the vertical bars might be missing + +---+ + + represents one byte; a box like this: + + +==============+ + | | + +==============+ + + represents a variable number of bytes. + + Bytes stored within a computer do not have a "bit order", since + they are always treated as a unit. However, a byte considered as + an integer between 0 and 255 does have a most- and least- + significant bit, and since we write numbers with the most- + significant digit on the left, we also write bytes with the most- + significant bit on the left. In the diagrams below, we number the + bits of a byte so that bit 0 is the least-significant bit, i.e., + the bits are numbered: + + +--------+ + |76543210| + +--------+ + + This document does not address the issue of the order in which + bits of a byte are transmitted on a bit-sequential medium, since + the data format described here is byte- rather than bit-oriented. + + Within a computer, a number may occupy multiple bytes. All + multi-byte numbers in the format described here are stored with + the least-significant byte first (at the lower memory address). + For example, the decimal number 520 is stored as: + + 0 1 + +--------+--------+ + |00001000|00000010| + +--------+--------+ + ^ ^ + | | + | + more significant byte = 2 x 256 + + less significant byte = 8 + + + +Deutsch Informational [Page 4] + +RFC 1952 GZIP File Format Specification May 1996 + + + 2.2. File format + + A gzip file consists of a series of "members" (compressed data + sets). The format of each member is specified in the following + section. The members simply appear one after another in the file, + with no additional information before, between, or after them. + + 2.3. Member format + + Each member has the following structure: + + +---+---+---+---+---+---+---+---+---+---+ + |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) + +---+---+---+---+---+---+---+---+---+---+ + + (if FLG.FEXTRA set) + + +---+---+=================================+ + | XLEN |...XLEN bytes of "extra field"...| (more-->) + +---+---+=================================+ + + (if FLG.FNAME set) + + +=========================================+ + |...original file name, zero-terminated...| (more-->) + +=========================================+ + + (if FLG.FCOMMENT set) + + +===================================+ + |...file comment, zero-terminated...| (more-->) + +===================================+ + + (if FLG.FHCRC set) + + +---+---+ + | CRC16 | + +---+---+ + + +=======================+ + |...compressed blocks...| (more-->) + +=======================+ + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | CRC32 | ISIZE | + +---+---+---+---+---+---+---+---+ + + + + +Deutsch Informational [Page 5] + +RFC 1952 GZIP File Format Specification May 1996 + + + 2.3.1. Member header and trailer + + ID1 (IDentification 1) + ID2 (IDentification 2) + These have the fixed values ID1 = 31 (0x1f, \037), ID2 = 139 + (0x8b, \213), to identify the file as being in gzip format. + + CM (Compression Method) + This identifies the compression method used in the file. CM + = 0-7 are reserved. CM = 8 denotes the "deflate" + compression method, which is the one customarily used by + gzip and which is documented elsewhere. + + FLG (FLaGs) + This flag byte is divided into individual bits as follows: + + bit 0 FTEXT + bit 1 FHCRC + bit 2 FEXTRA + bit 3 FNAME + bit 4 FCOMMENT + bit 5 reserved + bit 6 reserved + bit 7 reserved + + If FTEXT is set, the file is probably ASCII text. This is + an optional indication, which the compressor may set by + checking a small amount of the input data to see whether any + non-ASCII characters are present. In case of doubt, FTEXT + is cleared, indicating binary data. For systems which have + different file formats for ascii text and binary data, the + decompressor can use FTEXT to choose the appropriate format. + We deliberately do not specify the algorithm used to set + this bit, since a compressor always has the option of + leaving it cleared and a decompressor always has the option + of ignoring it and letting some other program handle issues + of data conversion. + + If FHCRC is set, a CRC16 for the gzip header is present, + immediately before the compressed data. The CRC16 consists + of the two least significant bytes of the CRC32 for all + bytes of the gzip header up to and not including the CRC16. + [The FHCRC bit was never set by versions of gzip up to + 1.2.4, even though it was documented with a different + meaning in gzip 1.2.4.] + + If FEXTRA is set, optional extra fields are present, as + described in a following section. + + + +Deutsch Informational [Page 6] + +RFC 1952 GZIP File Format Specification May 1996 + + + If FNAME is set, an original file name is present, + terminated by a zero byte. The name must consist of ISO + 8859-1 (LATIN-1) characters; on operating systems using + EBCDIC or any other character set for file names, the name + must be translated to the ISO LATIN-1 character set. This + is the original name of the file being compressed, with any + directory components removed, and, if the file being + compressed is on a file system with case insensitive names, + forced to lower case. There is no original file name if the + data was compressed from a source other than a named file; + for example, if the source was stdin on a Unix system, there + is no file name. + + If FCOMMENT is set, a zero-terminated file comment is + present. This comment is not interpreted; it is only + intended for human consumption. The comment must consist of + ISO 8859-1 (LATIN-1) characters. Line breaks should be + denoted by a single line feed character (10 decimal). + + Reserved FLG bits must be zero. + + MTIME (Modification TIME) + This gives the most recent modification time of the original + file being compressed. The time is in Unix format, i.e., + seconds since 00:00:00 GMT, Jan. 1, 1970. (Note that this + may cause problems for MS-DOS and other systems that use + local rather than Universal time.) If the compressed data + did not come from a file, MTIME is set to the time at which + compression started. MTIME = 0 means no time stamp is + available. + + XFL (eXtra FLags) + These flags are available for use by specific compression + methods. The "deflate" method (CM = 8) sets these flags as + follows: + + XFL = 2 - compressor used maximum compression, + slowest algorithm + XFL = 4 - compressor used fastest algorithm + + OS (Operating System) + This identifies the type of file system on which compression + took place. This may be useful in determining end-of-line + convention for text files. The currently defined values are + as follows: + + + + + + +Deutsch Informational [Page 7] + +RFC 1952 GZIP File Format Specification May 1996 + + + 0 - FAT filesystem (MS-DOS, OS/2, NT/Win32) + 1 - Amiga + 2 - VMS (or OpenVMS) + 3 - Unix + 4 - VM/CMS + 5 - Atari TOS + 6 - HPFS filesystem (OS/2, NT) + 7 - Macintosh + 8 - Z-System + 9 - CP/M + 10 - TOPS-20 + 11 - NTFS filesystem (NT) + 12 - QDOS + 13 - Acorn RISCOS + 255 - unknown + + XLEN (eXtra LENgth) + If FLG.FEXTRA is set, this gives the length of the optional + extra field. See below for details. + + CRC32 (CRC-32) + This contains a Cyclic Redundancy Check value of the + uncompressed data computed according to CRC-32 algorithm + used in the ISO 3309 standard and in section 8.1.1.6.2 of + ITU-T recommendation V.42. (See http://www.iso.ch for + ordering ISO documents. See gopher://info.itu.ch for an + online version of ITU-T V.42.) + + ISIZE (Input SIZE) + This contains the size of the original (uncompressed) input + data modulo 2^32. + + 2.3.1.1. Extra field + + If the FLG.FEXTRA bit is set, an "extra field" is present in + the header, with total length XLEN bytes. It consists of a + series of subfields, each of the form: + + +---+---+---+---+==================================+ + |SI1|SI2| LEN |... LEN bytes of subfield data ...| + +---+---+---+---+==================================+ + + SI1 and SI2 provide a subfield ID, typically two ASCII letters + with some mnemonic value. Jean-Loup Gailly + is maintaining a registry of subfield + IDs; please send him any subfield ID you wish to use. Subfield + IDs with SI2 = 0 are reserved for future use. The following + IDs are currently defined: + + + +Deutsch Informational [Page 8] + +RFC 1952 GZIP File Format Specification May 1996 + + + SI1 SI2 Data + ---------- ---------- ---- + 0x41 ('A') 0x70 ('P') Apollo file type information + + LEN gives the length of the subfield data, excluding the 4 + initial bytes. + + 2.3.1.2. Compliance + + A compliant compressor must produce files with correct ID1, + ID2, CM, CRC32, and ISIZE, but may set all the other fields in + the fixed-length part of the header to default values (255 for + OS, 0 for all others). The compressor must set all reserved + bits to zero. + + A compliant decompressor must check ID1, ID2, and CM, and + provide an error indication if any of these have incorrect + values. It must examine FEXTRA/XLEN, FNAME, FCOMMENT and FHCRC + at least so it can skip over the optional fields if they are + present. It need not examine any other part of the header or + trailer; in particular, a decompressor may ignore FTEXT and OS + and always produce binary output, and still be compliant. A + compliant decompressor must give an error indication if any + reserved bit is non-zero, since such a bit could indicate the + presence of a new field that would cause subsequent data to be + interpreted incorrectly. + +3. References + + [1] "Information Processing - 8-bit single-byte coded graphic + character sets - Part 1: Latin alphabet No.1" (ISO 8859-1:1987). + The ISO 8859-1 (Latin-1) character set is a superset of 7-bit + ASCII. Files defining this character set are available as + iso_8859-1.* in ftp://ftp.uu.net/graphics/png/documents/ + + [2] ISO 3309 + + [3] ITU-T recommendation V.42 + + [4] Deutsch, L.P.,"DEFLATE Compressed Data Format Specification", + available in ftp://ftp.uu.net/pub/archiving/zip/doc/ + + [5] Gailly, J.-L., GZIP documentation, available as gzip-*.tar in + ftp://prep.ai.mit.edu/pub/gnu/ + + [6] Sarwate, D.V., "Computation of Cyclic Redundancy Checks via Table + Look-Up", Communications of the ACM, 31(8), pp.1008-1013. + + + + +Deutsch Informational [Page 9] + +RFC 1952 GZIP File Format Specification May 1996 + + + [7] Schwaderer, W.D., "CRC Calculation", April 85 PC Tech Journal, + pp.118-133. + + [8] ftp://ftp.adelaide.edu.au/pub/rocksoft/papers/crc_v3.txt, + describing the CRC concept. + +4. Security Considerations + + Any data compression method involves the reduction of redundancy in + the data. Consequently, any corruption of the data is likely to have + severe effects and be difficult to correct. Uncompressed text, on + the other hand, will probably still be readable despite the presence + of some corrupted bytes. + + It is recommended that systems using this data format provide some + means of validating the integrity of the compressed data, such as by + setting and checking the CRC-32 check value. + +5. Acknowledgements + + Trademarks cited in this document are the property of their + respective owners. + + Jean-Loup Gailly designed the gzip format and wrote, with Mark Adler, + the related software described in this specification. Glenn + Randers-Pehrson converted this document to RFC and HTML format. + +6. Author's Address + + L. Peter Deutsch + Aladdin Enterprises + 203 Santa Margarita Ave. + Menlo Park, CA 94025 + + Phone: (415) 322-0103 (AM only) + FAX: (415) 322-1734 + EMail: + + Questions about the technical content of this specification can be + sent by email to: + + Jean-Loup Gailly and + Mark Adler + + Editorial comments on this specification can be sent by email to: + + L. Peter Deutsch and + Glenn Randers-Pehrson + + + +Deutsch Informational [Page 10] + +RFC 1952 GZIP File Format Specification May 1996 + + +7. Appendix: Jean-Loup Gailly's gzip utility + + The most widely used implementation of gzip compression, and the + original documentation on which this specification is based, were + created by Jean-Loup Gailly . Since this + implementation is a de facto standard, we mention some more of its + features here. Again, the material in this section is not part of + the specification per se, and implementations need not follow it to + be compliant. + + When compressing or decompressing a file, gzip preserves the + protection, ownership, and modification time attributes on the local + file system, since there is no provision for representing protection + attributes in the gzip file format itself. Since the file format + includes a modification time, the gzip decompressor provides a + command line switch that assigns the modification time from the file, + rather than the local modification time of the compressed input, to + the decompressed output. + +8. Appendix: Sample CRC Code + + The following sample code represents a practical implementation of + the CRC (Cyclic Redundancy Check). (See also ISO 3309 and ITU-T V.42 + for a formal specification.) + + The sample code is in the ANSI C programming language. Non C users + may find it easier to read with these hints: + + & Bitwise AND operator. + ^ Bitwise exclusive-OR operator. + >> Bitwise right shift operator. When applied to an + unsigned quantity, as here, right shift inserts zero + bit(s) at the left. + ! Logical NOT operator. + ++ "n++" increments the variable n. + 0xNNN 0x introduces a hexadecimal (base 16) constant. + Suffix L indicates a long value (at least 32 bits). + + /* Table of CRCs of all 8-bit messages. */ + unsigned long crc_table[256]; + + /* Flag: has the table been computed? Initially false. */ + int crc_table_computed = 0; + + /* Make the table for a fast CRC. */ + void make_crc_table(void) + { + unsigned long c; + + + +Deutsch Informational [Page 11] + +RFC 1952 GZIP File Format Specification May 1996 + + + int n, k; + for (n = 0; n < 256; n++) { + c = (unsigned long) n; + for (k = 0; k < 8; k++) { + if (c & 1) { + c = 0xedb88320L ^ (c >> 1); + } else { + c = c >> 1; + } + } + crc_table[n] = c; + } + crc_table_computed = 1; + } + + /* + Update a running crc with the bytes buf[0..len-1] and return + the updated crc. The crc should be initialized to zero. Pre- and + post-conditioning (one's complement) is performed within this + function so it shouldn't be done by the caller. Usage example: + + unsigned long crc = 0L; + + while (read_buffer(buffer, length) != EOF) { + crc = update_crc(crc, buffer, length); + } + if (crc != original_crc) error(); + */ + unsigned long update_crc(unsigned long crc, + unsigned char *buf, int len) + { + unsigned long c = crc ^ 0xffffffffL; + int n; + + if (!crc_table_computed) + make_crc_table(); + for (n = 0; n < len; n++) { + c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c ^ 0xffffffffL; + } + + /* Return the CRC of the bytes buf[0..len-1]. */ + unsigned long crc(unsigned char *buf, int len) + { + return update_crc(0L, buf, len); + } + + + + +Deutsch Informational [Page 12] + diff --git a/zlib/zlib/doc/txtvsbin.txt b/zlib/zlib/doc/txtvsbin.txt new file mode 100644 index 00000000..3d0f0634 --- /dev/null +++ b/zlib/zlib/doc/txtvsbin.txt @@ -0,0 +1,107 @@ +A Fast Method for Identifying Plain Text Files +============================================== + + +Introduction +------------ + +Given a file coming from an unknown source, it is sometimes desirable +to find out whether the format of that file is plain text. Although +this may appear like a simple task, a fully accurate detection of the +file type requires heavy-duty semantic analysis on the file contents. +It is, however, possible to obtain satisfactory results by employing +various heuristics. + +Previous versions of PKZip and other zip-compatible compression tools +were using a crude detection scheme: if more than 80% (4/5) of the bytes +found in a certain buffer are within the range [7..127], the file is +labeled as plain text, otherwise it is labeled as binary. A prominent +limitation of this scheme is the restriction to Latin-based alphabets. +Other alphabets, like Greek, Cyrillic or Asian, make extensive use of +the bytes within the range [128..255], and texts using these alphabets +are most often misidentified by this scheme; in other words, the rate +of false negatives is sometimes too high, which means that the recall +is low. Another weakness of this scheme is a reduced precision, due to +the false positives that may occur when binary files containing large +amounts of textual characters are misidentified as plain text. + +In this article we propose a new, simple detection scheme that features +a much increased precision and a near-100% recall. This scheme is +designed to work on ASCII, Unicode and other ASCII-derived alphabets, +and it handles single-byte encodings (ISO-8859, MacRoman, KOI8, etc.) +and variable-sized encodings (ISO-2022, UTF-8, etc.). Wider encodings +(UCS-2/UTF-16 and UCS-4/UTF-32) are not handled, however. + + +The Algorithm +------------- + +The algorithm works by dividing the set of bytecodes [0..255] into three +categories: +- The white list of textual bytecodes: + 9 (TAB), 10 (LF), 13 (CR), 32 (SPACE) to 255. +- The gray list of tolerated bytecodes: + 7 (BEL), 8 (BS), 11 (VT), 12 (FF), 26 (SUB), 27 (ESC). +- The black list of undesired, non-textual bytecodes: + 0 (NUL) to 6, 14 to 31. + +If a file contains at least one byte that belongs to the white list and +no byte that belongs to the black list, then the file is categorized as +plain text; otherwise, it is categorized as binary. (The boundary case, +when the file is empty, automatically falls into the latter category.) + + +Rationale +--------- + +The idea behind this algorithm relies on two observations. + +The first observation is that, although the full range of 7-bit codes +[0..127] is properly specified by the ASCII standard, most control +characters in the range [0..31] are not used in practice. The only +widely-used, almost universally-portable control codes are 9 (TAB), +10 (LF) and 13 (CR). There are a few more control codes that are +recognized on a reduced range of platforms and text viewers/editors: +7 (BEL), 8 (BS), 11 (VT), 12 (FF), 26 (SUB) and 27 (ESC); but these +codes are rarely (if ever) used alone, without being accompanied by +some printable text. Even the newer, portable text formats such as +XML avoid using control characters outside the list mentioned here. + +The second observation is that most of the binary files tend to contain +control characters, especially 0 (NUL). Even though the older text +detection schemes observe the presence of non-ASCII codes from the range +[128..255], the precision rarely has to suffer if this upper range is +labeled as textual, because the files that are genuinely binary tend to +contain both control characters and codes from the upper range. On the +other hand, the upper range needs to be labeled as textual, because it +is used by virtually all ASCII extensions. In particular, this range is +used for encoding non-Latin scripts. + +Since there is no counting involved, other than simply observing the +presence or the absence of some byte values, the algorithm produces +consistent results, regardless what alphabet encoding is being used. +(If counting were involved, it could be possible to obtain different +results on a text encoded, say, using ISO-8859-16 versus UTF-8.) + +There is an extra category of plain text files that are "polluted" with +one or more black-listed codes, either by mistake or by peculiar design +considerations. In such cases, a scheme that tolerates a small fraction +of black-listed codes would provide an increased recall (i.e. more true +positives). This, however, incurs a reduced precision overall, since +false positives are more likely to appear in binary files that contain +large chunks of textual data. Furthermore, "polluted" plain text should +be regarded as binary by general-purpose text detection schemes, because +general-purpose text processing algorithms might not be applicable. +Under this premise, it is safe to say that our detection method provides +a near-100% recall. + +Experiments have been run on many files coming from various platforms +and applications. We tried plain text files, system logs, source code, +formatted office documents, compiled object code, etc. The results +confirm the optimistic assumptions about the capabilities of this +algorithm. + + +-- +Cosmin Truta +Last updated: 2006-May-28 diff --git a/zlib/zlib/examples/README.examples b/zlib/zlib/examples/README.examples new file mode 100644 index 00000000..56a31714 --- /dev/null +++ b/zlib/zlib/examples/README.examples @@ -0,0 +1,49 @@ +This directory contains examples of the use of zlib and other relevant +programs and documentation. + +enough.c + calculation and justification of ENOUGH parameter in inftrees.h + - calculates the maximum table space used in inflate tree + construction over all possible Huffman codes + +fitblk.c + compress just enough input to nearly fill a requested output size + - zlib isn't designed to do this, but fitblk does it anyway + +gun.c + uncompress a gzip file + - illustrates the use of inflateBack() for high speed file-to-file + decompression using call-back functions + - is approximately twice as fast as gzip -d + - also provides Unix uncompress functionality, again twice as fast + +gzappend.c + append to a gzip file + - illustrates the use of the Z_BLOCK flush parameter for inflate() + - illustrates the use of deflatePrime() to start at any bit + +gzjoin.c + join gzip files without recalculating the crc or recompressing + - illustrates the use of the Z_BLOCK flush parameter for inflate() + - illustrates the use of crc32_combine() + +gzlog.c +gzlog.h + efficiently and robustly maintain a message log file in gzip format + - illustrates use of raw deflate, Z_PARTIAL_FLUSH, deflatePrime(), + and deflateSetDictionary() + - illustrates use of a gzip header extra field + +zlib_how.html + painfully comprehensive description of zpipe.c (see below) + - describes in excruciating detail the use of deflate() and inflate() + +zpipe.c + reads and writes zlib streams from stdin to stdout + - illustrates the proper use of deflate() and inflate() + - deeply commented in zlib_how.html (see above) + +zran.c + index a zlib or gzip stream and randomly access it + - illustrates the use of Z_BLOCK, inflatePrime(), and + inflateSetDictionary() to provide random access diff --git a/zlib/zlib/examples/enough.c b/zlib/zlib/examples/enough.c new file mode 100644 index 00000000..b9911443 --- /dev/null +++ b/zlib/zlib/examples/enough.c @@ -0,0 +1,572 @@ +/* enough.c -- determine the maximum size of inflate's Huffman code tables over + * all possible valid and complete Huffman codes, subject to a length limit. + * Copyright (C) 2007, 2008, 2012 Mark Adler + * Version 1.4 18 August 2012 Mark Adler + */ + +/* Version history: + 1.0 3 Jan 2007 First version (derived from codecount.c version 1.4) + 1.1 4 Jan 2007 Use faster incremental table usage computation + Prune examine() search on previously visited states + 1.2 5 Jan 2007 Comments clean up + As inflate does, decrease root for short codes + Refuse cases where inflate would increase root + 1.3 17 Feb 2008 Add argument for initial root table size + Fix bug for initial root table size == max - 1 + Use a macro to compute the history index + 1.4 18 Aug 2012 Avoid shifts more than bits in type (caused endless loop!) + Clean up comparisons of different types + Clean up code indentation + */ + +/* + Examine all possible Huffman codes for a given number of symbols and a + maximum code length in bits to determine the maximum table size for zilb's + inflate. Only complete Huffman codes are counted. + + Two codes are considered distinct if the vectors of the number of codes per + length are not identical. So permutations of the symbol assignments result + in the same code for the counting, as do permutations of the assignments of + the bit values to the codes (i.e. only canonical codes are counted). + + We build a code from shorter to longer lengths, determining how many symbols + are coded at each length. At each step, we have how many symbols remain to + be coded, what the last code length used was, and how many bit patterns of + that length remain unused. Then we add one to the code length and double the + number of unused patterns to graduate to the next code length. We then + assign all portions of the remaining symbols to that code length that + preserve the properties of a correct and eventually complete code. Those + properties are: we cannot use more bit patterns than are available; and when + all the symbols are used, there are exactly zero possible bit patterns + remaining. + + The inflate Huffman decoding algorithm uses two-level lookup tables for + speed. There is a single first-level table to decode codes up to root bits + in length (root == 9 in the current inflate implementation). The table + has 1 << root entries and is indexed by the next root bits of input. Codes + shorter than root bits have replicated table entries, so that the correct + entry is pointed to regardless of the bits that follow the short code. If + the code is longer than root bits, then the table entry points to a second- + level table. The size of that table is determined by the longest code with + that root-bit prefix. If that longest code has length len, then the table + has size 1 << (len - root), to index the remaining bits in that set of + codes. Each subsequent root-bit prefix then has its own sub-table. The + total number of table entries required by the code is calculated + incrementally as the number of codes at each bit length is populated. When + all of the codes are shorter than root bits, then root is reduced to the + longest code length, resulting in a single, smaller, one-level table. + + The inflate algorithm also provides for small values of root (relative to + the log2 of the number of symbols), where the shortest code has more bits + than root. In that case, root is increased to the length of the shortest + code. This program, by design, does not handle that case, so it is verified + that the number of symbols is less than 2^(root + 1). + + In order to speed up the examination (by about ten orders of magnitude for + the default arguments), the intermediate states in the build-up of a code + are remembered and previously visited branches are pruned. The memory + required for this will increase rapidly with the total number of symbols and + the maximum code length in bits. However this is a very small price to pay + for the vast speedup. + + First, all of the possible Huffman codes are counted, and reachable + intermediate states are noted by a non-zero count in a saved-results array. + Second, the intermediate states that lead to (root + 1) bit or longer codes + are used to look at all sub-codes from those junctures for their inflate + memory usage. (The amount of memory used is not affected by the number of + codes of root bits or less in length.) Third, the visited states in the + construction of those sub-codes and the associated calculation of the table + size is recalled in order to avoid recalculating from the same juncture. + Beginning the code examination at (root + 1) bit codes, which is enabled by + identifying the reachable nodes, accounts for about six of the orders of + magnitude of improvement for the default arguments. About another four + orders of magnitude come from not revisiting previous states. Out of + approximately 2x10^16 possible Huffman codes, only about 2x10^6 sub-codes + need to be examined to cover all of the possible table memory usage cases + for the default arguments of 286 symbols limited to 15-bit codes. + + Note that an unsigned long long type is used for counting. It is quite easy + to exceed the capacity of an eight-byte integer with a large number of + symbols and a large maximum code length, so multiple-precision arithmetic + would need to replace the unsigned long long arithmetic in that case. This + program will abort if an overflow occurs. The big_t type identifies where + the counting takes place. + + An unsigned long long type is also used for calculating the number of + possible codes remaining at the maximum length. This limits the maximum + code length to the number of bits in a long long minus the number of bits + needed to represent the symbols in a flat code. The code_t type identifies + where the bit pattern counting takes place. + */ + +#include +#include +#include +#include + +#define local static + +/* special data types */ +typedef unsigned long long big_t; /* type for code counting */ +typedef unsigned long long code_t; /* type for bit pattern counting */ +struct tab { /* type for been here check */ + size_t len; /* length of bit vector in char's */ + char *vec; /* allocated bit vector */ +}; + +/* The array for saving results, num[], is indexed with this triplet: + + syms: number of symbols remaining to code + left: number of available bit patterns at length len + len: number of bits in the codes currently being assigned + + Those indices are constrained thusly when saving results: + + syms: 3..totsym (totsym == total symbols to code) + left: 2..syms - 1, but only the evens (so syms == 8 -> 2, 4, 6) + len: 1..max - 1 (max == maximum code length in bits) + + syms == 2 is not saved since that immediately leads to a single code. left + must be even, since it represents the number of available bit patterns at + the current length, which is double the number at the previous length. + left ends at syms-1 since left == syms immediately results in a single code. + (left > sym is not allowed since that would result in an incomplete code.) + len is less than max, since the code completes immediately when len == max. + + The offset into the array is calculated for the three indices with the + first one (syms) being outermost, and the last one (len) being innermost. + We build the array with length max-1 lists for the len index, with syms-3 + of those for each symbol. There are totsym-2 of those, with each one + varying in length as a function of sym. See the calculation of index in + count() for the index, and the calculation of size in main() for the size + of the array. + + For the deflate example of 286 symbols limited to 15-bit codes, the array + has 284,284 entries, taking up 2.17 MB for an 8-byte big_t. More than + half of the space allocated for saved results is actually used -- not all + possible triplets are reached in the generation of valid Huffman codes. + */ + +/* The array for tracking visited states, done[], is itself indexed identically + to the num[] array as described above for the (syms, left, len) triplet. + Each element in the array is further indexed by the (mem, rem) doublet, + where mem is the amount of inflate table space used so far, and rem is the + remaining unused entries in the current inflate sub-table. Each indexed + element is simply one bit indicating whether the state has been visited or + not. Since the ranges for mem and rem are not known a priori, each bit + vector is of a variable size, and grows as needed to accommodate the visited + states. mem and rem are used to calculate a single index in a triangular + array. Since the range of mem is expected in the default case to be about + ten times larger than the range of rem, the array is skewed to reduce the + memory usage, with eight times the range for mem than for rem. See the + calculations for offset and bit in beenhere() for the details. + + For the deflate example of 286 symbols limited to 15-bit codes, the bit + vectors grow to total approximately 21 MB, in addition to the 4.3 MB done[] + array itself. + */ + +/* Globals to avoid propagating constants or constant pointers recursively */ +local int max; /* maximum allowed bit length for the codes */ +local int root; /* size of base code table in bits */ +local int large; /* largest code table so far */ +local size_t size; /* number of elements in num and done */ +local int *code; /* number of symbols assigned to each bit length */ +local big_t *num; /* saved results array for code counting */ +local struct tab *done; /* states already evaluated array */ + +/* Index function for num[] and done[] */ +#define INDEX(i,j,k) (((size_t)((i-1)>>1)*((i-2)>>1)+(j>>1)-1)*(max-1)+k-1) + +/* Free allocated space. Uses globals code, num, and done. */ +local void cleanup(void) +{ + size_t n; + + if (done != NULL) { + for (n = 0; n < size; n++) + if (done[n].len) + free(done[n].vec); + free(done); + } + if (num != NULL) + free(num); + if (code != NULL) + free(code); +} + +/* Return the number of possible Huffman codes using bit patterns of lengths + len through max inclusive, coding syms symbols, with left bit patterns of + length len unused -- return -1 if there is an overflow in the counting. + Keep a record of previous results in num to prevent repeating the same + calculation. Uses the globals max and num. */ +local big_t count(int syms, int len, int left) +{ + big_t sum; /* number of possible codes from this juncture */ + big_t got; /* value returned from count() */ + int least; /* least number of syms to use at this juncture */ + int most; /* most number of syms to use at this juncture */ + int use; /* number of bit patterns to use in next call */ + size_t index; /* index of this case in *num */ + + /* see if only one possible code */ + if (syms == left) + return 1; + + /* note and verify the expected state */ + assert(syms > left && left > 0 && len < max); + + /* see if we've done this one already */ + index = INDEX(syms, left, len); + got = num[index]; + if (got) + return got; /* we have -- return the saved result */ + + /* we need to use at least this many bit patterns so that the code won't be + incomplete at the next length (more bit patterns than symbols) */ + least = (left << 1) - syms; + if (least < 0) + least = 0; + + /* we can use at most this many bit patterns, lest there not be enough + available for the remaining symbols at the maximum length (if there were + no limit to the code length, this would become: most = left - 1) */ + most = (((code_t)left << (max - len)) - syms) / + (((code_t)1 << (max - len)) - 1); + + /* count all possible codes from this juncture and add them up */ + sum = 0; + for (use = least; use <= most; use++) { + got = count(syms - use, len + 1, (left - use) << 1); + sum += got; + if (got == (big_t)0 - 1 || sum < got) /* overflow */ + return (big_t)0 - 1; + } + + /* verify that all recursive calls are productive */ + assert(sum != 0); + + /* save the result and return it */ + num[index] = sum; + return sum; +} + +/* Return true if we've been here before, set to true if not. Set a bit in a + bit vector to indicate visiting this state. Each (syms,len,left) state + has a variable size bit vector indexed by (mem,rem). The bit vector is + lengthened if needed to allow setting the (mem,rem) bit. */ +local int beenhere(int syms, int len, int left, int mem, int rem) +{ + size_t index; /* index for this state's bit vector */ + size_t offset; /* offset in this state's bit vector */ + int bit; /* mask for this state's bit */ + size_t length; /* length of the bit vector in bytes */ + char *vector; /* new or enlarged bit vector */ + + /* point to vector for (syms,left,len), bit in vector for (mem,rem) */ + index = INDEX(syms, left, len); + mem -= 1 << root; + offset = (mem >> 3) + rem; + offset = ((offset * (offset + 1)) >> 1) + rem; + bit = 1 << (mem & 7); + + /* see if we've been here */ + length = done[index].len; + if (offset < length && (done[index].vec[offset] & bit) != 0) + return 1; /* done this! */ + + /* we haven't been here before -- set the bit to show we have now */ + + /* see if we need to lengthen the vector in order to set the bit */ + if (length <= offset) { + /* if we have one already, enlarge it, zero out the appended space */ + if (length) { + do { + length <<= 1; + } while (length <= offset); + vector = realloc(done[index].vec, length); + if (vector != NULL) + memset(vector + done[index].len, 0, length - done[index].len); + } + + /* otherwise we need to make a new vector and zero it out */ + else { + length = 1 << (len - root); + while (length <= offset) + length <<= 1; + vector = calloc(length, sizeof(char)); + } + + /* in either case, bail if we can't get the memory */ + if (vector == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + exit(1); + } + + /* install the new vector */ + done[index].len = length; + done[index].vec = vector; + } + + /* set the bit */ + done[index].vec[offset] |= bit; + return 0; +} + +/* Examine all possible codes from the given node (syms, len, left). Compute + the amount of memory required to build inflate's decoding tables, where the + number of code structures used so far is mem, and the number remaining in + the current sub-table is rem. Uses the globals max, code, root, large, and + done. */ +local void examine(int syms, int len, int left, int mem, int rem) +{ + int least; /* least number of syms to use at this juncture */ + int most; /* most number of syms to use at this juncture */ + int use; /* number of bit patterns to use in next call */ + + /* see if we have a complete code */ + if (syms == left) { + /* set the last code entry */ + code[len] = left; + + /* complete computation of memory used by this code */ + while (rem < left) { + left -= rem; + rem = 1 << (len - root); + mem += rem; + } + assert(rem == left); + + /* if this is a new maximum, show the entries used and the sub-code */ + if (mem > large) { + large = mem; + printf("max %d: ", mem); + for (use = root + 1; use <= max; use++) + if (code[use]) + printf("%d[%d] ", code[use], use); + putchar('\n'); + fflush(stdout); + } + + /* remove entries as we drop back down in the recursion */ + code[len] = 0; + return; + } + + /* prune the tree if we can */ + if (beenhere(syms, len, left, mem, rem)) + return; + + /* we need to use at least this many bit patterns so that the code won't be + incomplete at the next length (more bit patterns than symbols) */ + least = (left << 1) - syms; + if (least < 0) + least = 0; + + /* we can use at most this many bit patterns, lest there not be enough + available for the remaining symbols at the maximum length (if there were + no limit to the code length, this would become: most = left - 1) */ + most = (((code_t)left << (max - len)) - syms) / + (((code_t)1 << (max - len)) - 1); + + /* occupy least table spaces, creating new sub-tables as needed */ + use = least; + while (rem < use) { + use -= rem; + rem = 1 << (len - root); + mem += rem; + } + rem -= use; + + /* examine codes from here, updating table space as we go */ + for (use = least; use <= most; use++) { + code[len] = use; + examine(syms - use, len + 1, (left - use) << 1, + mem + (rem ? 1 << (len - root) : 0), rem << 1); + if (rem == 0) { + rem = 1 << (len - root); + mem += rem; + } + rem--; + } + + /* remove entries as we drop back down in the recursion */ + code[len] = 0; +} + +/* Look at all sub-codes starting with root + 1 bits. Look at only the valid + intermediate code states (syms, left, len). For each completed code, + calculate the amount of memory required by inflate to build the decoding + tables. Find the maximum amount of memory required and show the code that + requires that maximum. Uses the globals max, root, and num. */ +local void enough(int syms) +{ + int n; /* number of remaing symbols for this node */ + int left; /* number of unused bit patterns at this length */ + size_t index; /* index of this case in *num */ + + /* clear code */ + for (n = 0; n <= max; n++) + code[n] = 0; + + /* look at all (root + 1) bit and longer codes */ + large = 1 << root; /* base table */ + if (root < max) /* otherwise, there's only a base table */ + for (n = 3; n <= syms; n++) + for (left = 2; left < n; left += 2) + { + /* look at all reachable (root + 1) bit nodes, and the + resulting codes (complete at root + 2 or more) */ + index = INDEX(n, left, root + 1); + if (root + 1 < max && num[index]) /* reachable node */ + examine(n, root + 1, left, 1 << root, 0); + + /* also look at root bit codes with completions at root + 1 + bits (not saved in num, since complete), just in case */ + if (num[index - 1] && n <= left << 1) + examine((n - left) << 1, root + 1, (n - left) << 1, + 1 << root, 0); + } + + /* done */ + printf("done: maximum of %d table entries\n", large); +} + +/* + Examine and show the total number of possible Huffman codes for a given + maximum number of symbols, initial root table size, and maximum code length + in bits -- those are the command arguments in that order. The default + values are 286, 9, and 15 respectively, for the deflate literal/length code. + The possible codes are counted for each number of coded symbols from two to + the maximum. The counts for each of those and the total number of codes are + shown. The maximum number of inflate table entires is then calculated + across all possible codes. Each new maximum number of table entries and the + associated sub-code (starting at root + 1 == 10 bits) is shown. + + To count and examine Huffman codes that are not length-limited, provide a + maximum length equal to the number of symbols minus one. + + For the deflate literal/length code, use "enough". For the deflate distance + code, use "enough 30 6". + + This uses the %llu printf format to print big_t numbers, which assumes that + big_t is an unsigned long long. If the big_t type is changed (for example + to a multiple precision type), the method of printing will also need to be + updated. + */ +int main(int argc, char **argv) +{ + int syms; /* total number of symbols to code */ + int n; /* number of symbols to code for this run */ + big_t got; /* return value of count() */ + big_t sum; /* accumulated number of codes over n */ + code_t word; /* for counting bits in code_t */ + + /* set up globals for cleanup() */ + code = NULL; + num = NULL; + done = NULL; + + /* get arguments -- default to the deflate literal/length code */ + syms = 286; + root = 9; + max = 15; + if (argc > 1) { + syms = atoi(argv[1]); + if (argc > 2) { + root = atoi(argv[2]); + if (argc > 3) + max = atoi(argv[3]); + } + } + if (argc > 4 || syms < 2 || root < 1 || max < 1) { + fputs("invalid arguments, need: [sym >= 2 [root >= 1 [max >= 1]]]\n", + stderr); + return 1; + } + + /* if not restricting the code length, the longest is syms - 1 */ + if (max > syms - 1) + max = syms - 1; + + /* determine the number of bits in a code_t */ + for (n = 0, word = 1; word; n++, word <<= 1) + ; + + /* make sure that the calculation of most will not overflow */ + if (max > n || (code_t)(syms - 2) >= (((code_t)0 - 1) >> (max - 1))) { + fputs("abort: code length too long for internal types\n", stderr); + return 1; + } + + /* reject impossible code requests */ + if ((code_t)(syms - 1) > ((code_t)1 << max) - 1) { + fprintf(stderr, "%d symbols cannot be coded in %d bits\n", + syms, max); + return 1; + } + + /* allocate code vector */ + code = calloc(max + 1, sizeof(int)); + if (code == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + return 1; + } + + /* determine size of saved results array, checking for overflows, + allocate and clear the array (set all to zero with calloc()) */ + if (syms == 2) /* iff max == 1 */ + num = NULL; /* won't be saving any results */ + else { + size = syms >> 1; + if (size > ((size_t)0 - 1) / (n = (syms - 1) >> 1) || + (size *= n, size > ((size_t)0 - 1) / (n = max - 1)) || + (size *= n, size > ((size_t)0 - 1) / sizeof(big_t)) || + (num = calloc(size, sizeof(big_t))) == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + return 1; + } + } + + /* count possible codes for all numbers of symbols, add up counts */ + sum = 0; + for (n = 2; n <= syms; n++) { + got = count(n, 1, 2); + sum += got; + if (got == (big_t)0 - 1 || sum < got) { /* overflow */ + fputs("abort: can't count that high!\n", stderr); + cleanup(); + return 1; + } + printf("%llu %d-codes\n", got, n); + } + printf("%llu total codes for 2 to %d symbols", sum, syms); + if (max < syms - 1) + printf(" (%d-bit length limit)\n", max); + else + puts(" (no length limit)"); + + /* allocate and clear done array for beenhere() */ + if (syms == 2) + done = NULL; + else if (size > ((size_t)0 - 1) / sizeof(struct tab) || + (done = calloc(size, sizeof(struct tab))) == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + return 1; + } + + /* find and show maximum inflate table usage */ + if (root > max) /* reduce root to max length */ + root = max; + if ((code_t)syms < ((code_t)1 << (root + 1))) + enough(syms); + else + puts("cannot handle minimum code lengths > root"); + + /* done */ + cleanup(); + return 0; +} diff --git a/zlib/zlib/examples/fitblk.c b/zlib/zlib/examples/fitblk.c new file mode 100644 index 00000000..c61de5c9 --- /dev/null +++ b/zlib/zlib/examples/fitblk.c @@ -0,0 +1,233 @@ +/* fitblk.c: example of fitting compressed output to a specified size + Not copyrighted -- provided to the public domain + Version 1.1 25 November 2004 Mark Adler */ + +/* Version history: + 1.0 24 Nov 2004 First version + 1.1 25 Nov 2004 Change deflateInit2() to deflateInit() + Use fixed-size, stack-allocated raw buffers + Simplify code moving compression to subroutines + Use assert() for internal errors + Add detailed description of approach + */ + +/* Approach to just fitting a requested compressed size: + + fitblk performs three compression passes on a portion of the input + data in order to determine how much of that input will compress to + nearly the requested output block size. The first pass generates + enough deflate blocks to produce output to fill the requested + output size plus a specfied excess amount (see the EXCESS define + below). The last deflate block may go quite a bit past that, but + is discarded. The second pass decompresses and recompresses just + the compressed data that fit in the requested plus excess sized + buffer. The deflate process is terminated after that amount of + input, which is less than the amount consumed on the first pass. + The last deflate block of the result will be of a comparable size + to the final product, so that the header for that deflate block and + the compression ratio for that block will be about the same as in + the final product. The third compression pass decompresses the + result of the second step, but only the compressed data up to the + requested size minus an amount to allow the compressed stream to + complete (see the MARGIN define below). That will result in a + final compressed stream whose length is less than or equal to the + requested size. Assuming sufficient input and a requested size + greater than a few hundred bytes, the shortfall will typically be + less than ten bytes. + + If the input is short enough that the first compression completes + before filling the requested output size, then that compressed + stream is return with no recompression. + + EXCESS is chosen to be just greater than the shortfall seen in a + two pass approach similar to the above. That shortfall is due to + the last deflate block compressing more efficiently with a smaller + header on the second pass. EXCESS is set to be large enough so + that there is enough uncompressed data for the second pass to fill + out the requested size, and small enough so that the final deflate + block of the second pass will be close in size to the final deflate + block of the third and final pass. MARGIN is chosen to be just + large enough to assure that the final compression has enough room + to complete in all cases. + */ + +#include +#include +#include +#include "zlib.h" + +#define local static + +/* print nastygram and leave */ +local void quit(char *why) +{ + fprintf(stderr, "fitblk abort: %s\n", why); + exit(1); +} + +#define RAWLEN 4096 /* intermediate uncompressed buffer size */ + +/* compress from file to def until provided buffer is full or end of + input reached; return last deflate() return value, or Z_ERRNO if + there was read error on the file */ +local int partcompress(FILE *in, z_streamp def) +{ + int ret, flush; + unsigned char raw[RAWLEN]; + + flush = Z_NO_FLUSH; + do { + def->avail_in = fread(raw, 1, RAWLEN, in); + if (ferror(in)) + return Z_ERRNO; + def->next_in = raw; + if (feof(in)) + flush = Z_FINISH; + ret = deflate(def, flush); + assert(ret != Z_STREAM_ERROR); + } while (def->avail_out != 0 && flush == Z_NO_FLUSH); + return ret; +} + +/* recompress from inf's input to def's output; the input for inf and + the output for def are set in those structures before calling; + return last deflate() return value, or Z_MEM_ERROR if inflate() + was not able to allocate enough memory when it needed to */ +local int recompress(z_streamp inf, z_streamp def) +{ + int ret, flush; + unsigned char raw[RAWLEN]; + + flush = Z_NO_FLUSH; + do { + /* decompress */ + inf->avail_out = RAWLEN; + inf->next_out = raw; + ret = inflate(inf, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR && ret != Z_DATA_ERROR && + ret != Z_NEED_DICT); + if (ret == Z_MEM_ERROR) + return ret; + + /* compress what was decompresed until done or no room */ + def->avail_in = RAWLEN - inf->avail_out; + def->next_in = raw; + if (inf->avail_out != 0) + flush = Z_FINISH; + ret = deflate(def, flush); + assert(ret != Z_STREAM_ERROR); + } while (ret != Z_STREAM_END && def->avail_out != 0); + return ret; +} + +#define EXCESS 256 /* empirically determined stream overage */ +#define MARGIN 8 /* amount to back off for completion */ + +/* compress from stdin to fixed-size block on stdout */ +int main(int argc, char **argv) +{ + int ret; /* return code */ + unsigned size; /* requested fixed output block size */ + unsigned have; /* bytes written by deflate() call */ + unsigned char *blk; /* intermediate and final stream */ + unsigned char *tmp; /* close to desired size stream */ + z_stream def, inf; /* zlib deflate and inflate states */ + + /* get requested output size */ + if (argc != 2) + quit("need one argument: size of output block"); + ret = strtol(argv[1], argv + 1, 10); + if (argv[1][0] != 0) + quit("argument must be a number"); + if (ret < 8) /* 8 is minimum zlib stream size */ + quit("need positive size of 8 or greater"); + size = (unsigned)ret; + + /* allocate memory for buffers and compression engine */ + blk = malloc(size + EXCESS); + def.zalloc = Z_NULL; + def.zfree = Z_NULL; + def.opaque = Z_NULL; + ret = deflateInit(&def, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK || blk == NULL) + quit("out of memory"); + + /* compress from stdin until output full, or no more input */ + def.avail_out = size + EXCESS; + def.next_out = blk; + ret = partcompress(stdin, &def); + if (ret == Z_ERRNO) + quit("error reading input"); + + /* if it all fit, then size was undersubscribed -- done! */ + if (ret == Z_STREAM_END && def.avail_out >= EXCESS) { + /* write block to stdout */ + have = size + EXCESS - def.avail_out; + if (fwrite(blk, 1, have, stdout) != have || ferror(stdout)) + quit("error writing output"); + + /* clean up and print results to stderr */ + ret = deflateEnd(&def); + assert(ret != Z_STREAM_ERROR); + free(blk); + fprintf(stderr, + "%u bytes unused out of %u requested (all input)\n", + size - have, size); + return 0; + } + + /* it didn't all fit -- set up for recompression */ + inf.zalloc = Z_NULL; + inf.zfree = Z_NULL; + inf.opaque = Z_NULL; + inf.avail_in = 0; + inf.next_in = Z_NULL; + ret = inflateInit(&inf); + tmp = malloc(size + EXCESS); + if (ret != Z_OK || tmp == NULL) + quit("out of memory"); + ret = deflateReset(&def); + assert(ret != Z_STREAM_ERROR); + + /* do first recompression close to the right amount */ + inf.avail_in = size + EXCESS; + inf.next_in = blk; + def.avail_out = size + EXCESS; + def.next_out = tmp; + ret = recompress(&inf, &def); + if (ret == Z_MEM_ERROR) + quit("out of memory"); + + /* set up for next reocmpression */ + ret = inflateReset(&inf); + assert(ret != Z_STREAM_ERROR); + ret = deflateReset(&def); + assert(ret != Z_STREAM_ERROR); + + /* do second and final recompression (third compression) */ + inf.avail_in = size - MARGIN; /* assure stream will complete */ + inf.next_in = tmp; + def.avail_out = size; + def.next_out = blk; + ret = recompress(&inf, &def); + if (ret == Z_MEM_ERROR) + quit("out of memory"); + assert(ret == Z_STREAM_END); /* otherwise MARGIN too small */ + + /* done -- write block to stdout */ + have = size - def.avail_out; + if (fwrite(blk, 1, have, stdout) != have || ferror(stdout)) + quit("error writing output"); + + /* clean up and print results to stderr */ + free(tmp); + ret = inflateEnd(&inf); + assert(ret != Z_STREAM_ERROR); + ret = deflateEnd(&def); + assert(ret != Z_STREAM_ERROR); + free(blk); + fprintf(stderr, + "%u bytes unused out of %u requested (%lu input)\n", + size - have, size, def.total_in); + return 0; +} diff --git a/zlib/zlib/examples/gun.c b/zlib/zlib/examples/gun.c new file mode 100644 index 00000000..89e484fe --- /dev/null +++ b/zlib/zlib/examples/gun.c @@ -0,0 +1,702 @@ +/* gun.c -- simple gunzip to give an example of the use of inflateBack() + * Copyright (C) 2003, 2005, 2008, 2010, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + Version 1.7 12 August 2012 Mark Adler */ + +/* Version history: + 1.0 16 Feb 2003 First version for testing of inflateBack() + 1.1 21 Feb 2005 Decompress concatenated gzip streams + Remove use of "this" variable (C++ keyword) + Fix return value for in() + Improve allocation failure checking + Add typecasting for void * structures + Add -h option for command version and usage + Add a bunch of comments + 1.2 20 Mar 2005 Add Unix compress (LZW) decompression + Copy file attributes from input file to output file + 1.3 12 Jun 2005 Add casts for error messages [Oberhumer] + 1.4 8 Dec 2006 LZW decompression speed improvements + 1.5 9 Feb 2008 Avoid warning in latest version of gcc + 1.6 17 Jan 2010 Avoid signed/unsigned comparison warnings + 1.7 12 Aug 2012 Update for z_const usage in zlib 1.2.8 + */ + +/* + gun [ -t ] [ name ... ] + + decompresses the data in the named gzip files. If no arguments are given, + gun will decompress from stdin to stdout. The names must end in .gz, -gz, + .z, -z, _z, or .Z. The uncompressed data will be written to a file name + with the suffix stripped. On success, the original file is deleted. On + failure, the output file is deleted. For most failures, the command will + continue to process the remaining names on the command line. A memory + allocation failure will abort the command. If -t is specified, then the + listed files or stdin will be tested as gzip files for integrity (without + checking for a proper suffix), no output will be written, and no files + will be deleted. + + Like gzip, gun allows concatenated gzip streams and will decompress them, + writing all of the uncompressed data to the output. Unlike gzip, gun allows + an empty file on input, and will produce no error writing an empty output + file. + + gun will also decompress files made by Unix compress, which uses LZW + compression. These files are automatically detected by virtue of their + magic header bytes. Since the end of Unix compress stream is marked by the + end-of-file, they cannot be concantenated. If a Unix compress stream is + encountered in an input file, it is the last stream in that file. + + Like gunzip and uncompress, the file attributes of the orignal compressed + file are maintained in the final uncompressed file, to the extent that the + user permissions allow it. + + On my Mac OS X PowerPC G4, gun is almost twice as fast as gunzip (version + 1.2.4) is on the same file, when gun is linked with zlib 1.2.2. Also the + LZW decompression provided by gun is about twice as fast as the standard + Unix uncompress command. + */ + +/* external functions and related types and constants */ +#include /* fprintf() */ +#include /* malloc(), free() */ +#include /* strerror(), strcmp(), strlen(), memcpy() */ +#include /* errno */ +#include /* open() */ +#include /* read(), write(), close(), chown(), unlink() */ +#include +#include /* stat(), chmod() */ +#include /* utime() */ +#include "zlib.h" /* inflateBackInit(), inflateBack(), */ + /* inflateBackEnd(), crc32() */ + +/* function declaration */ +#define local static + +/* buffer constants */ +#define SIZE 32768U /* input and output buffer sizes */ +#define PIECE 16384 /* limits i/o chunks for 16-bit int case */ + +/* structure for infback() to pass to input function in() -- it maintains the + input file and a buffer of size SIZE */ +struct ind { + int infile; + unsigned char *inbuf; +}; + +/* Load input buffer, assumed to be empty, and return bytes loaded and a + pointer to them. read() is called until the buffer is full, or until it + returns end-of-file or error. Return 0 on error. */ +local unsigned in(void *in_desc, z_const unsigned char **buf) +{ + int ret; + unsigned len; + unsigned char *next; + struct ind *me = (struct ind *)in_desc; + + next = me->inbuf; + *buf = next; + len = 0; + do { + ret = PIECE; + if ((unsigned)ret > SIZE - len) + ret = (int)(SIZE - len); + ret = (int)read(me->infile, next, ret); + if (ret == -1) { + len = 0; + break; + } + next += ret; + len += ret; + } while (ret != 0 && len < SIZE); + return len; +} + +/* structure for infback() to pass to output function out() -- it maintains the + output file, a running CRC-32 check on the output and the total number of + bytes output, both for checking against the gzip trailer. (The length in + the gzip trailer is stored modulo 2^32, so it's ok if a long is 32 bits and + the output is greater than 4 GB.) */ +struct outd { + int outfile; + int check; /* true if checking crc and total */ + unsigned long crc; + unsigned long total; +}; + +/* Write output buffer and update the CRC-32 and total bytes written. write() + is called until all of the output is written or an error is encountered. + On success out() returns 0. For a write failure, out() returns 1. If the + output file descriptor is -1, then nothing is written. + */ +local int out(void *out_desc, unsigned char *buf, unsigned len) +{ + int ret; + struct outd *me = (struct outd *)out_desc; + + if (me->check) { + me->crc = crc32(me->crc, buf, len); + me->total += len; + } + if (me->outfile != -1) + do { + ret = PIECE; + if ((unsigned)ret > len) + ret = (int)len; + ret = (int)write(me->outfile, buf, ret); + if (ret == -1) + return 1; + buf += ret; + len -= ret; + } while (len != 0); + return 0; +} + +/* next input byte macro for use inside lunpipe() and gunpipe() */ +#define NEXT() (have ? 0 : (have = in(indp, &next)), \ + last = have ? (have--, (int)(*next++)) : -1) + +/* memory for gunpipe() and lunpipe() -- + the first 256 entries of prefix[] and suffix[] are never used, could + have offset the index, but it's faster to waste the memory */ +unsigned char inbuf[SIZE]; /* input buffer */ +unsigned char outbuf[SIZE]; /* output buffer */ +unsigned short prefix[65536]; /* index to LZW prefix string */ +unsigned char suffix[65536]; /* one-character LZW suffix */ +unsigned char match[65280 + 2]; /* buffer for reversed match or gzip + 32K sliding window */ + +/* throw out what's left in the current bits byte buffer (this is a vestigial + aspect of the compressed data format derived from an implementation that + made use of a special VAX machine instruction!) */ +#define FLUSHCODE() \ + do { \ + left = 0; \ + rem = 0; \ + if (chunk > have) { \ + chunk -= have; \ + have = 0; \ + if (NEXT() == -1) \ + break; \ + chunk--; \ + if (chunk > have) { \ + chunk = have = 0; \ + break; \ + } \ + } \ + have -= chunk; \ + next += chunk; \ + chunk = 0; \ + } while (0) + +/* Decompress a compress (LZW) file from indp to outfile. The compress magic + header (two bytes) has already been read and verified. There are have bytes + of buffered input at next. strm is used for passing error information back + to gunpipe(). + + lunpipe() will return Z_OK on success, Z_BUF_ERROR for an unexpected end of + file, read error, or write error (a write error indicated by strm->next_in + not equal to Z_NULL), or Z_DATA_ERROR for invalid input. + */ +local int lunpipe(unsigned have, z_const unsigned char *next, struct ind *indp, + int outfile, z_stream *strm) +{ + int last; /* last byte read by NEXT(), or -1 if EOF */ + unsigned chunk; /* bytes left in current chunk */ + int left; /* bits left in rem */ + unsigned rem; /* unused bits from input */ + int bits; /* current bits per code */ + unsigned code; /* code, table traversal index */ + unsigned mask; /* mask for current bits codes */ + int max; /* maximum bits per code for this stream */ + unsigned flags; /* compress flags, then block compress flag */ + unsigned end; /* last valid entry in prefix/suffix tables */ + unsigned temp; /* current code */ + unsigned prev; /* previous code */ + unsigned final; /* last character written for previous code */ + unsigned stack; /* next position for reversed string */ + unsigned outcnt; /* bytes in output buffer */ + struct outd outd; /* output structure */ + unsigned char *p; + + /* set up output */ + outd.outfile = outfile; + outd.check = 0; + + /* process remainder of compress header -- a flags byte */ + flags = NEXT(); + if (last == -1) + return Z_BUF_ERROR; + if (flags & 0x60) { + strm->msg = (char *)"unknown lzw flags set"; + return Z_DATA_ERROR; + } + max = flags & 0x1f; + if (max < 9 || max > 16) { + strm->msg = (char *)"lzw bits out of range"; + return Z_DATA_ERROR; + } + if (max == 9) /* 9 doesn't really mean 9 */ + max = 10; + flags &= 0x80; /* true if block compress */ + + /* clear table */ + bits = 9; + mask = 0x1ff; + end = flags ? 256 : 255; + + /* set up: get first 9-bit code, which is the first decompressed byte, but + don't create a table entry until the next code */ + if (NEXT() == -1) /* no compressed data is ok */ + return Z_OK; + final = prev = (unsigned)last; /* low 8 bits of code */ + if (NEXT() == -1) /* missing a bit */ + return Z_BUF_ERROR; + if (last & 1) { /* code must be < 256 */ + strm->msg = (char *)"invalid lzw code"; + return Z_DATA_ERROR; + } + rem = (unsigned)last >> 1; /* remaining 7 bits */ + left = 7; + chunk = bits - 2; /* 7 bytes left in this chunk */ + outbuf[0] = (unsigned char)final; /* write first decompressed byte */ + outcnt = 1; + + /* decode codes */ + stack = 0; + for (;;) { + /* if the table will be full after this, increment the code size */ + if (end >= mask && bits < max) { + FLUSHCODE(); + bits++; + mask <<= 1; + mask++; + } + + /* get a code of length bits */ + if (chunk == 0) /* decrement chunk modulo bits */ + chunk = bits; + code = rem; /* low bits of code */ + if (NEXT() == -1) { /* EOF is end of compressed data */ + /* write remaining buffered output */ + if (outcnt && out(&outd, outbuf, outcnt)) { + strm->next_in = outbuf; /* signal write error */ + return Z_BUF_ERROR; + } + return Z_OK; + } + code += (unsigned)last << left; /* middle (or high) bits of code */ + left += 8; + chunk--; + if (bits > left) { /* need more bits */ + if (NEXT() == -1) /* can't end in middle of code */ + return Z_BUF_ERROR; + code += (unsigned)last << left; /* high bits of code */ + left += 8; + chunk--; + } + code &= mask; /* mask to current code length */ + left -= bits; /* number of unused bits */ + rem = (unsigned)last >> (8 - left); /* unused bits from last byte */ + + /* process clear code (256) */ + if (code == 256 && flags) { + FLUSHCODE(); + bits = 9; /* initialize bits and mask */ + mask = 0x1ff; + end = 255; /* empty table */ + continue; /* get next code */ + } + + /* special code to reuse last match */ + temp = code; /* save the current code */ + if (code > end) { + /* Be picky on the allowed code here, and make sure that the code + we drop through (prev) will be a valid index so that random + input does not cause an exception. The code != end + 1 check is + empirically derived, and not checked in the original uncompress + code. If this ever causes a problem, that check could be safely + removed. Leaving this check in greatly improves gun's ability + to detect random or corrupted input after a compress header. + In any case, the prev > end check must be retained. */ + if (code != end + 1 || prev > end) { + strm->msg = (char *)"invalid lzw code"; + return Z_DATA_ERROR; + } + match[stack++] = (unsigned char)final; + code = prev; + } + + /* walk through linked list to generate output in reverse order */ + p = match + stack; + while (code >= 256) { + *p++ = suffix[code]; + code = prefix[code]; + } + stack = p - match; + match[stack++] = (unsigned char)code; + final = code; + + /* link new table entry */ + if (end < mask) { + end++; + prefix[end] = (unsigned short)prev; + suffix[end] = (unsigned char)final; + } + + /* set previous code for next iteration */ + prev = temp; + + /* write output in forward order */ + while (stack > SIZE - outcnt) { + while (outcnt < SIZE) + outbuf[outcnt++] = match[--stack]; + if (out(&outd, outbuf, outcnt)) { + strm->next_in = outbuf; /* signal write error */ + return Z_BUF_ERROR; + } + outcnt = 0; + } + p = match + stack; + do { + outbuf[outcnt++] = *--p; + } while (p > match); + stack = 0; + + /* loop for next code with final and prev as the last match, rem and + left provide the first 0..7 bits of the next code, end is the last + valid table entry */ + } +} + +/* Decompress a gzip file from infile to outfile. strm is assumed to have been + successfully initialized with inflateBackInit(). The input file may consist + of a series of gzip streams, in which case all of them will be decompressed + to the output file. If outfile is -1, then the gzip stream(s) integrity is + checked and nothing is written. + + The return value is a zlib error code: Z_MEM_ERROR if out of memory, + Z_DATA_ERROR if the header or the compressed data is invalid, or if the + trailer CRC-32 check or length doesn't match, Z_BUF_ERROR if the input ends + prematurely or a write error occurs, or Z_ERRNO if junk (not a another gzip + stream) follows a valid gzip stream. + */ +local int gunpipe(z_stream *strm, int infile, int outfile) +{ + int ret, first, last; + unsigned have, flags, len; + z_const unsigned char *next = NULL; + struct ind ind, *indp; + struct outd outd; + + /* setup input buffer */ + ind.infile = infile; + ind.inbuf = inbuf; + indp = &ind; + + /* decompress concatenated gzip streams */ + have = 0; /* no input data read in yet */ + first = 1; /* looking for first gzip header */ + strm->next_in = Z_NULL; /* so Z_BUF_ERROR means EOF */ + for (;;) { + /* look for the two magic header bytes for a gzip stream */ + if (NEXT() == -1) { + ret = Z_OK; + break; /* empty gzip stream is ok */ + } + if (last != 31 || (NEXT() != 139 && last != 157)) { + strm->msg = (char *)"incorrect header check"; + ret = first ? Z_DATA_ERROR : Z_ERRNO; + break; /* not a gzip or compress header */ + } + first = 0; /* next non-header is junk */ + + /* process a compress (LZW) file -- can't be concatenated after this */ + if (last == 157) { + ret = lunpipe(have, next, indp, outfile, strm); + break; + } + + /* process remainder of gzip header */ + ret = Z_BUF_ERROR; + if (NEXT() != 8) { /* only deflate method allowed */ + if (last == -1) break; + strm->msg = (char *)"unknown compression method"; + ret = Z_DATA_ERROR; + break; + } + flags = NEXT(); /* header flags */ + NEXT(); /* discard mod time, xflgs, os */ + NEXT(); + NEXT(); + NEXT(); + NEXT(); + NEXT(); + if (last == -1) break; + if (flags & 0xe0) { + strm->msg = (char *)"unknown header flags set"; + ret = Z_DATA_ERROR; + break; + } + if (flags & 4) { /* extra field */ + len = NEXT(); + len += (unsigned)(NEXT()) << 8; + if (last == -1) break; + while (len > have) { + len -= have; + have = 0; + if (NEXT() == -1) break; + len--; + } + if (last == -1) break; + have -= len; + next += len; + } + if (flags & 8) /* file name */ + while (NEXT() != 0 && last != -1) + ; + if (flags & 16) /* comment */ + while (NEXT() != 0 && last != -1) + ; + if (flags & 2) { /* header crc */ + NEXT(); + NEXT(); + } + if (last == -1) break; + + /* set up output */ + outd.outfile = outfile; + outd.check = 1; + outd.crc = crc32(0L, Z_NULL, 0); + outd.total = 0; + + /* decompress data to output */ + strm->next_in = next; + strm->avail_in = have; + ret = inflateBack(strm, in, indp, out, &outd); + if (ret != Z_STREAM_END) break; + next = strm->next_in; + have = strm->avail_in; + strm->next_in = Z_NULL; /* so Z_BUF_ERROR means EOF */ + + /* check trailer */ + ret = Z_BUF_ERROR; + if (NEXT() != (int)(outd.crc & 0xff) || + NEXT() != (int)((outd.crc >> 8) & 0xff) || + NEXT() != (int)((outd.crc >> 16) & 0xff) || + NEXT() != (int)((outd.crc >> 24) & 0xff)) { + /* crc error */ + if (last != -1) { + strm->msg = (char *)"incorrect data check"; + ret = Z_DATA_ERROR; + } + break; + } + if (NEXT() != (int)(outd.total & 0xff) || + NEXT() != (int)((outd.total >> 8) & 0xff) || + NEXT() != (int)((outd.total >> 16) & 0xff) || + NEXT() != (int)((outd.total >> 24) & 0xff)) { + /* length error */ + if (last != -1) { + strm->msg = (char *)"incorrect length check"; + ret = Z_DATA_ERROR; + } + break; + } + + /* go back and look for another gzip stream */ + } + + /* clean up and return */ + return ret; +} + +/* Copy file attributes, from -> to, as best we can. This is best effort, so + no errors are reported. The mode bits, including suid, sgid, and the sticky + bit are copied (if allowed), the owner's user id and group id are copied + (again if allowed), and the access and modify times are copied. */ +local void copymeta(char *from, char *to) +{ + struct stat was; + struct utimbuf when; + + /* get all of from's Unix meta data, return if not a regular file */ + if (stat(from, &was) != 0 || (was.st_mode & S_IFMT) != S_IFREG) + return; + + /* set to's mode bits, ignore errors */ + (void)chmod(to, was.st_mode & 07777); + + /* copy owner's user and group, ignore errors */ + (void)chown(to, was.st_uid, was.st_gid); + + /* copy access and modify times, ignore errors */ + when.actime = was.st_atime; + when.modtime = was.st_mtime; + (void)utime(to, &when); +} + +/* Decompress the file inname to the file outnname, of if test is true, just + decompress without writing and check the gzip trailer for integrity. If + inname is NULL or an empty string, read from stdin. If outname is NULL or + an empty string, write to stdout. strm is a pre-initialized inflateBack + structure. When appropriate, copy the file attributes from inname to + outname. + + gunzip() returns 1 if there is an out-of-memory error or an unexpected + return code from gunpipe(). Otherwise it returns 0. + */ +local int gunzip(z_stream *strm, char *inname, char *outname, int test) +{ + int ret; + int infile, outfile; + + /* open files */ + if (inname == NULL || *inname == 0) { + inname = "-"; + infile = 0; /* stdin */ + } + else { + infile = open(inname, O_RDONLY, 0); + if (infile == -1) { + fprintf(stderr, "gun cannot open %s\n", inname); + return 0; + } + } + if (test) + outfile = -1; + else if (outname == NULL || *outname == 0) { + outname = "-"; + outfile = 1; /* stdout */ + } + else { + outfile = open(outname, O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (outfile == -1) { + close(infile); + fprintf(stderr, "gun cannot create %s\n", outname); + return 0; + } + } + errno = 0; + + /* decompress */ + ret = gunpipe(strm, infile, outfile); + if (outfile > 2) close(outfile); + if (infile > 2) close(infile); + + /* interpret result */ + switch (ret) { + case Z_OK: + case Z_ERRNO: + if (infile > 2 && outfile > 2) { + copymeta(inname, outname); /* copy attributes */ + unlink(inname); + } + if (ret == Z_ERRNO) + fprintf(stderr, "gun warning: trailing garbage ignored in %s\n", + inname); + break; + case Z_DATA_ERROR: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun data error on %s: %s\n", inname, strm->msg); + break; + case Z_MEM_ERROR: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun out of memory error--aborting\n"); + return 1; + case Z_BUF_ERROR: + if (outfile > 2) unlink(outname); + if (strm->next_in != Z_NULL) { + fprintf(stderr, "gun write error on %s: %s\n", + outname, strerror(errno)); + } + else if (errno) { + fprintf(stderr, "gun read error on %s: %s\n", + inname, strerror(errno)); + } + else { + fprintf(stderr, "gun unexpected end of file on %s\n", + inname); + } + break; + default: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun internal error--aborting\n"); + return 1; + } + return 0; +} + +/* Process the gun command line arguments. See the command syntax near the + beginning of this source file. */ +int main(int argc, char **argv) +{ + int ret, len, test; + char *outname; + unsigned char *window; + z_stream strm; + + /* initialize inflateBack state for repeated use */ + window = match; /* reuse LZW match buffer */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = inflateBackInit(&strm, 15, window); + if (ret != Z_OK) { + fprintf(stderr, "gun out of memory error--aborting\n"); + return 1; + } + + /* decompress each file to the same name with the suffix removed */ + argc--; + argv++; + test = 0; + if (argc && strcmp(*argv, "-h") == 0) { + fprintf(stderr, "gun 1.6 (17 Jan 2010)\n"); + fprintf(stderr, "Copyright (C) 2003-2010 Mark Adler\n"); + fprintf(stderr, "usage: gun [-t] [file1.gz [file2.Z ...]]\n"); + return 0; + } + if (argc && strcmp(*argv, "-t") == 0) { + test = 1; + argc--; + argv++; + } + if (argc) + do { + if (test) + outname = NULL; + else { + len = (int)strlen(*argv); + if (strcmp(*argv + len - 3, ".gz") == 0 || + strcmp(*argv + len - 3, "-gz") == 0) + len -= 3; + else if (strcmp(*argv + len - 2, ".z") == 0 || + strcmp(*argv + len - 2, "-z") == 0 || + strcmp(*argv + len - 2, "_z") == 0 || + strcmp(*argv + len - 2, ".Z") == 0) + len -= 2; + else { + fprintf(stderr, "gun error: no gz type on %s--skipping\n", + *argv); + continue; + } + outname = malloc(len + 1); + if (outname == NULL) { + fprintf(stderr, "gun out of memory error--aborting\n"); + ret = 1; + break; + } + memcpy(outname, *argv, len); + outname[len] = 0; + } + ret = gunzip(&strm, *argv, outname, test); + if (outname != NULL) free(outname); + if (ret) break; + } while (argv++, --argc); + else + ret = gunzip(&strm, NULL, NULL, test); + + /* clean up */ + inflateBackEnd(&strm); + return ret; +} diff --git a/zlib/zlib/examples/gzappend.c b/zlib/zlib/examples/gzappend.c new file mode 100644 index 00000000..662dec37 --- /dev/null +++ b/zlib/zlib/examples/gzappend.c @@ -0,0 +1,504 @@ +/* gzappend -- command to append to a gzip file + + Copyright (C) 2003, 2012 Mark Adler, all rights reserved + version 1.2, 11 Oct 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* + * Change history: + * + * 1.0 19 Oct 2003 - First version + * 1.1 4 Nov 2003 - Expand and clarify some comments and notes + * - Add version and copyright to help + * - Send help to stdout instead of stderr + * - Add some preemptive typecasts + * - Add L to constants in lseek() calls + * - Remove some debugging information in error messages + * - Use new data_type definition for zlib 1.2.1 + * - Simplfy and unify file operations + * - Finish off gzip file in gztack() + * - Use deflatePrime() instead of adding empty blocks + * - Keep gzip file clean on appended file read errors + * - Use in-place rotate instead of auxiliary buffer + * (Why you ask? Because it was fun to write!) + * 1.2 11 Oct 2012 - Fix for proper z_const usage + * - Check for input buffer malloc failure + */ + +/* + gzappend takes a gzip file and appends to it, compressing files from the + command line or data from stdin. The gzip file is written to directly, to + avoid copying that file, in case it's large. Note that this results in the + unfriendly behavior that if gzappend fails, the gzip file is corrupted. + + This program was written to illustrate the use of the new Z_BLOCK option of + zlib 1.2.x's inflate() function. This option returns from inflate() at each + block boundary to facilitate locating and modifying the last block bit at + the start of the final deflate block. Also whether using Z_BLOCK or not, + another required feature of zlib 1.2.x is that inflate() now provides the + number of unusued bits in the last input byte used. gzappend will not work + with versions of zlib earlier than 1.2.1. + + gzappend first decompresses the gzip file internally, discarding all but + the last 32K of uncompressed data, and noting the location of the last block + bit and the number of unused bits in the last byte of the compressed data. + The gzip trailer containing the CRC-32 and length of the uncompressed data + is verified. This trailer will be later overwritten. + + Then the last block bit is cleared by seeking back in the file and rewriting + the byte that contains it. Seeking forward, the last byte of the compressed + data is saved along with the number of unused bits to initialize deflate. + + A deflate process is initialized, using the last 32K of the uncompressed + data from the gzip file to initialize the dictionary. If the total + uncompressed data was less than 32K, then all of it is used to initialize + the dictionary. The deflate output bit buffer is also initialized with the + last bits from the original deflate stream. From here on, the data to + append is simply compressed using deflate, and written to the gzip file. + When that is complete, the new CRC-32 and uncompressed length are written + as the trailer of the gzip file. + */ + +#include +#include +#include +#include +#include +#include "zlib.h" + +#define local static +#define LGCHUNK 14 +#define CHUNK (1U << LGCHUNK) +#define DSIZE 32768U + +/* print an error message and terminate with extreme prejudice */ +local void bye(char *msg1, char *msg2) +{ + fprintf(stderr, "gzappend error: %s%s\n", msg1, msg2); + exit(1); +} + +/* return the greatest common divisor of a and b using Euclid's algorithm, + modified to be fast when one argument much greater than the other, and + coded to avoid unnecessary swapping */ +local unsigned gcd(unsigned a, unsigned b) +{ + unsigned c; + + while (a && b) + if (a > b) { + c = b; + while (a - c >= c) + c <<= 1; + a -= c; + } + else { + c = a; + while (b - c >= c) + c <<= 1; + b -= c; + } + return a + b; +} + +/* rotate list[0..len-1] left by rot positions, in place */ +local void rotate(unsigned char *list, unsigned len, unsigned rot) +{ + unsigned char tmp; + unsigned cycles; + unsigned char *start, *last, *to, *from; + + /* normalize rot and handle degenerate cases */ + if (len < 2) return; + if (rot >= len) rot %= len; + if (rot == 0) return; + + /* pointer to last entry in list */ + last = list + (len - 1); + + /* do simple left shift by one */ + if (rot == 1) { + tmp = *list; + memcpy(list, list + 1, len - 1); + *last = tmp; + return; + } + + /* do simple right shift by one */ + if (rot == len - 1) { + tmp = *last; + memmove(list + 1, list, len - 1); + *list = tmp; + return; + } + + /* otherwise do rotate as a set of cycles in place */ + cycles = gcd(len, rot); /* number of cycles */ + do { + start = from = list + cycles; /* start index is arbitrary */ + tmp = *from; /* save entry to be overwritten */ + for (;;) { + to = from; /* next step in cycle */ + from += rot; /* go right rot positions */ + if (from > last) from -= len; /* (pointer better not wrap) */ + if (from == start) break; /* all but one shifted */ + *to = *from; /* shift left */ + } + *to = tmp; /* complete the circle */ + } while (--cycles); +} + +/* structure for gzip file read operations */ +typedef struct { + int fd; /* file descriptor */ + int size; /* 1 << size is bytes in buf */ + unsigned left; /* bytes available at next */ + unsigned char *buf; /* buffer */ + z_const unsigned char *next; /* next byte in buffer */ + char *name; /* file name for error messages */ +} file; + +/* reload buffer */ +local int readin(file *in) +{ + int len; + + len = read(in->fd, in->buf, 1 << in->size); + if (len == -1) bye("error reading ", in->name); + in->left = (unsigned)len; + in->next = in->buf; + return len; +} + +/* read from file in, exit if end-of-file */ +local int readmore(file *in) +{ + if (readin(in) == 0) bye("unexpected end of ", in->name); + return 0; +} + +#define read1(in) (in->left == 0 ? readmore(in) : 0, \ + in->left--, *(in->next)++) + +/* skip over n bytes of in */ +local void skip(file *in, unsigned n) +{ + unsigned bypass; + + if (n > in->left) { + n -= in->left; + bypass = n & ~((1U << in->size) - 1); + if (bypass) { + if (lseek(in->fd, (off_t)bypass, SEEK_CUR) == -1) + bye("seeking ", in->name); + n -= bypass; + } + readmore(in); + if (n > in->left) + bye("unexpected end of ", in->name); + } + in->left -= n; + in->next += n; +} + +/* read a four-byte unsigned integer, little-endian, from in */ +unsigned long read4(file *in) +{ + unsigned long val; + + val = read1(in); + val += (unsigned)read1(in) << 8; + val += (unsigned long)read1(in) << 16; + val += (unsigned long)read1(in) << 24; + return val; +} + +/* skip over gzip header */ +local void gzheader(file *in) +{ + int flags; + unsigned n; + + if (read1(in) != 31 || read1(in) != 139) bye(in->name, " not a gzip file"); + if (read1(in) != 8) bye("unknown compression method in", in->name); + flags = read1(in); + if (flags & 0xe0) bye("unknown header flags set in", in->name); + skip(in, 6); + if (flags & 4) { + n = read1(in); + n += (unsigned)(read1(in)) << 8; + skip(in, n); + } + if (flags & 8) while (read1(in) != 0) ; + if (flags & 16) while (read1(in) != 0) ; + if (flags & 2) skip(in, 2); +} + +/* decompress gzip file "name", return strm with a deflate stream ready to + continue compression of the data in the gzip file, and return a file + descriptor pointing to where to write the compressed data -- the deflate + stream is initialized to compress using level "level" */ +local int gzscan(char *name, z_stream *strm, int level) +{ + int ret, lastbit, left, full; + unsigned have; + unsigned long crc, tot; + unsigned char *window; + off_t lastoff, end; + file gz; + + /* open gzip file */ + gz.name = name; + gz.fd = open(name, O_RDWR, 0); + if (gz.fd == -1) bye("cannot open ", name); + gz.buf = malloc(CHUNK); + if (gz.buf == NULL) bye("out of memory", ""); + gz.size = LGCHUNK; + gz.left = 0; + + /* skip gzip header */ + gzheader(&gz); + + /* prepare to decompress */ + window = malloc(DSIZE); + if (window == NULL) bye("out of memory", ""); + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = inflateInit2(strm, -15); + if (ret != Z_OK) bye("out of memory", " or library mismatch"); + + /* decompress the deflate stream, saving append information */ + lastbit = 0; + lastoff = lseek(gz.fd, 0L, SEEK_CUR) - gz.left; + left = 0; + strm->avail_in = gz.left; + strm->next_in = gz.next; + crc = crc32(0L, Z_NULL, 0); + have = full = 0; + do { + /* if needed, get more input */ + if (strm->avail_in == 0) { + readmore(&gz); + strm->avail_in = gz.left; + strm->next_in = gz.next; + } + + /* set up output to next available section of sliding window */ + strm->avail_out = DSIZE - have; + strm->next_out = window + have; + + /* inflate and check for errors */ + ret = inflate(strm, Z_BLOCK); + if (ret == Z_STREAM_ERROR) bye("internal stream error!", ""); + if (ret == Z_MEM_ERROR) bye("out of memory", ""); + if (ret == Z_DATA_ERROR) + bye("invalid compressed data--format violated in", name); + + /* update crc and sliding window pointer */ + crc = crc32(crc, window + have, DSIZE - have - strm->avail_out); + if (strm->avail_out) + have = DSIZE - strm->avail_out; + else { + have = 0; + full = 1; + } + + /* process end of block */ + if (strm->data_type & 128) { + if (strm->data_type & 64) + left = strm->data_type & 0x1f; + else { + lastbit = strm->data_type & 0x1f; + lastoff = lseek(gz.fd, 0L, SEEK_CUR) - strm->avail_in; + } + } + } while (ret != Z_STREAM_END); + inflateEnd(strm); + gz.left = strm->avail_in; + gz.next = strm->next_in; + + /* save the location of the end of the compressed data */ + end = lseek(gz.fd, 0L, SEEK_CUR) - gz.left; + + /* check gzip trailer and save total for deflate */ + if (crc != read4(&gz)) + bye("invalid compressed data--crc mismatch in ", name); + tot = strm->total_out; + if ((tot & 0xffffffffUL) != read4(&gz)) + bye("invalid compressed data--length mismatch in", name); + + /* if not at end of file, warn */ + if (gz.left || readin(&gz)) + fprintf(stderr, + "gzappend warning: junk at end of gzip file overwritten\n"); + + /* clear last block bit */ + lseek(gz.fd, lastoff - (lastbit != 0), SEEK_SET); + if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name); + *gz.buf = (unsigned char)(*gz.buf ^ (1 << ((8 - lastbit) & 7))); + lseek(gz.fd, -1L, SEEK_CUR); + if (write(gz.fd, gz.buf, 1) != 1) bye("writing after seek to ", name); + + /* if window wrapped, build dictionary from window by rotating */ + if (full) { + rotate(window, DSIZE, have); + have = DSIZE; + } + + /* set up deflate stream with window, crc, total_in, and leftover bits */ + ret = deflateInit2(strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) bye("out of memory", ""); + deflateSetDictionary(strm, window, have); + strm->adler = crc; + strm->total_in = tot; + if (left) { + lseek(gz.fd, --end, SEEK_SET); + if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name); + deflatePrime(strm, 8 - left, *gz.buf); + } + lseek(gz.fd, end, SEEK_SET); + + /* clean up and return */ + free(window); + free(gz.buf); + return gz.fd; +} + +/* append file "name" to gzip file gd using deflate stream strm -- if last + is true, then finish off the deflate stream at the end */ +local void gztack(char *name, int gd, z_stream *strm, int last) +{ + int fd, len, ret; + unsigned left; + unsigned char *in, *out; + + /* open file to compress and append */ + fd = 0; + if (name != NULL) { + fd = open(name, O_RDONLY, 0); + if (fd == -1) + fprintf(stderr, "gzappend warning: %s not found, skipping ...\n", + name); + } + + /* allocate buffers */ + in = malloc(CHUNK); + out = malloc(CHUNK); + if (in == NULL || out == NULL) bye("out of memory", ""); + + /* compress input file and append to gzip file */ + do { + /* get more input */ + len = read(fd, in, CHUNK); + if (len == -1) { + fprintf(stderr, + "gzappend warning: error reading %s, skipping rest ...\n", + name); + len = 0; + } + strm->avail_in = (unsigned)len; + strm->next_in = in; + if (len) strm->adler = crc32(strm->adler, in, (unsigned)len); + + /* compress and write all available output */ + do { + strm->avail_out = CHUNK; + strm->next_out = out; + ret = deflate(strm, last && len == 0 ? Z_FINISH : Z_NO_FLUSH); + left = CHUNK - strm->avail_out; + while (left) { + len = write(gd, out + CHUNK - strm->avail_out - left, left); + if (len == -1) bye("writing gzip file", ""); + left -= (unsigned)len; + } + } while (strm->avail_out == 0 && ret != Z_STREAM_END); + } while (len != 0); + + /* write trailer after last entry */ + if (last) { + deflateEnd(strm); + out[0] = (unsigned char)(strm->adler); + out[1] = (unsigned char)(strm->adler >> 8); + out[2] = (unsigned char)(strm->adler >> 16); + out[3] = (unsigned char)(strm->adler >> 24); + out[4] = (unsigned char)(strm->total_in); + out[5] = (unsigned char)(strm->total_in >> 8); + out[6] = (unsigned char)(strm->total_in >> 16); + out[7] = (unsigned char)(strm->total_in >> 24); + len = 8; + do { + ret = write(gd, out + 8 - len, len); + if (ret == -1) bye("writing gzip file", ""); + len -= ret; + } while (len); + close(gd); + } + + /* clean up and return */ + free(out); + free(in); + if (fd > 0) close(fd); +} + +/* process the compression level option if present, scan the gzip file, and + append the specified files, or append the data from stdin if no other file + names are provided on the command line -- the gzip file must be writable + and seekable */ +int main(int argc, char **argv) +{ + int gd, level; + z_stream strm; + + /* ignore command name */ + argc--; argv++; + + /* provide usage if no arguments */ + if (*argv == NULL) { + printf( + "gzappend 1.2 (11 Oct 2012) Copyright (C) 2003, 2012 Mark Adler\n" + ); + printf( + "usage: gzappend [-level] file.gz [ addthis [ andthis ... ]]\n"); + return 0; + } + + /* set compression level */ + level = Z_DEFAULT_COMPRESSION; + if (argv[0][0] == '-') { + if (argv[0][1] < '0' || argv[0][1] > '9' || argv[0][2] != 0) + bye("invalid compression level", ""); + level = argv[0][1] - '0'; + if (*++argv == NULL) bye("no gzip file name after options", ""); + } + + /* prepare to append to gzip file */ + gd = gzscan(*argv++, &strm, level); + + /* append files on command line, or from stdin if none */ + if (*argv == NULL) + gztack(NULL, gd, &strm, 1); + else + do { + gztack(*argv, gd, &strm, argv[1] == NULL); + } while (*++argv != NULL); + return 0; +} diff --git a/zlib/zlib/examples/gzjoin.c b/zlib/zlib/examples/gzjoin.c new file mode 100644 index 00000000..89e80984 --- /dev/null +++ b/zlib/zlib/examples/gzjoin.c @@ -0,0 +1,449 @@ +/* gzjoin -- command to join gzip files into one gzip file + + Copyright (C) 2004, 2005, 2012 Mark Adler, all rights reserved + version 1.2, 14 Aug 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* + * Change history: + * + * 1.0 11 Dec 2004 - First version + * 1.1 12 Jun 2005 - Changed ssize_t to long for portability + * 1.2 14 Aug 2012 - Clean up for z_const usage + */ + +/* + gzjoin takes one or more gzip files on the command line and writes out a + single gzip file that will uncompress to the concatenation of the + uncompressed data from the individual gzip files. gzjoin does this without + having to recompress any of the data and without having to calculate a new + crc32 for the concatenated uncompressed data. gzjoin does however have to + decompress all of the input data in order to find the bits in the compressed + data that need to be modified to concatenate the streams. + + gzjoin does not do an integrity check on the input gzip files other than + checking the gzip header and decompressing the compressed data. They are + otherwise assumed to be complete and correct. + + Each joint between gzip files removes at least 18 bytes of previous trailer + and subsequent header, and inserts an average of about three bytes to the + compressed data in order to connect the streams. The output gzip file + has a minimal ten-byte gzip header with no file name or modification time. + + This program was written to illustrate the use of the Z_BLOCK option of + inflate() and the crc32_combine() function. gzjoin will not compile with + versions of zlib earlier than 1.2.3. + */ + +#include /* fputs(), fprintf(), fwrite(), putc() */ +#include /* exit(), malloc(), free() */ +#include /* open() */ +#include /* close(), read(), lseek() */ +#include "zlib.h" + /* crc32(), crc32_combine(), inflateInit2(), inflate(), inflateEnd() */ + +#define local static + +/* exit with an error (return a value to allow use in an expression) */ +local int bail(char *why1, char *why2) +{ + fprintf(stderr, "gzjoin error: %s%s, output incomplete\n", why1, why2); + exit(1); + return 0; +} + +/* -- simple buffered file input with access to the buffer -- */ + +#define CHUNK 32768 /* must be a power of two and fit in unsigned */ + +/* bin buffered input file type */ +typedef struct { + char *name; /* name of file for error messages */ + int fd; /* file descriptor */ + unsigned left; /* bytes remaining at next */ + unsigned char *next; /* next byte to read */ + unsigned char *buf; /* allocated buffer of length CHUNK */ +} bin; + +/* close a buffered file and free allocated memory */ +local void bclose(bin *in) +{ + if (in != NULL) { + if (in->fd != -1) + close(in->fd); + if (in->buf != NULL) + free(in->buf); + free(in); + } +} + +/* open a buffered file for input, return a pointer to type bin, or NULL on + failure */ +local bin *bopen(char *name) +{ + bin *in; + + in = malloc(sizeof(bin)); + if (in == NULL) + return NULL; + in->buf = malloc(CHUNK); + in->fd = open(name, O_RDONLY, 0); + if (in->buf == NULL || in->fd == -1) { + bclose(in); + return NULL; + } + in->left = 0; + in->next = in->buf; + in->name = name; + return in; +} + +/* load buffer from file, return -1 on read error, 0 or 1 on success, with + 1 indicating that end-of-file was reached */ +local int bload(bin *in) +{ + long len; + + if (in == NULL) + return -1; + if (in->left != 0) + return 0; + in->next = in->buf; + do { + len = (long)read(in->fd, in->buf + in->left, CHUNK - in->left); + if (len < 0) + return -1; + in->left += (unsigned)len; + } while (len != 0 && in->left < CHUNK); + return len == 0 ? 1 : 0; +} + +/* get a byte from the file, bail if end of file */ +#define bget(in) (in->left ? 0 : bload(in), \ + in->left ? (in->left--, *(in->next)++) : \ + bail("unexpected end of file on ", in->name)) + +/* get a four-byte little-endian unsigned integer from file */ +local unsigned long bget4(bin *in) +{ + unsigned long val; + + val = bget(in); + val += (unsigned long)(bget(in)) << 8; + val += (unsigned long)(bget(in)) << 16; + val += (unsigned long)(bget(in)) << 24; + return val; +} + +/* skip bytes in file */ +local void bskip(bin *in, unsigned skip) +{ + /* check pointer */ + if (in == NULL) + return; + + /* easy case -- skip bytes in buffer */ + if (skip <= in->left) { + in->left -= skip; + in->next += skip; + return; + } + + /* skip what's in buffer, discard buffer contents */ + skip -= in->left; + in->left = 0; + + /* seek past multiples of CHUNK bytes */ + if (skip > CHUNK) { + unsigned left; + + left = skip & (CHUNK - 1); + if (left == 0) { + /* exact number of chunks: seek all the way minus one byte to check + for end-of-file with a read */ + lseek(in->fd, skip - 1, SEEK_CUR); + if (read(in->fd, in->buf, 1) != 1) + bail("unexpected end of file on ", in->name); + return; + } + + /* skip the integral chunks, update skip with remainder */ + lseek(in->fd, skip - left, SEEK_CUR); + skip = left; + } + + /* read more input and skip remainder */ + bload(in); + if (skip > in->left) + bail("unexpected end of file on ", in->name); + in->left -= skip; + in->next += skip; +} + +/* -- end of buffered input functions -- */ + +/* skip the gzip header from file in */ +local void gzhead(bin *in) +{ + int flags; + + /* verify gzip magic header and compression method */ + if (bget(in) != 0x1f || bget(in) != 0x8b || bget(in) != 8) + bail(in->name, " is not a valid gzip file"); + + /* get and verify flags */ + flags = bget(in); + if ((flags & 0xe0) != 0) + bail("unknown reserved bits set in ", in->name); + + /* skip modification time, extra flags, and os */ + bskip(in, 6); + + /* skip extra field if present */ + if (flags & 4) { + unsigned len; + + len = bget(in); + len += (unsigned)(bget(in)) << 8; + bskip(in, len); + } + + /* skip file name if present */ + if (flags & 8) + while (bget(in) != 0) + ; + + /* skip comment if present */ + if (flags & 16) + while (bget(in) != 0) + ; + + /* skip header crc if present */ + if (flags & 2) + bskip(in, 2); +} + +/* write a four-byte little-endian unsigned integer to out */ +local void put4(unsigned long val, FILE *out) +{ + putc(val & 0xff, out); + putc((val >> 8) & 0xff, out); + putc((val >> 16) & 0xff, out); + putc((val >> 24) & 0xff, out); +} + +/* Load up zlib stream from buffered input, bail if end of file */ +local void zpull(z_streamp strm, bin *in) +{ + if (in->left == 0) + bload(in); + if (in->left == 0) + bail("unexpected end of file on ", in->name); + strm->avail_in = in->left; + strm->next_in = in->next; +} + +/* Write header for gzip file to out and initialize trailer. */ +local void gzinit(unsigned long *crc, unsigned long *tot, FILE *out) +{ + fwrite("\x1f\x8b\x08\0\0\0\0\0\0\xff", 1, 10, out); + *crc = crc32(0L, Z_NULL, 0); + *tot = 0; +} + +/* Copy the compressed data from name, zeroing the last block bit of the last + block if clr is true, and adding empty blocks as needed to get to a byte + boundary. If clr is false, then the last block becomes the last block of + the output, and the gzip trailer is written. crc and tot maintains the + crc and length (modulo 2^32) of the output for the trailer. The resulting + gzip file is written to out. gzinit() must be called before the first call + of gzcopy() to write the gzip header and to initialize crc and tot. */ +local void gzcopy(char *name, int clr, unsigned long *crc, unsigned long *tot, + FILE *out) +{ + int ret; /* return value from zlib functions */ + int pos; /* where the "last block" bit is in byte */ + int last; /* true if processing the last block */ + bin *in; /* buffered input file */ + unsigned char *start; /* start of compressed data in buffer */ + unsigned char *junk; /* buffer for uncompressed data -- discarded */ + z_off_t len; /* length of uncompressed data (support > 4 GB) */ + z_stream strm; /* zlib inflate stream */ + + /* open gzip file and skip header */ + in = bopen(name); + if (in == NULL) + bail("could not open ", name); + gzhead(in); + + /* allocate buffer for uncompressed data and initialize raw inflate + stream */ + junk = malloc(CHUNK); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -15); + if (junk == NULL || ret != Z_OK) + bail("out of memory", ""); + + /* inflate and copy compressed data, clear last-block bit if requested */ + len = 0; + zpull(&strm, in); + start = in->next; + last = start[0] & 1; + if (last && clr) + start[0] &= ~1; + strm.avail_out = 0; + for (;;) { + /* if input used and output done, write used input and get more */ + if (strm.avail_in == 0 && strm.avail_out != 0) { + fwrite(start, 1, strm.next_in - start, out); + start = in->buf; + in->left = 0; + zpull(&strm, in); + } + + /* decompress -- return early when end-of-block reached */ + strm.avail_out = CHUNK; + strm.next_out = junk; + ret = inflate(&strm, Z_BLOCK); + switch (ret) { + case Z_MEM_ERROR: + bail("out of memory", ""); + case Z_DATA_ERROR: + bail("invalid compressed data in ", in->name); + } + + /* update length of uncompressed data */ + len += CHUNK - strm.avail_out; + + /* check for block boundary (only get this when block copied out) */ + if (strm.data_type & 128) { + /* if that was the last block, then done */ + if (last) + break; + + /* number of unused bits in last byte */ + pos = strm.data_type & 7; + + /* find the next last-block bit */ + if (pos != 0) { + /* next last-block bit is in last used byte */ + pos = 0x100 >> pos; + last = strm.next_in[-1] & pos; + if (last && clr) + in->buf[strm.next_in - in->buf - 1] &= ~pos; + } + else { + /* next last-block bit is in next unused byte */ + if (strm.avail_in == 0) { + /* don't have that byte yet -- get it */ + fwrite(start, 1, strm.next_in - start, out); + start = in->buf; + in->left = 0; + zpull(&strm, in); + } + last = strm.next_in[0] & 1; + if (last && clr) + in->buf[strm.next_in - in->buf] &= ~1; + } + } + } + + /* update buffer with unused input */ + in->left = strm.avail_in; + in->next = in->buf + (strm.next_in - in->buf); + + /* copy used input, write empty blocks to get to byte boundary */ + pos = strm.data_type & 7; + fwrite(start, 1, in->next - start - 1, out); + last = in->next[-1]; + if (pos == 0 || !clr) + /* already at byte boundary, or last file: write last byte */ + putc(last, out); + else { + /* append empty blocks to last byte */ + last &= ((0x100 >> pos) - 1); /* assure unused bits are zero */ + if (pos & 1) { + /* odd -- append an empty stored block */ + putc(last, out); + if (pos == 1) + putc(0, out); /* two more bits in block header */ + fwrite("\0\0\xff\xff", 1, 4, out); + } + else { + /* even -- append 1, 2, or 3 empty fixed blocks */ + switch (pos) { + case 6: + putc(last | 8, out); + last = 0; + case 4: + putc(last | 0x20, out); + last = 0; + case 2: + putc(last | 0x80, out); + putc(0, out); + } + } + } + + /* update crc and tot */ + *crc = crc32_combine(*crc, bget4(in), len); + *tot += (unsigned long)len; + + /* clean up */ + inflateEnd(&strm); + free(junk); + bclose(in); + + /* write trailer if this is the last gzip file */ + if (!clr) { + put4(*crc, out); + put4(*tot, out); + } +} + +/* join the gzip files on the command line, write result to stdout */ +int main(int argc, char **argv) +{ + unsigned long crc, tot; /* running crc and total uncompressed length */ + + /* skip command name */ + argc--; + argv++; + + /* show usage if no arguments */ + if (argc == 0) { + fputs("gzjoin usage: gzjoin f1.gz [f2.gz [f3.gz ...]] > fjoin.gz\n", + stderr); + return 0; + } + + /* join gzip files on command line and write to stdout */ + gzinit(&crc, &tot, stdout); + while (argc--) + gzcopy(*argv++, argc, &crc, &tot, stdout); + + /* done */ + return 0; +} diff --git a/zlib/zlib/examples/gzlog.c b/zlib/zlib/examples/gzlog.c new file mode 100644 index 00000000..922f878d --- /dev/null +++ b/zlib/zlib/examples/gzlog.c @@ -0,0 +1,1059 @@ +/* + * gzlog.c + * Copyright (C) 2004, 2008, 2012 Mark Adler, all rights reserved + * For conditions of distribution and use, see copyright notice in gzlog.h + * version 2.2, 14 Aug 2012 + */ + +/* + gzlog provides a mechanism for frequently appending short strings to a gzip + file that is efficient both in execution time and compression ratio. The + strategy is to write the short strings in an uncompressed form to the end of + the gzip file, only compressing when the amount of uncompressed data has + reached a given threshold. + + gzlog also provides protection against interruptions in the process due to + system crashes. The status of the operation is recorded in an extra field + in the gzip file, and is only updated once the gzip file is brought to a + valid state. The last data to be appended or compressed is saved in an + auxiliary file, so that if the operation is interrupted, it can be completed + the next time an append operation is attempted. + + gzlog maintains another auxiliary file with the last 32K of data from the + compressed portion, which is preloaded for the compression of the subsequent + data. This minimizes the impact to the compression ratio of appending. + */ + +/* + Operations Concept: + + Files (log name "foo"): + foo.gz -- gzip file with the complete log + foo.add -- last message to append or last data to compress + foo.dict -- dictionary of the last 32K of data for next compression + foo.temp -- temporary dictionary file for compression after this one + foo.lock -- lock file for reading and writing the other files + foo.repairs -- log file for log file recovery operations (not compressed) + + gzip file structure: + - fixed-length (no file name) header with extra field (see below) + - compressed data ending initially with empty stored block + - uncompressed data filling out originally empty stored block and + subsequent stored blocks as needed (16K max each) + - gzip trailer + - no junk at end (no other gzip streams) + + When appending data, the information in the first three items above plus the + foo.add file are sufficient to recover an interrupted append operation. The + extra field has the necessary information to restore the start of the last + stored block and determine where to append the data in the foo.add file, as + well as the crc and length of the gzip data before the append operation. + + The foo.add file is created before the gzip file is marked for append, and + deleted after the gzip file is marked as complete. So if the append + operation is interrupted, the data to add will still be there. If due to + some external force, the foo.add file gets deleted between when the append + operation was interrupted and when recovery is attempted, the gzip file will + still be restored, but without the appended data. + + When compressing data, the information in the first two items above plus the + foo.add file are sufficient to recover an interrupted compress operation. + The extra field has the necessary information to find the end of the + compressed data, and contains both the crc and length of just the compressed + data and of the complete set of data including the contents of the foo.add + file. + + Again, the foo.add file is maintained during the compress operation in case + of an interruption. If in the unlikely event the foo.add file with the data + to be compressed is missing due to some external force, a gzip file with + just the previous compressed data will be reconstructed. In this case, all + of the data that was to be compressed is lost (approximately one megabyte). + This will not occur if all that happened was an interruption of the compress + operation. + + The third state that is marked is the replacement of the old dictionary with + the new dictionary after a compress operation. Once compression is + complete, the gzip file is marked as being in the replace state. This + completes the gzip file, so an interrupt after being so marked does not + result in recompression. Then the dictionary file is replaced, and the gzip + file is marked as completed. This state prevents the possibility of + restarting compression with the wrong dictionary file. + + All three operations are wrapped by a lock/unlock procedure. In order to + gain exclusive access to the log files, first a foo.lock file must be + exclusively created. When all operations are complete, the lock is + released by deleting the foo.lock file. If when attempting to create the + lock file, it already exists and the modify time of the lock file is more + than five minutes old (set by the PATIENCE define below), then the old + lock file is considered stale and deleted, and the exclusive creation of + the lock file is retried. To assure that there are no false assessments + of the staleness of the lock file, the operations periodically touch the + lock file to update the modified date. + + Following is the definition of the extra field with all of the information + required to enable the above append and compress operations and their + recovery if interrupted. Multi-byte values are stored little endian + (consistent with the gzip format). File pointers are eight bytes long. + The crc's and lengths for the gzip trailer are four bytes long. (Note that + the length at the end of a gzip file is used for error checking only, and + for large files is actually the length modulo 2^32.) The stored block + length is two bytes long. The gzip extra field two-byte identification is + "ap" for append. It is assumed that writing the extra field to the file is + an "atomic" operation. That is, either all of the extra field is written + to the file, or none of it is, if the operation is interrupted right at the + point of updating the extra field. This is a reasonable assumption, since + the extra field is within the first 52 bytes of the file, which is smaller + than any expected block size for a mass storage device (usually 512 bytes or + larger). + + Extra field (35 bytes): + - Pointer to first stored block length -- this points to the two-byte length + of the first stored block, which is followed by the two-byte, one's + complement of that length. The stored block length is preceded by the + three-bit header of the stored block, which is the actual start of the + stored block in the deflate format. See the bit offset field below. + - Pointer to the last stored block length. This is the same as above, but + for the last stored block of the uncompressed data in the gzip file. + Initially this is the same as the first stored block length pointer. + When the stored block gets to 16K (see the MAX_STORE define), then a new + stored block as added, at which point the last stored block length pointer + is different from the first stored block length pointer. When they are + different, the first bit of the last stored block header is eight bits, or + one byte back from the block length. + - Compressed data crc and length. This is the crc and length of the data + that is in the compressed portion of the deflate stream. These are used + only in the event that the foo.add file containing the data to compress is + lost after a compress operation is interrupted. + - Total data crc and length. This is the crc and length of all of the data + stored in the gzip file, compressed and uncompressed. It is used to + reconstruct the gzip trailer when compressing, as well as when recovering + interrupted operations. + - Final stored block length. This is used to quickly find where to append, + and allows the restoration of the original final stored block state when + an append operation is interrupted. + - First stored block start as the number of bits back from the final stored + block first length byte. This value is in the range of 3..10, and is + stored as the low three bits of the final byte of the extra field after + subtracting three (0..7). This allows the last-block bit of the stored + block header to be updated when a new stored block is added, for the case + when the first stored block and the last stored block are the same. (When + they are different, the numbers of bits back is known to be eight.) This + also allows for new compressed data to be appended to the old compressed + data in the compress operation, overwriting the previous first stored + block, or for the compressed data to be terminated and a valid gzip file + reconstructed on the off chance that a compression operation was + interrupted and the data to compress in the foo.add file was deleted. + - The operation in process. This is the next two bits in the last byte (the + bits under the mask 0x18). The are interpreted as 0: nothing in process, + 1: append in process, 2: compress in process, 3: replace in process. + - The top three bits of the last byte in the extra field are reserved and + are currently set to zero. + + Main procedure: + - Exclusively create the foo.lock file using the O_CREAT and O_EXCL modes of + the system open() call. If the modify time of an existing lock file is + more than PATIENCE seconds old, then the lock file is deleted and the + exclusive create is retried. + - Load the extra field from the foo.gz file, and see if an operation was in + progress but not completed. If so, apply the recovery procedure below. + - Perform the append procedure with the provided data. + - If the uncompressed data in the foo.gz file is 1MB or more, apply the + compress procedure. + - Delete the foo.lock file. + + Append procedure: + - Put what to append in the foo.add file so that the operation can be + restarted if this procedure is interrupted. + - Mark the foo.gz extra field with the append operation in progress. + + Restore the original last-block bit and stored block length of the last + stored block from the information in the extra field, in case a previous + append operation was interrupted. + - Append the provided data to the last stored block, creating new stored + blocks as needed and updating the stored blocks last-block bits and + lengths. + - Update the crc and length with the new data, and write the gzip trailer. + - Write over the extra field (with a single write operation) with the new + pointers, lengths, and crc's, and mark the gzip file as not in process. + Though there is still a foo.add file, it will be ignored since nothing + is in process. If a foo.add file is leftover from a previously + completed operation, it is truncated when writing new data to it. + - Delete the foo.add file. + + Compress and replace procedures: + - Read all of the uncompressed data in the stored blocks in foo.gz and write + it to foo.add. Also write foo.temp with the last 32K of that data to + provide a dictionary for the next invocation of this procedure. + - Rewrite the extra field marking foo.gz with a compression in process. + * If there is no data provided to compress (due to a missing foo.add file + when recovering), reconstruct and truncate the foo.gz file to contain + only the previous compressed data and proceed to the step after the next + one. Otherwise ... + - Compress the data with the dictionary in foo.dict, and write to the + foo.gz file starting at the bit immediately following the last previously + compressed block. If there is no foo.dict, proceed anyway with the + compression at slightly reduced efficiency. (For the foo.dict file to be + missing requires some external failure beyond simply the interruption of + a compress operation.) During this process, the foo.lock file is + periodically touched to assure that that file is not considered stale by + another process before we're done. The deflation is terminated with a + non-last empty static block (10 bits long), that is then located and + written over by a last-bit-set empty stored block. + - Append the crc and length of the data in the gzip file (previously + calculated during the append operations). + - Write over the extra field with the updated stored block offsets, bits + back, crc's, and lengths, and mark foo.gz as in process for a replacement + of the dictionary. + @ Delete the foo.add file. + - Replace foo.dict with foo.temp. + - Write over the extra field, marking foo.gz as complete. + + Recovery procedure: + - If not a replace recovery, read in the foo.add file, and provide that data + to the appropriate recovery below. If there is no foo.add file, provide + a zero data length to the recovery. In that case, the append recovery + restores the foo.gz to the previous compressed + uncompressed data state. + For the the compress recovery, a missing foo.add file results in foo.gz + being restored to the previous compressed-only data state. + - Append recovery: + - Pick up append at + step above + - Compress recovery: + - Pick up compress at * step above + - Replace recovery: + - Pick up compress at @ step above + - Log the repair with a date stamp in foo.repairs + */ + +#include +#include /* rename, fopen, fprintf, fclose */ +#include /* malloc, free */ +#include /* strlen, strrchr, strcpy, strncpy, strcmp */ +#include /* open */ +#include /* lseek, read, write, close, unlink, sleep, */ + /* ftruncate, fsync */ +#include /* errno */ +#include /* time, ctime */ +#include /* stat */ +#include /* utimes */ +#include "zlib.h" /* crc32 */ + +#include "gzlog.h" /* header for external access */ + +#define local static +typedef unsigned int uint; +typedef unsigned long ulong; + +/* Macro for debugging to deterministically force recovery operations */ +#ifdef DEBUG + #include /* longjmp */ + jmp_buf gzlog_jump; /* where to go back to */ + int gzlog_bail = 0; /* which point to bail at (1..8) */ + int gzlog_count = -1; /* number of times through to wait */ +# define BAIL(n) do { if (n == gzlog_bail && gzlog_count-- == 0) \ + longjmp(gzlog_jump, gzlog_bail); } while (0) +#else +# define BAIL(n) +#endif + +/* how old the lock file can be in seconds before considering it stale */ +#define PATIENCE 300 + +/* maximum stored block size in Kbytes -- must be in 1..63 */ +#define MAX_STORE 16 + +/* number of stored Kbytes to trigger compression (must be >= 32 to allow + dictionary construction, and <= 204 * MAX_STORE, in order for >> 10 to + discard the stored block headers contribution of five bytes each) */ +#define TRIGGER 1024 + +/* size of a deflate dictionary (this cannot be changed) */ +#define DICT 32768U + +/* values for the operation (2 bits) */ +#define NO_OP 0 +#define APPEND_OP 1 +#define COMPRESS_OP 2 +#define REPLACE_OP 3 + +/* macros to extract little-endian integers from an unsigned byte buffer */ +#define PULL2(p) ((p)[0]+((uint)((p)[1])<<8)) +#define PULL4(p) (PULL2(p)+((ulong)PULL2(p+2)<<16)) +#define PULL8(p) (PULL4(p)+((off_t)PULL4(p+4)<<32)) + +/* macros to store integers into a byte buffer in little-endian order */ +#define PUT2(p,a) do {(p)[0]=a;(p)[1]=(a)>>8;} while(0) +#define PUT4(p,a) do {PUT2(p,a);PUT2(p+2,a>>16);} while(0) +#define PUT8(p,a) do {PUT4(p,a);PUT4(p+4,a>>32);} while(0) + +/* internal structure for log information */ +#define LOGID "\106\035\172" /* should be three non-zero characters */ +struct log { + char id[4]; /* contains LOGID to detect inadvertent overwrites */ + int fd; /* file descriptor for .gz file, opened read/write */ + char *path; /* allocated path, e.g. "/var/log/foo" or "foo" */ + char *end; /* end of path, for appending suffices such as ".gz" */ + off_t first; /* offset of first stored block first length byte */ + int back; /* location of first block id in bits back from first */ + uint stored; /* bytes currently in last stored block */ + off_t last; /* offset of last stored block first length byte */ + ulong ccrc; /* crc of compressed data */ + ulong clen; /* length (modulo 2^32) of compressed data */ + ulong tcrc; /* crc of total data */ + ulong tlen; /* length (modulo 2^32) of total data */ + time_t lock; /* last modify time of our lock file */ +}; + +/* gzip header for gzlog */ +local unsigned char log_gzhead[] = { + 0x1f, 0x8b, /* magic gzip id */ + 8, /* compression method is deflate */ + 4, /* there is an extra field (no file name) */ + 0, 0, 0, 0, /* no modification time provided */ + 0, 0xff, /* no extra flags, no OS specified */ + 39, 0, 'a', 'p', 35, 0 /* extra field with "ap" subfield */ + /* 35 is EXTRA, 39 is EXTRA + 4 */ +}; + +#define HEAD sizeof(log_gzhead) /* should be 16 */ + +/* initial gzip extra field content (52 == HEAD + EXTRA + 1) */ +local unsigned char log_gzext[] = { + 52, 0, 0, 0, 0, 0, 0, 0, /* offset of first stored block length */ + 52, 0, 0, 0, 0, 0, 0, 0, /* offset of last stored block length */ + 0, 0, 0, 0, 0, 0, 0, 0, /* compressed data crc and length */ + 0, 0, 0, 0, 0, 0, 0, 0, /* total data crc and length */ + 0, 0, /* final stored block data length */ + 5 /* op is NO_OP, last bit 8 bits back */ +}; + +#define EXTRA sizeof(log_gzext) /* should be 35 */ + +/* initial gzip data and trailer */ +local unsigned char log_gzbody[] = { + 1, 0, 0, 0xff, 0xff, /* empty stored block (last) */ + 0, 0, 0, 0, /* crc */ + 0, 0, 0, 0 /* uncompressed length */ +}; + +#define BODY sizeof(log_gzbody) + +/* Exclusively create foo.lock in order to negotiate exclusive access to the + foo.* files. If the modify time of an existing lock file is greater than + PATIENCE seconds in the past, then consider the lock file to have been + abandoned, delete it, and try the exclusive create again. Save the lock + file modify time for verification of ownership. Return 0 on success, or -1 + on failure, usually due to an access restriction or invalid path. Note that + if stat() or unlink() fails, it may be due to another process noticing the + abandoned lock file a smidge sooner and deleting it, so those are not + flagged as an error. */ +local int log_lock(struct log *log) +{ + int fd; + struct stat st; + + strcpy(log->end, ".lock"); + while ((fd = open(log->path, O_CREAT | O_EXCL, 0644)) < 0) { + if (errno != EEXIST) + return -1; + if (stat(log->path, &st) == 0 && time(NULL) - st.st_mtime > PATIENCE) { + unlink(log->path); + continue; + } + sleep(2); /* relinquish the CPU for two seconds while waiting */ + } + close(fd); + if (stat(log->path, &st) == 0) + log->lock = st.st_mtime; + return 0; +} + +/* Update the modify time of the lock file to now, in order to prevent another + task from thinking that the lock is stale. Save the lock file modify time + for verification of ownership. */ +local void log_touch(struct log *log) +{ + struct stat st; + + strcpy(log->end, ".lock"); + utimes(log->path, NULL); + if (stat(log->path, &st) == 0) + log->lock = st.st_mtime; +} + +/* Check the log file modify time against what is expected. Return true if + this is not our lock. If it is our lock, touch it to keep it. */ +local int log_check(struct log *log) +{ + struct stat st; + + strcpy(log->end, ".lock"); + if (stat(log->path, &st) || st.st_mtime != log->lock) + return 1; + log_touch(log); + return 0; +} + +/* Unlock a previously acquired lock, but only if it's ours. */ +local void log_unlock(struct log *log) +{ + if (log_check(log)) + return; + strcpy(log->end, ".lock"); + unlink(log->path); + log->lock = 0; +} + +/* Check the gzip header and read in the extra field, filling in the values in + the log structure. Return op on success or -1 if the gzip header was not as + expected. op is the current operation in progress last written to the extra + field. This assumes that the gzip file has already been opened, with the + file descriptor log->fd. */ +local int log_head(struct log *log) +{ + int op; + unsigned char buf[HEAD + EXTRA]; + + if (lseek(log->fd, 0, SEEK_SET) < 0 || + read(log->fd, buf, HEAD + EXTRA) != HEAD + EXTRA || + memcmp(buf, log_gzhead, HEAD)) { + return -1; + } + log->first = PULL8(buf + HEAD); + log->last = PULL8(buf + HEAD + 8); + log->ccrc = PULL4(buf + HEAD + 16); + log->clen = PULL4(buf + HEAD + 20); + log->tcrc = PULL4(buf + HEAD + 24); + log->tlen = PULL4(buf + HEAD + 28); + log->stored = PULL2(buf + HEAD + 32); + log->back = 3 + (buf[HEAD + 34] & 7); + op = (buf[HEAD + 34] >> 3) & 3; + return op; +} + +/* Write over the extra field contents, marking the operation as op. Use fsync + to assure that the device is written to, and in the requested order. This + operation, and only this operation, is assumed to be atomic in order to + assure that the log is recoverable in the event of an interruption at any + point in the process. Return -1 if the write to foo.gz failed. */ +local int log_mark(struct log *log, int op) +{ + int ret; + unsigned char ext[EXTRA]; + + PUT8(ext, log->first); + PUT8(ext + 8, log->last); + PUT4(ext + 16, log->ccrc); + PUT4(ext + 20, log->clen); + PUT4(ext + 24, log->tcrc); + PUT4(ext + 28, log->tlen); + PUT2(ext + 32, log->stored); + ext[34] = log->back - 3 + (op << 3); + fsync(log->fd); + ret = lseek(log->fd, HEAD, SEEK_SET) < 0 || + write(log->fd, ext, EXTRA) != EXTRA ? -1 : 0; + fsync(log->fd); + return ret; +} + +/* Rewrite the last block header bits and subsequent zero bits to get to a byte + boundary, setting the last block bit if last is true, and then write the + remainder of the stored block header (length and one's complement). Leave + the file pointer after the end of the last stored block data. Return -1 if + there is a read or write failure on the foo.gz file */ +local int log_last(struct log *log, int last) +{ + int back, len, mask; + unsigned char buf[6]; + + /* determine the locations of the bytes and bits to modify */ + back = log->last == log->first ? log->back : 8; + len = back > 8 ? 2 : 1; /* bytes back from log->last */ + mask = 0x80 >> ((back - 1) & 7); /* mask for block last-bit */ + + /* get the byte to modify (one or two back) into buf[0] -- don't need to + read the byte if the last-bit is eight bits back, since in that case + the entire byte will be modified */ + buf[0] = 0; + if (back != 8 && (lseek(log->fd, log->last - len, SEEK_SET) < 0 || + read(log->fd, buf, 1) != 1)) + return -1; + + /* change the last-bit of the last stored block as requested -- note + that all bits above the last-bit are set to zero, per the type bits + of a stored block being 00 and per the convention that the bits to + bring the stream to a byte boundary are also zeros */ + buf[1] = 0; + buf[2 - len] = (*buf & (mask - 1)) + (last ? mask : 0); + + /* write the modified stored block header and lengths, move the file + pointer to after the last stored block data */ + PUT2(buf + 2, log->stored); + PUT2(buf + 4, log->stored ^ 0xffff); + return lseek(log->fd, log->last - len, SEEK_SET) < 0 || + write(log->fd, buf + 2 - len, len + 4) != len + 4 || + lseek(log->fd, log->stored, SEEK_CUR) < 0 ? -1 : 0; +} + +/* Append len bytes from data to the locked and open log file. len may be zero + if recovering and no .add file was found. In that case, the previous state + of the foo.gz file is restored. The data is appended uncompressed in + deflate stored blocks. Return -1 if there was an error reading or writing + the foo.gz file. */ +local int log_append(struct log *log, unsigned char *data, size_t len) +{ + uint put; + off_t end; + unsigned char buf[8]; + + /* set the last block last-bit and length, in case recovering an + interrupted append, then position the file pointer to append to the + block */ + if (log_last(log, 1)) + return -1; + + /* append, adding stored blocks and updating the offset of the last stored + block as needed, and update the total crc and length */ + while (len) { + /* append as much as we can to the last block */ + put = (MAX_STORE << 10) - log->stored; + if (put > len) + put = (uint)len; + if (put) { + if (write(log->fd, data, put) != put) + return -1; + BAIL(1); + log->tcrc = crc32(log->tcrc, data, put); + log->tlen += put; + log->stored += put; + data += put; + len -= put; + } + + /* if we need to, add a new empty stored block */ + if (len) { + /* mark current block as not last */ + if (log_last(log, 0)) + return -1; + + /* point to new, empty stored block */ + log->last += 4 + log->stored + 1; + log->stored = 0; + } + + /* mark last block as last, update its length */ + if (log_last(log, 1)) + return -1; + BAIL(2); + } + + /* write the new crc and length trailer, and truncate just in case (could + be recovering from partial append with a missing foo.add file) */ + PUT4(buf, log->tcrc); + PUT4(buf + 4, log->tlen); + if (write(log->fd, buf, 8) != 8 || + (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end)) + return -1; + + /* write the extra field, marking the log file as done, delete .add file */ + if (log_mark(log, NO_OP)) + return -1; + strcpy(log->end, ".add"); + unlink(log->path); /* ignore error, since may not exist */ + return 0; +} + +/* Replace the foo.dict file with the foo.temp file. Also delete the foo.add + file, since the compress operation may have been interrupted before that was + done. Returns 1 if memory could not be allocated, or -1 if reading or + writing foo.gz fails, or if the rename fails for some reason other than + foo.temp not existing. foo.temp not existing is a permitted error, since + the replace operation may have been interrupted after the rename is done, + but before foo.gz is marked as complete. */ +local int log_replace(struct log *log) +{ + int ret; + char *dest; + + /* delete foo.add file */ + strcpy(log->end, ".add"); + unlink(log->path); /* ignore error, since may not exist */ + BAIL(3); + + /* rename foo.name to foo.dict, replacing foo.dict if it exists */ + strcpy(log->end, ".dict"); + dest = malloc(strlen(log->path) + 1); + if (dest == NULL) + return -2; + strcpy(dest, log->path); + strcpy(log->end, ".temp"); + ret = rename(log->path, dest); + free(dest); + if (ret && errno != ENOENT) + return -1; + BAIL(4); + + /* mark the foo.gz file as done */ + return log_mark(log, NO_OP); +} + +/* Compress the len bytes at data and append the compressed data to the + foo.gz deflate data immediately after the previous compressed data. This + overwrites the previous uncompressed data, which was stored in foo.add + and is the data provided in data[0..len-1]. If this operation is + interrupted, it picks up at the start of this routine, with the foo.add + file read in again. If there is no data to compress (len == 0), then we + simply terminate the foo.gz file after the previously compressed data, + appending a final empty stored block and the gzip trailer. Return -1 if + reading or writing the log.gz file failed, or -2 if there was a memory + allocation failure. */ +local int log_compress(struct log *log, unsigned char *data, size_t len) +{ + int fd; + uint got, max; + ssize_t dict; + off_t end; + z_stream strm; + unsigned char buf[DICT]; + + /* compress and append compressed data */ + if (len) { + /* set up for deflate, allocating memory */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, + Z_DEFAULT_STRATEGY) != Z_OK) + return -2; + + /* read in dictionary (last 32K of data that was compressed) */ + strcpy(log->end, ".dict"); + fd = open(log->path, O_RDONLY, 0); + if (fd >= 0) { + dict = read(fd, buf, DICT); + close(fd); + if (dict < 0) { + deflateEnd(&strm); + return -1; + } + if (dict) + deflateSetDictionary(&strm, buf, (uint)dict); + } + log_touch(log); + + /* prime deflate with last bits of previous block, position write + pointer to write those bits and overwrite what follows */ + if (lseek(log->fd, log->first - (log->back > 8 ? 2 : 1), + SEEK_SET) < 0 || + read(log->fd, buf, 1) != 1 || lseek(log->fd, -1, SEEK_CUR) < 0) { + deflateEnd(&strm); + return -1; + } + deflatePrime(&strm, (8 - log->back) & 7, *buf); + + /* compress, finishing with a partial non-last empty static block */ + strm.next_in = data; + max = (((uint)0 - 1) >> 1) + 1; /* in case int smaller than size_t */ + do { + strm.avail_in = len > max ? max : (uint)len; + len -= strm.avail_in; + do { + strm.avail_out = DICT; + strm.next_out = buf; + deflate(&strm, len ? Z_NO_FLUSH : Z_PARTIAL_FLUSH); + got = DICT - strm.avail_out; + if (got && write(log->fd, buf, got) != got) { + deflateEnd(&strm); + return -1; + } + log_touch(log); + } while (strm.avail_out == 0); + } while (len); + deflateEnd(&strm); + BAIL(5); + + /* find start of empty static block -- scanning backwards the first one + bit is the second bit of the block, if the last byte is zero, then + we know the byte before that has a one in the top bit, since an + empty static block is ten bits long */ + if ((log->first = lseek(log->fd, -1, SEEK_CUR)) < 0 || + read(log->fd, buf, 1) != 1) + return -1; + log->first++; + if (*buf) { + log->back = 1; + while ((*buf & ((uint)1 << (8 - log->back++))) == 0) + ; /* guaranteed to terminate, since *buf != 0 */ + } + else + log->back = 10; + + /* update compressed crc and length */ + log->ccrc = log->tcrc; + log->clen = log->tlen; + } + else { + /* no data to compress -- fix up existing gzip stream */ + log->tcrc = log->ccrc; + log->tlen = log->clen; + } + + /* complete and truncate gzip stream */ + log->last = log->first; + log->stored = 0; + PUT4(buf, log->tcrc); + PUT4(buf + 4, log->tlen); + if (log_last(log, 1) || write(log->fd, buf, 8) != 8 || + (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end)) + return -1; + BAIL(6); + + /* mark as being in the replace operation */ + if (log_mark(log, REPLACE_OP)) + return -1; + + /* execute the replace operation and mark the file as done */ + return log_replace(log); +} + +/* log a repair record to the .repairs file */ +local void log_log(struct log *log, int op, char *record) +{ + time_t now; + FILE *rec; + + now = time(NULL); + strcpy(log->end, ".repairs"); + rec = fopen(log->path, "a"); + if (rec == NULL) + return; + fprintf(rec, "%.24s %s recovery: %s\n", ctime(&now), op == APPEND_OP ? + "append" : (op == COMPRESS_OP ? "compress" : "replace"), record); + fclose(rec); + return; +} + +/* Recover the interrupted operation op. First read foo.add for recovering an + append or compress operation. Return -1 if there was an error reading or + writing foo.gz or reading an existing foo.add, or -2 if there was a memory + allocation failure. */ +local int log_recover(struct log *log, int op) +{ + int fd, ret = 0; + unsigned char *data = NULL; + size_t len = 0; + struct stat st; + + /* log recovery */ + log_log(log, op, "start"); + + /* load foo.add file if expected and present */ + if (op == APPEND_OP || op == COMPRESS_OP) { + strcpy(log->end, ".add"); + if (stat(log->path, &st) == 0 && st.st_size) { + len = (size_t)(st.st_size); + if ((off_t)len != st.st_size || + (data = malloc(st.st_size)) == NULL) { + log_log(log, op, "allocation failure"); + return -2; + } + if ((fd = open(log->path, O_RDONLY, 0)) < 0) { + log_log(log, op, ".add file read failure"); + return -1; + } + ret = (size_t)read(fd, data, len) != len; + close(fd); + if (ret) { + log_log(log, op, ".add file read failure"); + return -1; + } + log_log(log, op, "loaded .add file"); + } + else + log_log(log, op, "missing .add file!"); + } + + /* recover the interrupted operation */ + switch (op) { + case APPEND_OP: + ret = log_append(log, data, len); + break; + case COMPRESS_OP: + ret = log_compress(log, data, len); + break; + case REPLACE_OP: + ret = log_replace(log); + } + + /* log status */ + log_log(log, op, ret ? "failure" : "complete"); + + /* clean up */ + if (data != NULL) + free(data); + return ret; +} + +/* Close the foo.gz file (if open) and release the lock. */ +local void log_close(struct log *log) +{ + if (log->fd >= 0) + close(log->fd); + log->fd = -1; + log_unlock(log); +} + +/* Open foo.gz, verify the header, and load the extra field contents, after + first creating the foo.lock file to gain exclusive access to the foo.* + files. If foo.gz does not exist or is empty, then write the initial header, + extra, and body content of an empty foo.gz log file. If there is an error + creating the lock file due to access restrictions, or an error reading or + writing the foo.gz file, or if the foo.gz file is not a proper log file for + this object (e.g. not a gzip file or does not contain the expected extra + field), then return true. If there is an error, the lock is released. + Otherwise, the lock is left in place. */ +local int log_open(struct log *log) +{ + int op; + + /* release open file resource if left over -- can occur if lock lost + between gzlog_open() and gzlog_write() */ + if (log->fd >= 0) + close(log->fd); + log->fd = -1; + + /* negotiate exclusive access */ + if (log_lock(log) < 0) + return -1; + + /* open the log file, foo.gz */ + strcpy(log->end, ".gz"); + log->fd = open(log->path, O_RDWR | O_CREAT, 0644); + if (log->fd < 0) { + log_close(log); + return -1; + } + + /* if new, initialize foo.gz with an empty log, delete old dictionary */ + if (lseek(log->fd, 0, SEEK_END) == 0) { + if (write(log->fd, log_gzhead, HEAD) != HEAD || + write(log->fd, log_gzext, EXTRA) != EXTRA || + write(log->fd, log_gzbody, BODY) != BODY) { + log_close(log); + return -1; + } + strcpy(log->end, ".dict"); + unlink(log->path); + } + + /* verify log file and load extra field information */ + if ((op = log_head(log)) < 0) { + log_close(log); + return -1; + } + + /* check for interrupted process and if so, recover */ + if (op != NO_OP && log_recover(log, op)) { + log_close(log); + return -1; + } + + /* touch the lock file to prevent another process from grabbing it */ + log_touch(log); + return 0; +} + +/* See gzlog.h for the description of the external methods below */ +gzlog *gzlog_open(char *path) +{ + size_t n; + struct log *log; + + /* check arguments */ + if (path == NULL || *path == 0) + return NULL; + + /* allocate and initialize log structure */ + log = malloc(sizeof(struct log)); + if (log == NULL) + return NULL; + strcpy(log->id, LOGID); + log->fd = -1; + + /* save path and end of path for name construction */ + n = strlen(path); + log->path = malloc(n + 9); /* allow for ".repairs" */ + if (log->path == NULL) { + free(log); + return NULL; + } + strcpy(log->path, path); + log->end = log->path + n; + + /* gain exclusive access and verify log file -- may perform a + recovery operation if needed */ + if (log_open(log)) { + free(log->path); + free(log); + return NULL; + } + + /* return pointer to log structure */ + return log; +} + +/* gzlog_compress() return values: + 0: all good + -1: file i/o error (usually access issue) + -2: memory allocation failure + -3: invalid log pointer argument */ +int gzlog_compress(gzlog *logd) +{ + int fd, ret; + uint block; + size_t len, next; + unsigned char *data, buf[5]; + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + + /* see if we lost the lock -- if so get it again and reload the extra + field information (it probably changed), recover last operation if + necessary */ + if (log_check(log) && log_open(log)) + return -1; + + /* create space for uncompressed data */ + len = ((size_t)(log->last - log->first) & ~(((size_t)1 << 10) - 1)) + + log->stored; + if ((data = malloc(len)) == NULL) + return -2; + + /* do statement here is just a cheap trick for error handling */ + do { + /* read in the uncompressed data */ + if (lseek(log->fd, log->first - 1, SEEK_SET) < 0) + break; + next = 0; + while (next < len) { + if (read(log->fd, buf, 5) != 5) + break; + block = PULL2(buf + 1); + if (next + block > len || + read(log->fd, (char *)data + next, block) != block) + break; + next += block; + } + if (lseek(log->fd, 0, SEEK_CUR) != log->last + 4 + log->stored) + break; + log_touch(log); + + /* write the uncompressed data to the .add file */ + strcpy(log->end, ".add"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + break; + ret = (size_t)write(fd, data, len) != len; + if (ret | close(fd)) + break; + log_touch(log); + + /* write the dictionary for the next compress to the .temp file */ + strcpy(log->end, ".temp"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + break; + next = DICT > len ? len : DICT; + ret = (size_t)write(fd, (char *)data + len - next, next) != next; + if (ret | close(fd)) + break; + log_touch(log); + + /* roll back to compressed data, mark the compress in progress */ + log->last = log->first; + log->stored = 0; + if (log_mark(log, COMPRESS_OP)) + break; + BAIL(7); + + /* compress and append the data (clears mark) */ + ret = log_compress(log, data, len); + free(data); + return ret; + } while (0); + + /* broke out of do above on i/o error */ + free(data); + return -1; +} + +/* gzlog_write() return values: + 0: all good + -1: file i/o error (usually access issue) + -2: memory allocation failure + -3: invalid log pointer argument */ +int gzlog_write(gzlog *logd, void *data, size_t len) +{ + int fd, ret; + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + if (data == NULL || len <= 0) + return 0; + + /* see if we lost the lock -- if so get it again and reload the extra + field information (it probably changed), recover last operation if + necessary */ + if (log_check(log) && log_open(log)) + return -1; + + /* create and write .add file */ + strcpy(log->end, ".add"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -1; + ret = (size_t)write(fd, data, len) != len; + if (ret | close(fd)) + return -1; + log_touch(log); + + /* mark log file with append in progress */ + if (log_mark(log, APPEND_OP)) + return -1; + BAIL(8); + + /* append data (clears mark) */ + if (log_append(log, data, len)) + return -1; + + /* check to see if it's time to compress -- if not, then done */ + if (((log->last - log->first) >> 10) + (log->stored >> 10) < TRIGGER) + return 0; + + /* time to compress */ + return gzlog_compress(log); +} + +/* gzlog_close() return values: + 0: ok + -3: invalid log pointer argument */ +int gzlog_close(gzlog *logd) +{ + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + + /* close the log file and release the lock */ + log_close(log); + + /* free structure and return */ + if (log->path != NULL) + free(log->path); + strcpy(log->id, "bad"); + free(log); + return 0; +} diff --git a/zlib/zlib/examples/gzlog.h b/zlib/zlib/examples/gzlog.h new file mode 100644 index 00000000..86f0cecb --- /dev/null +++ b/zlib/zlib/examples/gzlog.h @@ -0,0 +1,91 @@ +/* gzlog.h + Copyright (C) 2004, 2008, 2012 Mark Adler, all rights reserved + version 2.2, 14 Aug 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* Version History: + 1.0 26 Nov 2004 First version + 2.0 25 Apr 2008 Complete redesign for recovery of interrupted operations + Interface changed slightly in that now path is a prefix + Compression now occurs as needed during gzlog_write() + gzlog_write() now always leaves the log file as valid gzip + 2.1 8 Jul 2012 Fix argument checks in gzlog_compress() and gzlog_write() + 2.2 14 Aug 2012 Clean up signed comparisons + */ + +/* + The gzlog object allows writing short messages to a gzipped log file, + opening the log file locked for small bursts, and then closing it. The log + object works by appending stored (uncompressed) data to the gzip file until + 1 MB has been accumulated. At that time, the stored data is compressed, and + replaces the uncompressed data in the file. The log file is truncated to + its new size at that time. After each write operation, the log file is a + valid gzip file that can decompressed to recover what was written. + + The gzlog operations can be interupted at any point due to an application or + system crash, and the log file will be recovered the next time the log is + opened with gzlog_open(). + */ + +#ifndef GZLOG_H +#define GZLOG_H + +/* gzlog object type */ +typedef void gzlog; + +/* Open a gzlog object, creating the log file if it does not exist. Return + NULL on error. Note that gzlog_open() could take a while to complete if it + has to wait to verify that a lock is stale (possibly for five minutes), or + if there is significant contention with other instantiations of this object + when locking the resource. path is the prefix of the file names created by + this object. If path is "foo", then the log file will be "foo.gz", and + other auxiliary files will be created and destroyed during the process: + "foo.dict" for a compression dictionary, "foo.temp" for a temporary (next) + dictionary, "foo.add" for data being added or compressed, "foo.lock" for the + lock file, and "foo.repairs" to log recovery operations performed due to + interrupted gzlog operations. A gzlog_open() followed by a gzlog_close() + will recover a previously interrupted operation, if any. */ +gzlog *gzlog_open(char *path); + +/* Write to a gzlog object. Return zero on success, -1 if there is a file i/o + error on any of the gzlog files (this should not happen if gzlog_open() + succeeded, unless the device has run out of space or leftover auxiliary + files have permissions or ownership that prevent their use), -2 if there is + a memory allocation failure, or -3 if the log argument is invalid (e.g. if + it was not created by gzlog_open()). This function will write data to the + file uncompressed, until 1 MB has been accumulated, at which time that data + will be compressed. The log file will be a valid gzip file upon successful + return. */ +int gzlog_write(gzlog *log, void *data, size_t len); + +/* Force compression of any uncompressed data in the log. This should be used + sparingly, if at all. The main application would be when a log file will + not be appended to again. If this is used to compress frequently while + appending, it will both significantly increase the execution time and + reduce the compression ratio. The return codes are the same as for + gzlog_write(). */ +int gzlog_compress(gzlog *log); + +/* Close a gzlog object. Return zero on success, -3 if the log argument is + invalid. The log object is freed, and so cannot be referenced again. */ +int gzlog_close(gzlog *log); + +#endif diff --git a/zlib/zlib/examples/zlib_how.html b/zlib/zlib/examples/zlib_how.html new file mode 100644 index 00000000..444ff1c9 --- /dev/null +++ b/zlib/zlib/examples/zlib_how.html @@ -0,0 +1,545 @@ + + + + +zlib Usage Example + + + +

zlib Usage Example

+We often get questions about how the deflate() and inflate() functions should be used. +Users wonder when they should provide more input, when they should use more output, +what to do with a Z_BUF_ERROR, how to make sure the process terminates properly, and +so on. So for those who have read zlib.h (a few times), and +would like further edification, below is an annotated example in C of simple routines to compress and decompress +from an input file to an output file using deflate() and inflate() respectively. The +annotations are interspersed between lines of the code. So please read between the lines. +We hope this helps explain some of the intricacies of zlib. +

+Without further adieu, here is the program zpipe.c: +


+/* zpipe.c: example of proper use of zlib's inflate() and deflate()
+   Not copyrighted -- provided to the public domain
+   Version 1.4  11 December 2005  Mark Adler */
+
+/* Version history:
+   1.0  30 Oct 2004  First version
+   1.1   8 Nov 2004  Add void casting for unused return values
+                     Use switch statement for inflate() return values
+   1.2   9 Nov 2004  Add assertions to document zlib guarantees
+   1.3   6 Apr 2005  Remove incorrect assertion in inf()
+   1.4  11 Dec 2005  Add hack to avoid MSDOS end-of-line conversions
+                     Avoid some compiler warnings for input and output buffers
+ */
+
+We now include the header files for the required definitions. From +stdio.h we use fopen(), fread(), fwrite(), +feof(), ferror(), and fclose() for file i/o, and +fputs() for error messages. From string.h we use +strcmp() for command line argument processing. +From assert.h we use the assert() macro. +From zlib.h +we use the basic compression functions deflateInit(), +deflate(), and deflateEnd(), and the basic decompression +functions inflateInit(), inflate(), and +inflateEnd(). +

+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "zlib.h"
+
+This is an ugly hack required to avoid corruption of the input and output data on +Windows/MS-DOS systems. Without this, those systems would assume that the input and output +files are text, and try to convert the end-of-line characters from one standard to +another. That would corrupt binary data, and in particular would render the compressed data unusable. +This sets the input and output to binary which suppresses the end-of-line conversions. +SET_BINARY_MODE() will be used later on stdin and stdout, at the beginning of main(). +

+#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
+#  include <fcntl.h>
+#  include <io.h>
+#  define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
+#else
+#  define SET_BINARY_MODE(file)
+#endif
+
+CHUNK is simply the buffer size for feeding data to and pulling data +from the zlib routines. Larger buffer sizes would be more efficient, +especially for inflate(). If the memory is available, buffers sizes +on the order of 128K or 256K bytes should be used. +

+#define CHUNK 16384
+
+The def() routine compresses data from an input file to an output file. The output data +will be in the zlib format, which is different from the gzip or zip +formats. The zlib format has a very small header of only two bytes to identify it as +a zlib stream and to provide decoding information, and a four-byte trailer with a fast +check value to verify the integrity of the uncompressed data after decoding. +

+/* Compress from file source to file dest until EOF on source.
+   def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
+   allocated for processing, Z_STREAM_ERROR if an invalid compression
+   level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
+   version of the library linked do not match, or Z_ERRNO if there is
+   an error reading or writing the files. */
+int def(FILE *source, FILE *dest, int level)
+{
+
+Here are the local variables for def(). ret will be used for zlib +return codes. flush will keep track of the current flushing state for deflate(), +which is either no flushing, or flush to completion after the end of the input file is reached. +have is the amount of data returned from deflate(). The strm structure +is used to pass information to and from the zlib routines, and to maintain the +deflate() state. in and out are the input and output buffers for +deflate(). +

+    int ret, flush;
+    unsigned have;
+    z_stream strm;
+    unsigned char in[CHUNK];
+    unsigned char out[CHUNK];
+
+The first thing we do is to initialize the zlib state for compression using +deflateInit(). This must be done before the first use of deflate(). +The zalloc, zfree, and opaque fields in the strm +structure must be initialized before calling deflateInit(). Here they are +set to the zlib constant Z_NULL to request that zlib use +the default memory allocation routines. An application may also choose to provide +custom memory allocation routines here. deflateInit() will allocate on the +order of 256K bytes for the internal state. +(See zlib Technical Details.) +

+deflateInit() is called with a pointer to the structure to be initialized and +the compression level, which is an integer in the range of -1 to 9. Lower compression +levels result in faster execution, but less compression. Higher levels result in +greater compression, but slower execution. The zlib constant Z_DEFAULT_COMPRESSION, +equal to -1, +provides a good compromise between compression and speed and is equivalent to level 6. +Level 0 actually does no compression at all, and in fact expands the data slightly to produce +the zlib format (it is not a byte-for-byte copy of the input). +More advanced applications of zlib +may use deflateInit2() here instead. Such an application may want to reduce how +much memory will be used, at some price in compression. Or it may need to request a +gzip header and trailer instead of a zlib header and trailer, or raw +encoding with no header or trailer at all. +

+We must check the return value of deflateInit() against the zlib constant +Z_OK to make sure that it was able to +allocate memory for the internal state, and that the provided arguments were valid. +deflateInit() will also check that the version of zlib that the zlib.h +file came from matches the version of zlib actually linked with the program. This +is especially important for environments in which zlib is a shared library. +

+Note that an application can initialize multiple, independent zlib streams, which can +operate in parallel. The state information maintained in the structure allows the zlib +routines to be reentrant. +


+    /* allocate deflate state */
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    ret = deflateInit(&strm, level);
+    if (ret != Z_OK)
+        return ret;
+
+With the pleasantries out of the way, now we can get down to business. The outer do-loop +reads all of the input file and exits at the bottom of the loop once end-of-file is reached. +This loop contains the only call of deflate(). So we must make sure that all of the +input data has been processed and that all of the output data has been generated and consumed +before we fall out of the loop at the bottom. +

+    /* compress until end of file */
+    do {
+
+We start off by reading data from the input file. The number of bytes read is put directly +into avail_in, and a pointer to those bytes is put into next_in. We also +check to see if end-of-file on the input has been reached. If we are at the end of file, then flush is set to the +zlib constant Z_FINISH, which is later passed to deflate() to +indicate that this is the last chunk of input data to compress. We need to use feof() +to check for end-of-file as opposed to seeing if fewer than CHUNK bytes have been read. The +reason is that if the input file length is an exact multiple of CHUNK, we will miss +the fact that we got to the end-of-file, and not know to tell deflate() to finish +up the compressed stream. If we are not yet at the end of the input, then the zlib +constant Z_NO_FLUSH will be passed to deflate to indicate that we are still +in the middle of the uncompressed data. +

+If there is an error in reading from the input file, the process is aborted with +deflateEnd() being called to free the allocated zlib state before returning +the error. We wouldn't want a memory leak, now would we? deflateEnd() can be called +at any time after the state has been initialized. Once that's done, deflateInit() (or +deflateInit2()) would have to be called to start a new compression process. There is +no point here in checking the deflateEnd() return code. The deallocation can't fail. +


+        strm.avail_in = fread(in, 1, CHUNK, source);
+        if (ferror(source)) {
+            (void)deflateEnd(&strm);
+            return Z_ERRNO;
+        }
+        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
+        strm.next_in = in;
+
+The inner do-loop passes our chunk of input data to deflate(), and then +keeps calling deflate() until it is done producing output. Once there is no more +new output, deflate() is guaranteed to have consumed all of the input, i.e., +avail_in will be zero. +

+        /* run deflate() on input until output buffer not full, finish
+           compression if all of source has been read in */
+        do {
+
+Output space is provided to deflate() by setting avail_out to the number +of available output bytes and next_out to a pointer to that space. +

+            strm.avail_out = CHUNK;
+            strm.next_out = out;
+
+Now we call the compression engine itself, deflate(). It takes as many of the +avail_in bytes at next_in as it can process, and writes as many as +avail_out bytes to next_out. Those counters and pointers are then +updated past the input data consumed and the output data written. It is the amount of +output space available that may limit how much input is consumed. +Hence the inner loop to make sure that +all of the input is consumed by providing more output space each time. Since avail_in +and next_in are updated by deflate(), we don't have to mess with those +between deflate() calls until it's all used up. +

+The parameters to deflate() are a pointer to the strm structure containing +the input and output information and the internal compression engine state, and a parameter +indicating whether and how to flush data to the output. Normally deflate will consume +several K bytes of input data before producing any output (except for the header), in order +to accumulate statistics on the data for optimum compression. It will then put out a burst of +compressed data, and proceed to consume more input before the next burst. Eventually, +deflate() +must be told to terminate the stream, complete the compression with provided input data, and +write out the trailer check value. deflate() will continue to compress normally as long +as the flush parameter is Z_NO_FLUSH. Once the Z_FINISH parameter is provided, +deflate() will begin to complete the compressed output stream. However depending on how +much output space is provided, deflate() may have to be called several times until it +has provided the complete compressed stream, even after it has consumed all of the input. The flush +parameter must continue to be Z_FINISH for those subsequent calls. +

+There are other values of the flush parameter that are used in more advanced applications. You can +force deflate() to produce a burst of output that encodes all of the input data provided +so far, even if it wouldn't have otherwise, for example to control data latency on a link with +compressed data. You can also ask that deflate() do that as well as erase any history up to +that point so that what follows can be decompressed independently, for example for random access +applications. Both requests will degrade compression by an amount depending on how often such +requests are made. +

+deflate() has a return value that can indicate errors, yet we do not check it here. Why +not? Well, it turns out that deflate() can do no wrong here. Let's go through +deflate()'s return values and dispense with them one by one. The possible values are +Z_OK, Z_STREAM_END, Z_STREAM_ERROR, or Z_BUF_ERROR. Z_OK +is, well, ok. Z_STREAM_END is also ok and will be returned for the last call of +deflate(). This is already guaranteed by calling deflate() with Z_FINISH +until it has no more output. Z_STREAM_ERROR is only possible if the stream is not +initialized properly, but we did initialize it properly. There is no harm in checking for +Z_STREAM_ERROR here, for example to check for the possibility that some +other part of the application inadvertently clobbered the memory containing the zlib state. +Z_BUF_ERROR will be explained further below, but +suffice it to say that this is simply an indication that deflate() could not consume +more input or produce more output. deflate() can be called again with more output space +or more available input, which it will be in this code. +


+            ret = deflate(&strm, flush);    /* no bad return value */
+            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
+
+Now we compute how much output deflate() provided on the last call, which is the +difference between how much space was provided before the call, and how much output space +is still available after the call. Then that data, if any, is written to the output file. +We can then reuse the output buffer for the next call of deflate(). Again if there +is a file i/o error, we call deflateEnd() before returning to avoid a memory leak. +

+            have = CHUNK - strm.avail_out;
+            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
+                (void)deflateEnd(&strm);
+                return Z_ERRNO;
+            }
+
+The inner do-loop is repeated until the last deflate() call fails to fill the +provided output buffer. Then we know that deflate() has done as much as it can with +the provided input, and that all of that input has been consumed. We can then fall out of this +loop and reuse the input buffer. +

+The way we tell that deflate() has no more output is by seeing that it did not fill +the output buffer, leaving avail_out greater than zero. However suppose that +deflate() has no more output, but just so happened to exactly fill the output buffer! +avail_out is zero, and we can't tell that deflate() has done all it can. +As far as we know, deflate() +has more output for us. So we call it again. But now deflate() produces no output +at all, and avail_out remains unchanged as CHUNK. That deflate() call +wasn't able to do anything, either consume input or produce output, and so it returns +Z_BUF_ERROR. (See, I told you I'd cover this later.) However this is not a problem at +all. Now we finally have the desired indication that deflate() is really done, +and so we drop out of the inner loop to provide more input to deflate(). +

+With flush set to Z_FINISH, this final set of deflate() calls will +complete the output stream. Once that is done, subsequent calls of deflate() would return +Z_STREAM_ERROR if the flush parameter is not Z_FINISH, and do no more processing +until the state is reinitialized. +

+Some applications of zlib have two loops that call deflate() +instead of the single inner loop we have here. The first loop would call +without flushing and feed all of the data to deflate(). The second loop would call +deflate() with no more +data and the Z_FINISH parameter to complete the process. As you can see from this +example, that can be avoided by simply keeping track of the current flush state. +


+        } while (strm.avail_out == 0);
+        assert(strm.avail_in == 0);     /* all input will be used */
+
+Now we check to see if we have already processed all of the input file. That information was +saved in the flush variable, so we see if that was set to Z_FINISH. If so, +then we're done and we fall out of the outer loop. We're guaranteed to get Z_STREAM_END +from the last deflate() call, since we ran it until the last chunk of input was +consumed and all of the output was generated. +

+        /* done when last data in file processed */
+    } while (flush != Z_FINISH);
+    assert(ret == Z_STREAM_END);        /* stream will be complete */
+
+The process is complete, but we still need to deallocate the state to avoid a memory leak +(or rather more like a memory hemorrhage if you didn't do this). Then +finally we can return with a happy return value. +

+    /* clean up and return */
+    (void)deflateEnd(&strm);
+    return Z_OK;
+}
+
+Now we do the same thing for decompression in the inf() routine. inf() +decompresses what is hopefully a valid zlib stream from the input file and writes the +uncompressed data to the output file. Much of the discussion above for def() +applies to inf() as well, so the discussion here will focus on the differences between +the two. +

+/* Decompress from file source to file dest until stream ends or EOF.
+   inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
+   allocated for processing, Z_DATA_ERROR if the deflate data is
+   invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
+   the version of the library linked do not match, or Z_ERRNO if there
+   is an error reading or writing the files. */
+int inf(FILE *source, FILE *dest)
+{
+
+The local variables have the same functionality as they do for def(). The +only difference is that there is no flush variable, since inflate() +can tell from the zlib stream itself when the stream is complete. +

+    int ret;
+    unsigned have;
+    z_stream strm;
+    unsigned char in[CHUNK];
+    unsigned char out[CHUNK];
+
+The initialization of the state is the same, except that there is no compression level, +of course, and two more elements of the structure are initialized. avail_in +and next_in must be initialized before calling inflateInit(). This +is because the application has the option to provide the start of the zlib stream in +order for inflateInit() to have access to information about the compression +method to aid in memory allocation. In the current implementation of zlib +(up through versions 1.2.x), the method-dependent memory allocations are deferred to the first call of +inflate() anyway. However those fields must be initialized since later versions +of zlib that provide more compression methods may take advantage of this interface. +In any case, no decompression is performed by inflateInit(), so the +avail_out and next_out fields do not need to be initialized before calling. +

+Here avail_in is set to zero and next_in is set to Z_NULL to +indicate that no input data is being provided. +


+    /* allocate inflate state */
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = 0;
+    strm.next_in = Z_NULL;
+    ret = inflateInit(&strm);
+    if (ret != Z_OK)
+        return ret;
+
+The outer do-loop decompresses input until inflate() indicates +that it has reached the end of the compressed data and has produced all of the uncompressed +output. This is in contrast to def() which processes all of the input file. +If end-of-file is reached before the compressed data self-terminates, then the compressed +data is incomplete and an error is returned. +

+    /* decompress until deflate stream ends or end of file */
+    do {
+
+We read input data and set the strm structure accordingly. If we've reached the +end of the input file, then we leave the outer loop and report an error, since the +compressed data is incomplete. Note that we may read more data than is eventually consumed +by inflate(), if the input file continues past the zlib stream. +For applications where zlib streams are embedded in other data, this routine would +need to be modified to return the unused data, or at least indicate how much of the input +data was not used, so the application would know where to pick up after the zlib stream. +

+        strm.avail_in = fread(in, 1, CHUNK, source);
+        if (ferror(source)) {
+            (void)inflateEnd(&strm);
+            return Z_ERRNO;
+        }
+        if (strm.avail_in == 0)
+            break;
+        strm.next_in = in;
+
+The inner do-loop has the same function it did in def(), which is to +keep calling inflate() until has generated all of the output it can with the +provided input. +

+        /* run inflate() on input until output buffer not full */
+        do {
+
+Just like in def(), the same output space is provided for each call of inflate(). +

+            strm.avail_out = CHUNK;
+            strm.next_out = out;
+
+Now we run the decompression engine itself. There is no need to adjust the flush parameter, since +the zlib format is self-terminating. The main difference here is that there are +return values that we need to pay attention to. Z_DATA_ERROR +indicates that inflate() detected an error in the zlib compressed data format, +which means that either the data is not a zlib stream to begin with, or that the data was +corrupted somewhere along the way since it was compressed. The other error to be processed is +Z_MEM_ERROR, which can occur since memory allocation is deferred until inflate() +needs it, unlike deflate(), whose memory is allocated at the start by deflateInit(). +

+Advanced applications may use +deflateSetDictionary() to prime deflate() with a set of likely data to improve the +first 32K or so of compression. This is noted in the zlib header, so inflate() +requests that that dictionary be provided before it can start to decompress. Without the dictionary, +correct decompression is not possible. For this routine, we have no idea what the dictionary is, +so the Z_NEED_DICT indication is converted to a Z_DATA_ERROR. +

+inflate() can also return Z_STREAM_ERROR, which should not be possible here, +but could be checked for as noted above for def(). Z_BUF_ERROR does not need to be +checked for here, for the same reasons noted for def(). Z_STREAM_END will be +checked for later. +


+            ret = inflate(&strm, Z_NO_FLUSH);
+            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
+            switch (ret) {
+            case Z_NEED_DICT:
+                ret = Z_DATA_ERROR;     /* and fall through */
+            case Z_DATA_ERROR:
+            case Z_MEM_ERROR:
+                (void)inflateEnd(&strm);
+                return ret;
+            }
+
+The output of inflate() is handled identically to that of deflate(). +

+            have = CHUNK - strm.avail_out;
+            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
+                (void)inflateEnd(&strm);
+                return Z_ERRNO;
+            }
+
+The inner do-loop ends when inflate() has no more output as indicated +by not filling the output buffer, just as for deflate(). In this case, we cannot +assert that strm.avail_in will be zero, since the deflate stream may end before the file +does. +

+        } while (strm.avail_out == 0);
+
+The outer do-loop ends when inflate() reports that it has reached the +end of the input zlib stream, has completed the decompression and integrity +check, and has provided all of the output. This is indicated by the inflate() +return value Z_STREAM_END. The inner loop is guaranteed to leave ret +equal to Z_STREAM_END if the last chunk of the input file read contained the end +of the zlib stream. So if the return value is not Z_STREAM_END, the +loop continues to read more input. +

+        /* done when inflate() says it's done */
+    } while (ret != Z_STREAM_END);
+
+At this point, decompression successfully completed, or we broke out of the loop due to no +more data being available from the input file. If the last inflate() return value +is not Z_STREAM_END, then the zlib stream was incomplete and a data error +is returned. Otherwise, we return with a happy return value. Of course, inflateEnd() +is called first to avoid a memory leak. +

+    /* clean up and return */
+    (void)inflateEnd(&strm);
+    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
+}
+
+That ends the routines that directly use zlib. The following routines make this +a command-line program by running data through the above routines from stdin to +stdout, and handling any errors reported by def() or inf(). +

+zerr() is used to interpret the possible error codes from def() +and inf(), as detailed in their comments above, and print out an error message. +Note that these are only a subset of the possible return values from deflate() +and inflate(). +


+/* report a zlib or i/o error */
+void zerr(int ret)
+{
+    fputs("zpipe: ", stderr);
+    switch (ret) {
+    case Z_ERRNO:
+        if (ferror(stdin))
+            fputs("error reading stdin\n", stderr);
+        if (ferror(stdout))
+            fputs("error writing stdout\n", stderr);
+        break;
+    case Z_STREAM_ERROR:
+        fputs("invalid compression level\n", stderr);
+        break;
+    case Z_DATA_ERROR:
+        fputs("invalid or incomplete deflate data\n", stderr);
+        break;
+    case Z_MEM_ERROR:
+        fputs("out of memory\n", stderr);
+        break;
+    case Z_VERSION_ERROR:
+        fputs("zlib version mismatch!\n", stderr);
+    }
+}
+
+Here is the main() routine used to test def() and inf(). The +zpipe command is simply a compression pipe from stdin to stdout, if +no arguments are given, or it is a decompression pipe if zpipe -d is used. If any other +arguments are provided, no compression or decompression is performed. Instead a usage +message is displayed. Examples are zpipe < foo.txt > foo.txt.z to compress, and +zpipe -d < foo.txt.z > foo.txt to decompress. +

+/* compress or decompress from stdin to stdout */
+int main(int argc, char **argv)
+{
+    int ret;
+
+    /* avoid end-of-line conversions */
+    SET_BINARY_MODE(stdin);
+    SET_BINARY_MODE(stdout);
+
+    /* do compression if no arguments */
+    if (argc == 1) {
+        ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
+        if (ret != Z_OK)
+            zerr(ret);
+        return ret;
+    }
+
+    /* do decompression if -d specified */
+    else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
+        ret = inf(stdin, stdout);
+        if (ret != Z_OK)
+            zerr(ret);
+        return ret;
+    }
+
+    /* otherwise, report usage */
+    else {
+        fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
+        return 1;
+    }
+}
+
+
+Copyright (c) 2004, 2005 by Mark Adler
Last modified 11 December 2005
+ + diff --git a/zlib/zlib/examples/zpipe.c b/zlib/zlib/examples/zpipe.c new file mode 100644 index 00000000..83535d16 --- /dev/null +++ b/zlib/zlib/examples/zpipe.c @@ -0,0 +1,205 @@ +/* zpipe.c: example of proper use of zlib's inflate() and deflate() + Not copyrighted -- provided to the public domain + Version 1.4 11 December 2005 Mark Adler */ + +/* Version history: + 1.0 30 Oct 2004 First version + 1.1 8 Nov 2004 Add void casting for unused return values + Use switch statement for inflate() return values + 1.2 9 Nov 2004 Add assertions to document zlib guarantees + 1.3 6 Apr 2005 Remove incorrect assertion in inf() + 1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions + Avoid some compiler warnings for input and output buffers + */ + +#include +#include +#include +#include "zlib.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define CHUNK 16384 + +/* Compress from file source to file dest until EOF on source. + def() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_STREAM_ERROR if an invalid compression + level is supplied, Z_VERSION_ERROR if the version of zlib.h and the + version of the library linked do not match, or Z_ERRNO if there is + an error reading or writing the files. */ +int def(FILE *source, FILE *dest, int level) +{ + int ret, flush; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, level); + if (ret != Z_OK) + return ret; + + /* compress until end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + + /* run deflate() on input until output buffer not full, finish + compression if all of source has been read in */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); /* no bad return value */ + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); /* all input will be used */ + + /* done when last data in file processed */ + } while (flush != Z_FINISH); + assert(ret == Z_STREAM_END); /* stream will be complete */ + + /* clean up and return */ + (void)deflateEnd(&strm); + return Z_OK; +} + +/* Decompress from file source to file dest until stream ends or EOF. + inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_DATA_ERROR if the deflate data is + invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and + the version of the library linked do not match, or Z_ERRNO if there + is an error reading or writing the files. */ +int inf(FILE *source, FILE *dest) +{ + int ret; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); + if (ret != Z_OK) + return ret; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + if (strm.avail_in == 0) + break; + strm.next_in = in; + + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} + +/* report a zlib or i/o error */ +void zerr(int ret) +{ + fputs("zpipe: ", stderr); + switch (ret) { + case Z_ERRNO: + if (ferror(stdin)) + fputs("error reading stdin\n", stderr); + if (ferror(stdout)) + fputs("error writing stdout\n", stderr); + break; + case Z_STREAM_ERROR: + fputs("invalid compression level\n", stderr); + break; + case Z_DATA_ERROR: + fputs("invalid or incomplete deflate data\n", stderr); + break; + case Z_MEM_ERROR: + fputs("out of memory\n", stderr); + break; + case Z_VERSION_ERROR: + fputs("zlib version mismatch!\n", stderr); + } +} + +/* compress or decompress from stdin to stdout */ +int main(int argc, char **argv) +{ + int ret; + + /* avoid end-of-line conversions */ + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + + /* do compression if no arguments */ + if (argc == 1) { + ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* do decompression if -d specified */ + else if (argc == 2 && strcmp(argv[1], "-d") == 0) { + ret = inf(stdin, stdout); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* otherwise, report usage */ + else { + fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr); + return 1; + } +} diff --git a/zlib/zlib/examples/zran.c b/zlib/zlib/examples/zran.c new file mode 100644 index 00000000..278f9ad0 --- /dev/null +++ b/zlib/zlib/examples/zran.c @@ -0,0 +1,409 @@ +/* zran.c -- example of zlib/gzip stream indexing and random access + * Copyright (C) 2005, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + Version 1.1 29 Sep 2012 Mark Adler */ + +/* Version History: + 1.0 29 May 2005 First version + 1.1 29 Sep 2012 Fix memory reallocation error + */ + +/* Illustrate the use of Z_BLOCK, inflatePrime(), and inflateSetDictionary() + for random access of a compressed file. A file containing a zlib or gzip + stream is provided on the command line. The compressed stream is decoded in + its entirety, and an index built with access points about every SPAN bytes + in the uncompressed output. The compressed file is left open, and can then + be read randomly, having to decompress on the average SPAN/2 uncompressed + bytes before getting to the desired block of data. + + An access point can be created at the start of any deflate block, by saving + the starting file offset and bit of that block, and the 32K bytes of + uncompressed data that precede that block. Also the uncompressed offset of + that block is saved to provide a referece for locating a desired starting + point in the uncompressed stream. build_index() works by decompressing the + input zlib or gzip stream a block at a time, and at the end of each block + deciding if enough uncompressed data has gone by to justify the creation of + a new access point. If so, that point is saved in a data structure that + grows as needed to accommodate the points. + + To use the index, an offset in the uncompressed data is provided, for which + the latest accees point at or preceding that offset is located in the index. + The input file is positioned to the specified location in the index, and if + necessary the first few bits of the compressed data is read from the file. + inflate is initialized with those bits and the 32K of uncompressed data, and + the decompression then proceeds until the desired offset in the file is + reached. Then the decompression continues to read the desired uncompressed + data from the file. + + Another approach would be to generate the index on demand. In that case, + requests for random access reads from the compressed data would try to use + the index, but if a read far enough past the end of the index is required, + then further index entries would be generated and added. + + There is some fair bit of overhead to starting inflation for the random + access, mainly copying the 32K byte dictionary. So if small pieces of the + file are being accessed, it would make sense to implement a cache to hold + some lookahead and avoid many calls to extract() for small lengths. + + Another way to build an index would be to use inflateCopy(). That would + not be constrained to have access points at block boundaries, but requires + more memory per access point, and also cannot be saved to file due to the + use of pointers in the state. The approach here allows for storage of the + index in a file. + */ + +#include +#include +#include +#include "zlib.h" + +#define local static + +#define SPAN 1048576L /* desired distance between access points */ +#define WINSIZE 32768U /* sliding window size */ +#define CHUNK 16384 /* file input buffer size */ + +/* access point entry */ +struct point { + off_t out; /* corresponding offset in uncompressed data */ + off_t in; /* offset in input file of first full byte */ + int bits; /* number of bits (1-7) from byte at in - 1, or 0 */ + unsigned char window[WINSIZE]; /* preceding 32K of uncompressed data */ +}; + +/* access point list */ +struct access { + int have; /* number of list entries filled in */ + int size; /* number of list entries allocated */ + struct point *list; /* allocated list */ +}; + +/* Deallocate an index built by build_index() */ +local void free_index(struct access *index) +{ + if (index != NULL) { + free(index->list); + free(index); + } +} + +/* Add an entry to the access point list. If out of memory, deallocate the + existing list and return NULL. */ +local struct access *addpoint(struct access *index, int bits, + off_t in, off_t out, unsigned left, unsigned char *window) +{ + struct point *next; + + /* if list is empty, create it (start with eight points) */ + if (index == NULL) { + index = malloc(sizeof(struct access)); + if (index == NULL) return NULL; + index->list = malloc(sizeof(struct point) << 3); + if (index->list == NULL) { + free(index); + return NULL; + } + index->size = 8; + index->have = 0; + } + + /* if list is full, make it bigger */ + else if (index->have == index->size) { + index->size <<= 1; + next = realloc(index->list, sizeof(struct point) * index->size); + if (next == NULL) { + free_index(index); + return NULL; + } + index->list = next; + } + + /* fill in entry and increment how many we have */ + next = index->list + index->have; + next->bits = bits; + next->in = in; + next->out = out; + if (left) + memcpy(next->window, window + WINSIZE - left, left); + if (left < WINSIZE) + memcpy(next->window + left, window, WINSIZE - left); + index->have++; + + /* return list, possibly reallocated */ + return index; +} + +/* Make one entire pass through the compressed stream and build an index, with + access points about every span bytes of uncompressed output -- span is + chosen to balance the speed of random access against the memory requirements + of the list, about 32K bytes per access point. Note that data after the end + of the first zlib or gzip stream in the file is ignored. build_index() + returns the number of access points on success (>= 1), Z_MEM_ERROR for out + of memory, Z_DATA_ERROR for an error in the input file, or Z_ERRNO for a + file read error. On success, *built points to the resulting index. */ +local int build_index(FILE *in, off_t span, struct access **built) +{ + int ret; + off_t totin, totout; /* our own total counters to avoid 4GB limit */ + off_t last; /* totout value of last access point */ + struct access *index; /* access points being generated */ + z_stream strm; + unsigned char input[CHUNK]; + unsigned char window[WINSIZE]; + + /* initialize inflate */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, 47); /* automatic zlib or gzip decoding */ + if (ret != Z_OK) + return ret; + + /* inflate the input, maintain a sliding window, and build an index -- this + also validates the integrity of the compressed data using the check + information at the end of the gzip or zlib stream */ + totin = totout = last = 0; + index = NULL; /* will be allocated by first addpoint() */ + strm.avail_out = 0; + do { + /* get some compressed data from input file */ + strm.avail_in = fread(input, 1, CHUNK, in); + if (ferror(in)) { + ret = Z_ERRNO; + goto build_index_error; + } + if (strm.avail_in == 0) { + ret = Z_DATA_ERROR; + goto build_index_error; + } + strm.next_in = input; + + /* process all of that, or until end of stream */ + do { + /* reset sliding window if necessary */ + if (strm.avail_out == 0) { + strm.avail_out = WINSIZE; + strm.next_out = window; + } + + /* inflate until out of input, output, or at end of block -- + update the total input and output counters */ + totin += strm.avail_in; + totout += strm.avail_out; + ret = inflate(&strm, Z_BLOCK); /* return at end of block */ + totin -= strm.avail_in; + totout -= strm.avail_out; + if (ret == Z_NEED_DICT) + ret = Z_DATA_ERROR; + if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) + goto build_index_error; + if (ret == Z_STREAM_END) + break; + + /* if at end of block, consider adding an index entry (note that if + data_type indicates an end-of-block, then all of the + uncompressed data from that block has been delivered, and none + of the compressed data after that block has been consumed, + except for up to seven bits) -- the totout == 0 provides an + entry point after the zlib or gzip header, and assures that the + index always has at least one access point; we avoid creating an + access point after the last block by checking bit 6 of data_type + */ + if ((strm.data_type & 128) && !(strm.data_type & 64) && + (totout == 0 || totout - last > span)) { + index = addpoint(index, strm.data_type & 7, totin, + totout, strm.avail_out, window); + if (index == NULL) { + ret = Z_MEM_ERROR; + goto build_index_error; + } + last = totout; + } + } while (strm.avail_in != 0); + } while (ret != Z_STREAM_END); + + /* clean up and return index (release unused entries in list) */ + (void)inflateEnd(&strm); + index->list = realloc(index->list, sizeof(struct point) * index->have); + index->size = index->have; + *built = index; + return index->size; + + /* return error */ + build_index_error: + (void)inflateEnd(&strm); + if (index != NULL) + free_index(index); + return ret; +} + +/* Use the index to read len bytes from offset into buf, return bytes read or + negative for error (Z_DATA_ERROR or Z_MEM_ERROR). If data is requested past + the end of the uncompressed data, then extract() will return a value less + than len, indicating how much as actually read into buf. This function + should not return a data error unless the file was modified since the index + was generated. extract() may also return Z_ERRNO if there is an error on + reading or seeking the input file. */ +local int extract(FILE *in, struct access *index, off_t offset, + unsigned char *buf, int len) +{ + int ret, skip; + z_stream strm; + struct point *here; + unsigned char input[CHUNK]; + unsigned char discard[WINSIZE]; + + /* proceed only if something reasonable to do */ + if (len < 0) + return 0; + + /* find where in stream to start */ + here = index->list; + ret = index->have; + while (--ret && here[1].out <= offset) + here++; + + /* initialize file and inflate state to start there */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -15); /* raw inflate */ + if (ret != Z_OK) + return ret; + ret = fseeko(in, here->in - (here->bits ? 1 : 0), SEEK_SET); + if (ret == -1) + goto extract_ret; + if (here->bits) { + ret = getc(in); + if (ret == -1) { + ret = ferror(in) ? Z_ERRNO : Z_DATA_ERROR; + goto extract_ret; + } + (void)inflatePrime(&strm, here->bits, ret >> (8 - here->bits)); + } + (void)inflateSetDictionary(&strm, here->window, WINSIZE); + + /* skip uncompressed bytes until offset reached, then satisfy request */ + offset -= here->out; + strm.avail_in = 0; + skip = 1; /* while skipping to offset */ + do { + /* define where to put uncompressed data, and how much */ + if (offset == 0 && skip) { /* at offset now */ + strm.avail_out = len; + strm.next_out = buf; + skip = 0; /* only do this once */ + } + if (offset > WINSIZE) { /* skip WINSIZE bytes */ + strm.avail_out = WINSIZE; + strm.next_out = discard; + offset -= WINSIZE; + } + else if (offset != 0) { /* last skip */ + strm.avail_out = (unsigned)offset; + strm.next_out = discard; + offset = 0; + } + + /* uncompress until avail_out filled, or end of stream */ + do { + if (strm.avail_in == 0) { + strm.avail_in = fread(input, 1, CHUNK, in); + if (ferror(in)) { + ret = Z_ERRNO; + goto extract_ret; + } + if (strm.avail_in == 0) { + ret = Z_DATA_ERROR; + goto extract_ret; + } + strm.next_in = input; + } + ret = inflate(&strm, Z_NO_FLUSH); /* normal inflate */ + if (ret == Z_NEED_DICT) + ret = Z_DATA_ERROR; + if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) + goto extract_ret; + if (ret == Z_STREAM_END) + break; + } while (strm.avail_out != 0); + + /* if reach end of stream, then don't keep trying to get more */ + if (ret == Z_STREAM_END) + break; + + /* do until offset reached and requested data read, or stream ends */ + } while (skip); + + /* compute number of uncompressed bytes read after offset */ + ret = skip ? 0 : len - strm.avail_out; + + /* clean up and return bytes read or error */ + extract_ret: + (void)inflateEnd(&strm); + return ret; +} + +/* Demonstrate the use of build_index() and extract() by processing the file + provided on the command line, and the extracting 16K from about 2/3rds of + the way through the uncompressed output, and writing that to stdout. */ +int main(int argc, char **argv) +{ + int len; + off_t offset; + FILE *in; + struct access *index = NULL; + unsigned char buf[CHUNK]; + + /* open input file */ + if (argc != 2) { + fprintf(stderr, "usage: zran file.gz\n"); + return 1; + } + in = fopen(argv[1], "rb"); + if (in == NULL) { + fprintf(stderr, "zran: could not open %s for reading\n", argv[1]); + return 1; + } + + /* build index */ + len = build_index(in, SPAN, &index); + if (len < 0) { + fclose(in); + switch (len) { + case Z_MEM_ERROR: + fprintf(stderr, "zran: out of memory\n"); + break; + case Z_DATA_ERROR: + fprintf(stderr, "zran: compressed data error in %s\n", argv[1]); + break; + case Z_ERRNO: + fprintf(stderr, "zran: read error on %s\n", argv[1]); + break; + default: + fprintf(stderr, "zran: error %d while building index\n", len); + } + return 1; + } + fprintf(stderr, "zran: built index with %d access points\n", len); + + /* use index by reading some bytes from an arbitrary offset */ + offset = (index->list[index->have - 1].out << 1) / 3; + len = extract(in, index, offset, buf, CHUNK); + if (len < 0) + fprintf(stderr, "zran: extraction failed: %s error\n", + len == Z_MEM_ERROR ? "out of memory" : "input corrupted"); + else { + fwrite(buf, 1, len, stdout); + fprintf(stderr, "zran: extracted %d bytes at %llu\n", len, offset); + } + + /* clean up and exit */ + free_index(index); + fclose(in); + return 0; +} diff --git a/zlib/zlib/gzclose.c b/zlib/zlib/gzclose.c new file mode 100644 index 00000000..caeb99a3 --- /dev/null +++ b/zlib/zlib/gzclose.c @@ -0,0 +1,25 @@ +/* gzclose.c -- zlib gzclose() function + * Copyright (C) 2004, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* gzclose() is in a separate file so that it is linked in only if it is used. + That way the other gzclose functions can be used instead to avoid linking in + unneeded compression or decompression routines. */ +int ZEXPORT gzclose(file) + gzFile file; +{ +#ifndef NO_GZCOMPRESS + gz_statep state; + + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file); +#else + return gzclose_r(file); +#endif +} diff --git a/zlib/zlib/gzguts.h b/zlib/zlib/gzguts.h new file mode 100644 index 00000000..d87659d0 --- /dev/null +++ b/zlib/zlib/gzguts.h @@ -0,0 +1,209 @@ +/* gzguts.h -- zlib internal header definitions for gz* operations + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#ifdef _LARGEFILE64_SOURCE +# ifndef _LARGEFILE_SOURCE +# define _LARGEFILE_SOURCE 1 +# endif +# ifdef _FILE_OFFSET_BITS +# undef _FILE_OFFSET_BITS +# endif +#endif + +#ifdef HAVE_HIDDEN +# define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) +#else +# define ZLIB_INTERNAL +#endif + +#include +#include "zlib.h" +#ifdef STDC +# include +# include +# include +#endif +#include + +#ifdef _WIN32 +# include +#endif + +#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32) +# include +#endif + +#ifdef WINAPI_FAMILY +# define open _open +# define read _read +# define write _write +# define close _close +#endif + +#ifdef NO_DEFLATE /* for compatibility with old definition */ +# define NO_GZCOMPRESS +#endif + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS +/* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 +/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) +# define vsnprintf _vsnprintf +# endif +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +# ifdef VMS +# define NO_vsnprintf +# endif +# ifdef __OS400__ +# define NO_vsnprintf +# endif +# ifdef __MVS__ +# define NO_vsnprintf +# endif +#endif + +/* unlike snprintf (which is required in C99, yet still not supported by + Microsoft more than a decade later!), _snprintf does not guarantee null + termination of the result -- however this is only used in gzlib.c where + the result is assured to fit in the space provided */ +#ifdef _MSC_VER +# define snprintf _snprintf +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +/* gz* functions always use library allocation functions */ +#ifndef STDC + extern voidp malloc OF((uInt size)); + extern void free OF((voidpf ptr)); +#endif + +/* get errno and strerror definition */ +#if defined UNDER_CE +# include +# define zstrerror() gz_strwinerror((DWORD)GetLastError()) +#else +# ifndef NO_STRERROR +# include +# define zstrerror() strerror(errno) +# else +# define zstrerror() "stdio error (consult errno)" +# endif +#endif + +/* provide prototypes for these when building zlib without LFS */ +#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); + ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); +#endif + +/* default memLevel */ +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif + +/* default i/o buffer size -- double this for output when reading (this and + twice this must be able to fit in an unsigned type) */ +#define GZBUFSIZE 8192 + +/* gzip modes, also provide a little integrity check on the passed structure */ +#define GZ_NONE 0 +#define GZ_READ 7247 +#define GZ_WRITE 31153 +#define GZ_APPEND 1 /* mode set to GZ_WRITE after the file is opened */ + +/* values for gz_state how */ +#define LOOK 0 /* look for a gzip header */ +#define COPY 1 /* copy input directly */ +#define GZIP 2 /* decompress a gzip stream */ + +/* internal gzip file state data structure */ +typedef struct { + /* exposed contents for gzgetc() macro */ + struct gzFile_s x; /* "x" for exposed */ + /* x.have: number of bytes available at x.next */ + /* x.next: next output data to deliver or write */ + /* x.pos: current position in uncompressed data */ + /* used for both reading and writing */ + int mode; /* see gzip modes above */ + int fd; /* file descriptor */ + char *path; /* path or fd for error messages */ + unsigned size; /* buffer size, zero if not allocated yet */ + unsigned want; /* requested buffer size, default is GZBUFSIZE */ + unsigned char *in; /* input buffer */ + unsigned char *out; /* output buffer (double-sized when reading) */ + int direct; /* 0 if processing gzip, 1 if transparent */ + /* just for reading */ + int how; /* 0: get header, 1: copy, 2: decompress */ + z_off64_t start; /* where the gzip data started, for rewinding */ + int eof; /* true if end of input file reached */ + int past; /* true if read requested past end */ + /* just for writing */ + int level; /* compression level */ + int strategy; /* compression strategy */ + /* seek request */ + z_off64_t skip; /* amount to skip (already rewound if backwards) */ + int seek; /* true if seek request pending */ + /* error information */ + int err; /* error code */ + char *msg; /* error message */ + /* zlib inflate or deflate stream */ + z_stream strm; /* stream structure in-place (not a pointer) */ +} gz_state; +typedef gz_state FAR *gz_statep; + +/* shared functions */ +void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *)); +#if defined UNDER_CE +char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error)); +#endif + +/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t + value -- needed when comparing unsigned to z_off64_t, which is signed + (possible z_off64_t types off_t, off64_t, and long are all signed) */ +#ifdef INT_MAX +# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) +#else +unsigned ZLIB_INTERNAL gz_intmax OF((void)); +# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) +#endif diff --git a/zlib/zlib/gzlib.c b/zlib/zlib/gzlib.c new file mode 100644 index 00000000..fae202ef --- /dev/null +++ b/zlib/zlib/gzlib.c @@ -0,0 +1,634 @@ +/* gzlib.c -- zlib functions common to reading and writing gzip files + * Copyright (C) 2004, 2010, 2011, 2012, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +#if defined(_WIN32) && !defined(__BORLANDC__) +# define LSEEK _lseeki64 +#else +#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +# define LSEEK lseek64 +#else +# define LSEEK lseek +#endif +#endif + +/* Local functions */ +local void gz_reset OF((gz_statep)); +local gzFile gz_open OF((const void *, int, const char *)); + +#if defined UNDER_CE + +/* Map the Windows error number in ERROR to a locale-dependent error message + string and return a pointer to it. Typically, the values for ERROR come + from GetLastError. + + The string pointed to shall not be modified by the application, but may be + overwritten by a subsequent call to gz_strwinerror + + The gz_strwinerror function does not change the current setting of + GetLastError. */ +char ZLIB_INTERNAL *gz_strwinerror (error) + DWORD error; +{ + static char buf[1024]; + + wchar_t *msgbuf; + DWORD lasterr = GetLastError(); + DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + error, + 0, /* Default language */ + (LPVOID)&msgbuf, + 0, + NULL); + if (chars != 0) { + /* If there is an \r\n appended, zap it. */ + if (chars >= 2 + && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') { + chars -= 2; + msgbuf[chars] = 0; + } + + if (chars > sizeof (buf) - 1) { + chars = sizeof (buf) - 1; + msgbuf[chars] = 0; + } + + wcstombs(buf, msgbuf, chars + 1); + LocalFree(msgbuf); + } + else { + sprintf(buf, "unknown win32 error (%ld)", error); + } + + SetLastError(lasterr); + return buf; +} + +#endif /* UNDER_CE */ + +/* Reset gzip file state */ +local void gz_reset(state) + gz_statep state; +{ + state->x.have = 0; /* no output data available */ + if (state->mode == GZ_READ) { /* for reading ... */ + state->eof = 0; /* not at end of file */ + state->past = 0; /* have not read past end yet */ + state->how = LOOK; /* look for gzip header */ + } + state->seek = 0; /* no seek request pending */ + gz_error(state, Z_OK, NULL); /* clear error */ + state->x.pos = 0; /* no uncompressed data yet */ + state->strm.avail_in = 0; /* no input data yet */ +} + +/* Open a gzip file either by name or file descriptor. */ +local gzFile gz_open(path, fd, mode) + const void *path; + int fd; + const char *mode; +{ + gz_statep state; + size_t len; + int oflag; +#ifdef O_CLOEXEC + int cloexec = 0; +#endif +#ifdef O_EXCL + int exclusive = 0; +#endif + + /* check input */ + if (path == NULL) + return NULL; + + /* allocate gzFile structure to return */ + state = (gz_statep)malloc(sizeof(gz_state)); + if (state == NULL) + return NULL; + state->size = 0; /* no buffers allocated yet */ + state->want = GZBUFSIZE; /* requested buffer size */ + state->msg = NULL; /* no error message yet */ + + /* interpret mode */ + state->mode = GZ_NONE; + state->level = Z_DEFAULT_COMPRESSION; + state->strategy = Z_DEFAULT_STRATEGY; + state->direct = 0; + while (*mode) { + if (*mode >= '0' && *mode <= '9') + state->level = *mode - '0'; + else + switch (*mode) { + case 'r': + state->mode = GZ_READ; + break; +#ifndef NO_GZCOMPRESS + case 'w': + state->mode = GZ_WRITE; + break; + case 'a': + state->mode = GZ_APPEND; + break; +#endif + case '+': /* can't read and write at the same time */ + free(state); + return NULL; + case 'b': /* ignore -- will request binary anyway */ + break; +#ifdef O_CLOEXEC + case 'e': + cloexec = 1; + break; +#endif +#ifdef O_EXCL + case 'x': + exclusive = 1; + break; +#endif + case 'f': + state->strategy = Z_FILTERED; + break; + case 'h': + state->strategy = Z_HUFFMAN_ONLY; + break; + case 'R': + state->strategy = Z_RLE; + break; + case 'F': + state->strategy = Z_FIXED; + break; + case 'T': + state->direct = 1; + break; + default: /* could consider as an error, but just ignore */ + ; + } + mode++; + } + + /* must provide an "r", "w", or "a" */ + if (state->mode == GZ_NONE) { + free(state); + return NULL; + } + + /* can't force transparent read */ + if (state->mode == GZ_READ) { + if (state->direct) { + free(state); + return NULL; + } + state->direct = 1; /* for empty file */ + } + + /* save the path name for error messages */ +#ifdef _WIN32 + if (fd == -2) { + len = wcstombs(NULL, path, 0); + if (len == (size_t)-1) + len = 0; + } + else +#endif + len = strlen((const char *)path); + state->path = (char *)malloc(len + 1); + if (state->path == NULL) { + free(state); + return NULL; + } +#ifdef _WIN32 + if (fd == -2) + if (len) + wcstombs(state->path, path, len + 1); + else + *(state->path) = 0; + else +#endif +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->path, len + 1, "%s", (const char *)path); +#else + strcpy(state->path, path); +#endif + + /* compute the flags for open() */ + oflag = +#ifdef O_LARGEFILE + O_LARGEFILE | +#endif +#ifdef O_BINARY + O_BINARY | +#endif +#ifdef O_CLOEXEC + (cloexec ? O_CLOEXEC : 0) | +#endif + (state->mode == GZ_READ ? + O_RDONLY : + (O_WRONLY | O_CREAT | +#ifdef O_EXCL + (exclusive ? O_EXCL : 0) | +#endif + (state->mode == GZ_WRITE ? + O_TRUNC : + O_APPEND))); + + /* open the file with the appropriate flags (or just use fd) */ + state->fd = fd > -1 ? fd : ( +#ifdef _WIN32 + fd == -2 ? _wopen(path, oflag, 0666) : +#endif + open((const char *)path, oflag, 0666)); + if (state->fd == -1) { + free(state->path); + free(state); + return NULL; + } + if (state->mode == GZ_APPEND) + state->mode = GZ_WRITE; /* simplify later checks */ + + /* save the current position for rewinding (only if reading) */ + if (state->mode == GZ_READ) { + state->start = LSEEK(state->fd, 0, SEEK_CUR); + if (state->start == -1) state->start = 0; + } + + /* initialize stream */ + gz_reset(state); + + /* return stream */ + return (gzFile)state; +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzopen(path, mode) + const char *path; + const char *mode; +{ + return gz_open(path, -1, mode); +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzopen64(path, mode) + const char *path; + const char *mode; +{ + return gz_open(path, -1, mode); +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzdopen(fd, mode) + int fd; + const char *mode; +{ + char *path; /* identifier for error messages */ + gzFile gz; + + if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL) + return NULL; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(path, 7 + 3 * sizeof(int), "", fd); /* for debugging */ +#else + sprintf(path, "", fd); /* for debugging */ +#endif + gz = gz_open(path, fd, mode); + free(path); + return gz; +} + +/* -- see zlib.h -- */ +#ifdef _WIN32 +gzFile ZEXPORT gzopen_w(path, mode) + const wchar_t *path; + const char *mode; +{ + return gz_open(path, -2, mode); +} +#endif + +/* -- see zlib.h -- */ +int ZEXPORT gzbuffer(file, size) + gzFile file; + unsigned size; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* make sure we haven't already allocated memory */ + if (state->size != 0) + return -1; + + /* check and set requested size */ + if (size < 2) + size = 2; /* need two bytes to check magic header */ + state->want = size; + return 0; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzrewind(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* back up and start over */ + if (LSEEK(state->fd, state->start, SEEK_SET) == -1) + return -1; + gz_reset(state); + return 0; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gzseek64(file, offset, whence) + gzFile file; + z_off64_t offset; + int whence; +{ + unsigned n; + z_off64_t ret; + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* check that there's no error */ + if (state->err != Z_OK && state->err != Z_BUF_ERROR) + return -1; + + /* can only seek from start or relative to current position */ + if (whence != SEEK_SET && whence != SEEK_CUR) + return -1; + + /* normalize offset to a SEEK_CUR specification */ + if (whence == SEEK_SET) + offset -= state->x.pos; + else if (state->seek) + offset += state->skip; + state->seek = 0; + + /* if within raw area while reading, just go there */ + if (state->mode == GZ_READ && state->how == COPY && + state->x.pos + offset >= 0) { + ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR); + if (ret == -1) + return -1; + state->x.have = 0; + state->eof = 0; + state->past = 0; + state->seek = 0; + gz_error(state, Z_OK, NULL); + state->strm.avail_in = 0; + state->x.pos += offset; + return state->x.pos; + } + + /* calculate skip amount, rewinding if needed for back seek when reading */ + if (offset < 0) { + if (state->mode != GZ_READ) /* writing -- can't go backwards */ + return -1; + offset += state->x.pos; + if (offset < 0) /* before start of file! */ + return -1; + if (gzrewind(file) == -1) /* rewind, then skip to offset */ + return -1; + } + + /* if reading, skip what's in output buffer (one less gzgetc() check) */ + if (state->mode == GZ_READ) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ? + (unsigned)offset : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + offset -= n; + } + + /* request skip (if not zero) */ + if (offset) { + state->seek = 1; + state->skip = offset; + } + return state->x.pos + offset; +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gzseek(file, offset, whence) + gzFile file; + z_off_t offset; + int whence; +{ + z_off64_t ret; + + ret = gzseek64(file, (z_off64_t)offset, whence); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gztell64(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* return position */ + return state->x.pos + (state->seek ? state->skip : 0); +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gztell(file) + gzFile file; +{ + z_off64_t ret; + + ret = gztell64(file); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gzoffset64(file) + gzFile file; +{ + z_off64_t offset; + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* compute and return effective offset in file */ + offset = LSEEK(state->fd, 0, SEEK_CUR); + if (offset == -1) + return -1; + if (state->mode == GZ_READ) /* reading */ + offset -= state->strm.avail_in; /* don't count buffered input */ + return offset; +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gzoffset(file) + gzFile file; +{ + z_off64_t ret; + + ret = gzoffset64(file); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzeof(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return 0; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return 0; + + /* return end-of-file state */ + return state->mode == GZ_READ ? state->past : 0; +} + +/* -- see zlib.h -- */ +const char * ZEXPORT gzerror(file, errnum) + gzFile file; + int *errnum; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return NULL; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return NULL; + + /* return error information */ + if (errnum != NULL) + *errnum = state->err; + return state->err == Z_MEM_ERROR ? "out of memory" : + (state->msg == NULL ? "" : state->msg); +} + +/* -- see zlib.h -- */ +void ZEXPORT gzclearerr(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return; + + /* clear error and end-of-file */ + if (state->mode == GZ_READ) { + state->eof = 0; + state->past = 0; + } + gz_error(state, Z_OK, NULL); +} + +/* Create an error message in allocated memory and set state->err and + state->msg accordingly. Free any previous error message already there. Do + not try to free or allocate space if the error is Z_MEM_ERROR (out of + memory). Simply save the error message as a static string. If there is an + allocation failure constructing the error message, then convert the error to + out of memory. */ +void ZLIB_INTERNAL gz_error(state, err, msg) + gz_statep state; + int err; + const char *msg; +{ + /* free previously allocated message and clear */ + if (state->msg != NULL) { + if (state->err != Z_MEM_ERROR) + free(state->msg); + state->msg = NULL; + } + + /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */ + if (err != Z_OK && err != Z_BUF_ERROR) + state->x.have = 0; + + /* set error code, and if no message, then done */ + state->err = err; + if (msg == NULL) + return; + + /* for an out of memory error, return literal string when requested */ + if (err == Z_MEM_ERROR) + return; + + /* construct error message with path */ + if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) == + NULL) { + state->err = Z_MEM_ERROR; + return; + } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->msg, strlen(state->path) + strlen(msg) + 3, + "%s%s%s", state->path, ": ", msg); +#else + strcpy(state->msg, state->path); + strcat(state->msg, ": "); + strcat(state->msg, msg); +#endif + return; +} + +#ifndef INT_MAX +/* portably return maximum value for an int (when limits.h presumed not + available) -- we need to do this to cover cases where 2's complement not + used, since C standard permits 1's complement and sign-bit representations, + otherwise we could just use ((unsigned)-1) >> 1 */ +unsigned ZLIB_INTERNAL gz_intmax() +{ + unsigned p, q; + + p = 1; + do { + q = p; + p <<= 1; + p++; + } while (p > q); + return q >> 1; +} +#endif diff --git a/zlib/zlib/gzread.c b/zlib/zlib/gzread.c new file mode 100644 index 00000000..bf4538eb --- /dev/null +++ b/zlib/zlib/gzread.c @@ -0,0 +1,594 @@ +/* gzread.c -- zlib functions for reading gzip files + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* Local functions */ +local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); +local int gz_avail OF((gz_statep)); +local int gz_look OF((gz_statep)); +local int gz_decomp OF((gz_statep)); +local int gz_fetch OF((gz_statep)); +local int gz_skip OF((gz_statep, z_off64_t)); + +/* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from + state->fd, and update state->eof, state->err, and state->msg as appropriate. + This function needs to loop on read(), since read() is not guaranteed to + read the number of bytes requested, depending on the type of descriptor. */ +local int gz_load(state, buf, len, have) + gz_statep state; + unsigned char *buf; + unsigned len; + unsigned *have; +{ + int ret; + + *have = 0; + do { + ret = read(state->fd, buf + *have, len - *have); + if (ret <= 0) + break; + *have += ret; + } while (*have < len); + if (ret < 0) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + if (ret == 0) + state->eof = 1; + return 0; +} + +/* Load up input buffer and set eof flag if last data loaded -- return -1 on + error, 0 otherwise. Note that the eof flag is set when the end of the input + file is reached, even though there may be unused data in the buffer. Once + that data has been used, no more attempts will be made to read the file. + If strm->avail_in != 0, then the current data is moved to the beginning of + the input buffer, and then the remainder of the buffer is loaded with the + available data from the input file. */ +local int gz_avail(state) + gz_statep state; +{ + unsigned got; + z_streamp strm = &(state->strm); + + if (state->err != Z_OK && state->err != Z_BUF_ERROR) + return -1; + if (state->eof == 0) { + if (strm->avail_in) { /* copy what's there to the start */ + unsigned char *p = state->in; + unsigned const char *q = strm->next_in; + unsigned n = strm->avail_in; + do { + *p++ = *q++; + } while (--n); + } + if (gz_load(state, state->in + strm->avail_in, + state->size - strm->avail_in, &got) == -1) + return -1; + strm->avail_in += got; + strm->next_in = state->in; + } + return 0; +} + +/* Look for gzip header, set up for inflate or copy. state->x.have must be 0. + If this is the first time in, allocate required memory. state->how will be + left unchanged if there is no more input data available, will be set to COPY + if there is no gzip header and direct copying will be performed, or it will + be set to GZIP for decompression. If direct copying, then leftover input + data from the input buffer will be copied to the output buffer. In that + case, all further file reads will be directly to either the output buffer or + a user buffer. If decompressing, the inflate state will be initialized. + gz_look() will return 0 on success or -1 on failure. */ +local int gz_look(state) + gz_statep state; +{ + z_streamp strm = &(state->strm); + + /* allocate read buffers and inflate memory */ + if (state->size == 0) { + /* allocate buffers */ + state->in = (unsigned char *)malloc(state->want); + state->out = (unsigned char *)malloc(state->want << 1); + if (state->in == NULL || state->out == NULL) { + if (state->out != NULL) + free(state->out); + if (state->in != NULL) + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + state->size = state->want; + + /* allocate inflate memory */ + state->strm.zalloc = Z_NULL; + state->strm.zfree = Z_NULL; + state->strm.opaque = Z_NULL; + state->strm.avail_in = 0; + state->strm.next_in = Z_NULL; + if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */ + free(state->out); + free(state->in); + state->size = 0; + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + } + + /* get at least the magic bytes in the input buffer */ + if (strm->avail_in < 2) { + if (gz_avail(state) == -1) + return -1; + if (strm->avail_in == 0) + return 0; + } + + /* look for gzip magic bytes -- if there, do gzip decoding (note: there is + a logical dilemma here when considering the case of a partially written + gzip file, to wit, if a single 31 byte is written, then we cannot tell + whether this is a single-byte file, or just a partially written gzip + file -- for here we assume that if a gzip file is being written, then + the header will be written in a single operation, so that reading a + single byte is sufficient indication that it is not a gzip file) */ + if (strm->avail_in > 1 && + strm->next_in[0] == 31 && strm->next_in[1] == 139) { + inflateReset(strm); + state->how = GZIP; + state->direct = 0; + return 0; + } + + /* no gzip header -- if we were decoding gzip before, then this is trailing + garbage. Ignore the trailing garbage and finish. */ + if (state->direct == 0) { + strm->avail_in = 0; + state->eof = 1; + state->x.have = 0; + return 0; + } + + /* doing raw i/o, copy any leftover input to output -- this assumes that + the output buffer is larger than the input buffer, which also assures + space for gzungetc() */ + state->x.next = state->out; + if (strm->avail_in) { + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; + strm->avail_in = 0; + } + state->how = COPY; + state->direct = 1; + return 0; +} + +/* Decompress from input to the provided next_out and avail_out in the state. + On return, state->x.have and state->x.next point to the just decompressed + data. If the gzip stream completes, state->how is reset to LOOK to look for + the next gzip stream or raw data, once state->x.have is depleted. Returns 0 + on success, -1 on failure. */ +local int gz_decomp(state) + gz_statep state; +{ + int ret = Z_OK; + unsigned had; + z_streamp strm = &(state->strm); + + /* fill output buffer up to end of deflate stream */ + had = strm->avail_out; + do { + /* get more input for inflate() */ + if (strm->avail_in == 0 && gz_avail(state) == -1) + return -1; + if (strm->avail_in == 0) { + gz_error(state, Z_BUF_ERROR, "unexpected end of file"); + break; + } + + /* decompress and handle errors */ + ret = inflate(strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) { + gz_error(state, Z_STREAM_ERROR, + "internal error: inflate stream corrupt"); + return -1; + } + if (ret == Z_MEM_ERROR) { + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + if (ret == Z_DATA_ERROR) { /* deflate stream invalid */ + gz_error(state, Z_DATA_ERROR, + strm->msg == NULL ? "compressed data error" : strm->msg); + return -1; + } + } while (strm->avail_out && ret != Z_STREAM_END); + + /* update available output */ + state->x.have = had - strm->avail_out; + state->x.next = strm->next_out - state->x.have; + + /* if the gzip stream completed successfully, look for another */ + if (ret == Z_STREAM_END) + state->how = LOOK; + + /* good decompression */ + return 0; +} + +/* Fetch data and put it in the output buffer. Assumes state->x.have is 0. + Data is either copied from the input file or decompressed from the input + file depending on state->how. If state->how is LOOK, then a gzip header is + looked for to determine whether to copy or decompress. Returns -1 on error, + otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the + end of the input file has been reached and all data has been processed. */ +local int gz_fetch(state) + gz_statep state; +{ + z_streamp strm = &(state->strm); + + do { + switch(state->how) { + case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */ + if (gz_look(state) == -1) + return -1; + if (state->how == LOOK) + return 0; + break; + case COPY: /* -> COPY */ + if (gz_load(state, state->out, state->size << 1, &(state->x.have)) + == -1) + return -1; + state->x.next = state->out; + return 0; + case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */ + strm->avail_out = state->size << 1; + strm->next_out = state->out; + if (gz_decomp(state) == -1) + return -1; + } + } while (state->x.have == 0 && (!state->eof || strm->avail_in)); + return 0; +} + +/* Skip len uncompressed bytes of output. Return -1 on error, 0 on success. */ +local int gz_skip(state, len) + gz_statep state; + z_off64_t len; +{ + unsigned n; + + /* skip over len bytes or reach end-of-file, whichever comes first */ + while (len) + /* skip over whatever is in output buffer */ + if (state->x.have) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ? + (unsigned)len : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + len -= n; + } + + /* output buffer empty -- return if we're at the end of the input */ + else if (state->eof && state->strm.avail_in == 0) + break; + + /* need more data to skip -- load up output buffer */ + else { + /* get more output, looking for header if required */ + if (gz_fetch(state) == -1) + return -1; + } + return 0; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzread(file, buf, len) + gzFile file; + voidp buf; + unsigned len; +{ + unsigned got, n; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* since an int is returned, make sure len fits in one, otherwise return + with an error (this avoids the flaw in the interface) */ + if ((int)len < 0) { + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); + return -1; + } + + /* if len is zero, avoid unnecessary operations */ + if (len == 0) + return 0; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return -1; + } + + /* get len bytes to buf, or less than len if at the end */ + got = 0; + do { + /* first just try copying data from the output buffer */ + if (state->x.have) { + n = state->x.have > len ? len : state->x.have; + memcpy(buf, state->x.next, n); + state->x.next += n; + state->x.have -= n; + } + + /* output buffer empty -- return if we're at the end of the input */ + else if (state->eof && strm->avail_in == 0) { + state->past = 1; /* tried to read past end */ + break; + } + + /* need output data -- for small len or new stream load up our output + buffer */ + else if (state->how == LOOK || len < (state->size << 1)) { + /* get more output, looking for header if required */ + if (gz_fetch(state) == -1) + return -1; + continue; /* no progress yet -- go back to copy above */ + /* the copy above assures that we will leave with space in the + output buffer, allowing at least one gzungetc() to succeed */ + } + + /* large len -- read directly into user buffer */ + else if (state->how == COPY) { /* read directly */ + if (gz_load(state, (unsigned char *)buf, len, &n) == -1) + return -1; + } + + /* large len -- decompress directly into user buffer */ + else { /* state->how == GZIP */ + strm->avail_out = len; + strm->next_out = (unsigned char *)buf; + if (gz_decomp(state) == -1) + return -1; + n = state->x.have; + state->x.have = 0; + } + + /* update progress */ + len -= n; + buf = (char *)buf + n; + got += n; + state->x.pos += n; + } while (len); + + /* return number of bytes read into user buffer (will fit in int) */ + return (int)got; +} + +/* -- see zlib.h -- */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +#else +# undef gzgetc +#endif +int ZEXPORT gzgetc(file) + gzFile file; +{ + int ret; + unsigned char buf[1]; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* try output buffer (no need to check for skip request) */ + if (state->x.have) { + state->x.have--; + state->x.pos++; + return *(state->x.next)++; + } + + /* nothing there -- try gzread() */ + ret = gzread(file, buf, 1); + return ret < 1 ? -1 : buf[0]; +} + +int ZEXPORT gzgetc_(file) +gzFile file; +{ + return gzgetc(file); +} + +/* -- see zlib.h -- */ +int ZEXPORT gzungetc(c, file) + int c; + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return -1; + } + + /* can't push EOF */ + if (c < 0) + return -1; + + /* if output buffer empty, put byte at end (allows more pushing) */ + if (state->x.have == 0) { + state->x.have = 1; + state->x.next = state->out + (state->size << 1) - 1; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; + return c; + } + + /* if no room, give up (must have already done a gzungetc()) */ + if (state->x.have == (state->size << 1)) { + gz_error(state, Z_DATA_ERROR, "out of room to push characters"); + return -1; + } + + /* slide output data if needed and insert byte before existing data */ + if (state->x.next == state->out) { + unsigned char *src = state->out + state->x.have; + unsigned char *dest = state->out + (state->size << 1); + while (src > state->out) + *--dest = *--src; + state->x.next = dest; + } + state->x.have++; + state->x.next--; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; + return c; +} + +/* -- see zlib.h -- */ +char * ZEXPORT gzgets(file, buf, len) + gzFile file; + char *buf; + int len; +{ + unsigned left, n; + char *str; + unsigned char *eol; + gz_statep state; + + /* check parameters and get internal structure */ + if (file == NULL || buf == NULL || len < 1) + return NULL; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return NULL; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return NULL; + } + + /* copy output bytes up to new line or len - 1, whichever comes first -- + append a terminating zero to the string (we don't check for a zero in + the contents, let the user worry about that) */ + str = buf; + left = (unsigned)len - 1; + if (left) do { + /* assure that something is in the output buffer */ + if (state->x.have == 0 && gz_fetch(state) == -1) + return NULL; /* error */ + if (state->x.have == 0) { /* end of file */ + state->past = 1; /* read past end */ + break; /* return what we have */ + } + + /* look for end-of-line in current output buffer */ + n = state->x.have > left ? left : state->x.have; + eol = (unsigned char *)memchr(state->x.next, '\n', n); + if (eol != NULL) + n = (unsigned)(eol - state->x.next) + 1; + + /* copy through end-of-line, or remainder if not found */ + memcpy(buf, state->x.next, n); + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + left -= n; + buf += n; + } while (left && eol == NULL); + + /* return terminated string, or if nothing, end of file */ + if (buf == str) + return NULL; + buf[0] = 0; + return str; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzdirect(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + + /* if the state is not known, but we can find out, then do so (this is + mainly for right after a gzopen() or gzdopen()) */ + if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0) + (void)gz_look(state); + + /* return 1 if transparent, 0 if processing a gzip stream */ + return state->direct; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzclose_r(file) + gzFile file; +{ + int ret, err; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + /* check that we're reading */ + if (state->mode != GZ_READ) + return Z_STREAM_ERROR; + + /* free memory and close file */ + if (state->size) { + inflateEnd(&(state->strm)); + free(state->out); + free(state->in); + } + err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK; + gz_error(state, Z_OK, NULL); + free(state->path); + ret = close(state->fd); + free(state); + return ret ? Z_ERRNO : err; +} diff --git a/zlib/zlib/gzwrite.c b/zlib/zlib/gzwrite.c new file mode 100644 index 00000000..aa767fbf --- /dev/null +++ b/zlib/zlib/gzwrite.c @@ -0,0 +1,577 @@ +/* gzwrite.c -- zlib functions for writing gzip files + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* Local functions */ +local int gz_init OF((gz_statep)); +local int gz_comp OF((gz_statep, int)); +local int gz_zero OF((gz_statep, z_off64_t)); + +/* Initialize state for writing a gzip file. Mark initialization by setting + state->size to non-zero. Return -1 on failure or 0 on success. */ +local int gz_init(state) + gz_statep state; +{ + int ret; + z_streamp strm = &(state->strm); + + /* allocate input buffer */ + state->in = (unsigned char *)malloc(state->want); + if (state->in == NULL) { + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* only need output buffer and deflate state if compressing */ + if (!state->direct) { + /* allocate output buffer */ + state->out = (unsigned char *)malloc(state->want); + if (state->out == NULL) { + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* allocate deflate memory, set up for gzip compression */ + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = deflateInit2(strm, state->level, Z_DEFLATED, + MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy); + if (ret != Z_OK) { + free(state->out); + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + } + + /* mark state as initialized */ + state->size = state->want; + + /* initialize write buffer if compressing */ + if (!state->direct) { + strm->avail_out = state->size; + strm->next_out = state->out; + state->x.next = strm->next_out; + } + return 0; +} + +/* Compress whatever is at avail_in and next_in and write to the output file. + Return -1 if there is an error writing to the output file, otherwise 0. + flush is assumed to be a valid deflate() flush value. If flush is Z_FINISH, + then the deflate() state is reset to start a new gzip stream. If gz->direct + is true, then simply write to the output file without compressing, and + ignore flush. */ +local int gz_comp(state, flush) + gz_statep state; + int flush; +{ + int ret, got; + unsigned have; + z_streamp strm = &(state->strm); + + /* allocate memory if this is the first time through */ + if (state->size == 0 && gz_init(state) == -1) + return -1; + + /* write directly if requested */ + if (state->direct) { + got = write(state->fd, strm->next_in, strm->avail_in); + if (got < 0 || (unsigned)got != strm->avail_in) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + strm->avail_in = 0; + return 0; + } + + /* run deflate() on provided input until it produces no more output */ + ret = Z_OK; + do { + /* write out current buffer contents if full, or if flushing, but if + doing Z_FINISH then don't write until we get to Z_STREAM_END */ + if (strm->avail_out == 0 || (flush != Z_NO_FLUSH && + (flush != Z_FINISH || ret == Z_STREAM_END))) { + have = (unsigned)(strm->next_out - state->x.next); + if (have && ((got = write(state->fd, state->x.next, have)) < 0 || + (unsigned)got != have)) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + if (strm->avail_out == 0) { + strm->avail_out = state->size; + strm->next_out = state->out; + } + state->x.next = strm->next_out; + } + + /* compress */ + have = strm->avail_out; + ret = deflate(strm, flush); + if (ret == Z_STREAM_ERROR) { + gz_error(state, Z_STREAM_ERROR, + "internal error: deflate stream corrupt"); + return -1; + } + have -= strm->avail_out; + } while (have); + + /* if that completed a deflate stream, allow another to start */ + if (flush == Z_FINISH) + deflateReset(strm); + + /* all done, no errors */ + return 0; +} + +/* Compress len zeros to output. Return -1 on error, 0 on success. */ +local int gz_zero(state, len) + gz_statep state; + z_off64_t len; +{ + int first; + unsigned n; + z_streamp strm = &(state->strm); + + /* consume whatever's left in the input buffer */ + if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return -1; + + /* compress len zeros (len guaranteed > 0) */ + first = 1; + while (len) { + n = GT_OFF(state->size) || (z_off64_t)state->size > len ? + (unsigned)len : state->size; + if (first) { + memset(state->in, 0, n); + first = 0; + } + strm->avail_in = n; + strm->next_in = state->in; + state->x.pos += n; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return -1; + len -= n; + } + return 0; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzwrite(file, buf, len) + gzFile file; + voidpc buf; + unsigned len; +{ + unsigned put = len; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return 0; + + /* since an int is returned, make sure len fits in one, otherwise return + with an error (this avoids the flaw in the interface) */ + if ((int)len < 0) { + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); + return 0; + } + + /* if len is zero, avoid unnecessary operations */ + if (len == 0) + return 0; + + /* allocate memory if this is the first time through */ + if (state->size == 0 && gz_init(state) == -1) + return 0; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return 0; + } + + /* for small len, copy to input buffer, otherwise compress directly */ + if (len < state->size) { + /* copy to input buffer, compress when full */ + do { + unsigned have, copy; + + if (strm->avail_in == 0) + strm->next_in = state->in; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + copy = state->size - have; + if (copy > len) + copy = len; + memcpy(state->in + have, buf, copy); + strm->avail_in += copy; + state->x.pos += copy; + buf = (const char *)buf + copy; + len -= copy; + if (len && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + } while (len); + } + else { + /* consume whatever's left in the input buffer */ + if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + + /* directly compress user buffer to file */ + strm->avail_in = len; + strm->next_in = (z_const Bytef *)buf; + state->x.pos += len; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + } + + /* input was all buffered or compressed (put will fit in int) */ + return (int)put; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzputc(file, c) + gzFile file; + int c; +{ + unsigned have; + unsigned char buf[1]; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return -1; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return -1; + } + + /* try writing to input buffer for speed (state->size == 0 if buffer not + initialized) */ + if (state->size) { + if (strm->avail_in == 0) + strm->next_in = state->in; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + if (have < state->size) { + state->in[have] = c; + strm->avail_in++; + state->x.pos++; + return c & 0xff; + } + } + + /* no room in buffer or not initialized, use gz_write() */ + buf[0] = c; + if (gzwrite(file, buf, 1) != 1) + return -1; + return c & 0xff; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzputs(file, str) + gzFile file; + const char *str; +{ + int ret; + unsigned len; + + /* write string */ + len = (unsigned)strlen(str); + ret = gzwrite(file, str, len); + return ret == 0 && len != 0 ? -1 : ret; +} + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +#include + +/* -- see zlib.h -- */ +int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) +{ + int size, len; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return 0; + + /* make sure we have some buffer space */ + if (state->size == 0 && gz_init(state) == -1) + return 0; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return 0; + } + + /* consume whatever's left in the input buffer */ + if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + + /* do the printf() into the input buffer, put length in len */ + size = (int)(state->size); + state->in[size - 1] = 0; +#ifdef NO_vsnprintf +# ifdef HAS_vsprintf_void + (void)vsprintf((char *)(state->in), format, va); + for (len = 0; len < size; len++) + if (state->in[len] == 0) break; +# else + len = vsprintf((char *)(state->in), format, va); +# endif +#else +# ifdef HAS_vsnprintf_void + (void)vsnprintf((char *)(state->in), size, format, va); + len = strlen((char *)(state->in)); +# else + len = vsnprintf((char *)(state->in), size, format, va); +# endif +#endif + + /* check that printf() results fit in buffer */ + if (len <= 0 || len >= (int)size || state->in[size - 1] != 0) + return 0; + + /* update buffer and position, defer compression until needed */ + strm->avail_in = (unsigned)len; + strm->next_in = state->in; + state->x.pos += len; + return len; +} + +int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) +{ + va_list va; + int ret; + + va_start(va, format); + ret = gzvprintf(file, format, va); + va_end(va); + return ret; +} + +#else /* !STDC && !Z_HAVE_STDARG_H */ + +/* -- see zlib.h -- */ +int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + gzFile file; + const char *format; + int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20; +{ + int size, len; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that can really pass pointer in ints */ + if (sizeof(int) != sizeof(void *)) + return 0; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return 0; + + /* make sure we have some buffer space */ + if (state->size == 0 && gz_init(state) == -1) + return 0; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return 0; + } + + /* consume whatever's left in the input buffer */ + if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + + /* do the printf() into the input buffer, put length in len */ + size = (int)(state->size); + state->in[size - 1] = 0; +#ifdef NO_snprintf +# ifdef HAS_sprintf_void + sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + for (len = 0; len < size; len++) + if (state->in[len] == 0) break; +# else + len = sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); +# endif +#else +# ifdef HAS_snprintf_void + snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = strlen((char *)(state->in)); +# else + len = snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20); +# endif +#endif + + /* check that printf() results fit in buffer */ + if (len <= 0 || len >= (int)size || state->in[size - 1] != 0) + return 0; + + /* update buffer and position, defer compression until needed */ + strm->avail_in = (unsigned)len; + strm->next_in = state->in; + state->x.pos += len; + return len; +} + +#endif + +/* -- see zlib.h -- */ +int ZEXPORT gzflush(file, flush) + gzFile file; + int flush; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* check flush parameter */ + if (flush < 0 || flush > Z_FINISH) + return Z_STREAM_ERROR; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return -1; + } + + /* compress remaining data with requested flush */ + gz_comp(state, flush); + return state->err; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzsetparams(file, level, strategy) + gzFile file; + int level; + int strategy; +{ + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* if no change is requested, then do nothing */ + if (level == state->level && strategy == state->strategy) + return Z_OK; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return -1; + } + + /* change compression parameters for subsequent input */ + if (state->size) { + /* flush previous input with previous parameters before changing */ + if (strm->avail_in && gz_comp(state, Z_PARTIAL_FLUSH) == -1) + return state->err; + deflateParams(strm, level, strategy); + } + state->level = level; + state->strategy = strategy; + return Z_OK; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzclose_w(file) + gzFile file; +{ + int ret = Z_OK; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + /* check that we're writing */ + if (state->mode != GZ_WRITE) + return Z_STREAM_ERROR; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + ret = state->err; + } + + /* flush, free memory, and close file */ + if (gz_comp(state, Z_FINISH) == -1) + ret = state->err; + if (state->size) { + if (!state->direct) { + (void)deflateEnd(&(state->strm)); + free(state->out); + } + free(state->in); + } + gz_error(state, Z_OK, NULL); + free(state->path); + if (close(state->fd) == -1) + ret = Z_ERRNO; + free(state); + return ret; +} diff --git a/zlib/zlib/infback.c b/zlib/zlib/infback.c new file mode 100644 index 00000000..f3833c2e --- /dev/null +++ b/zlib/zlib/infback.c @@ -0,0 +1,640 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2011 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size) +z_streamp strm; +int windowBits; +unsigned char FAR *window; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->wnext = 0; + state->whave = 0; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc) +z_streamp strm; +in_func in; +void FAR *in_desc; +out_func out; +void FAR *out_desc; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + state->length = (unsigned)here.val; + + /* process literal */ + if (here.op == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left)) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd(strm) +z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/zlib/zlib/inffast.c b/zlib/zlib/inffast.c new file mode 100644 index 00000000..bda59ceb --- /dev/null +++ b/zlib/zlib/inffast.c @@ -0,0 +1,340 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2008, 2010, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void ZLIB_INTERNAL inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *in; /* local strm->next_in */ + z_const unsigned char FAR *last; /* have enough input while in < last */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code here; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + wnext = state->wnext; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + here = lcode[hold & lmask]; + dolen: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op == 0) { /* literal */ + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + PUP(out) = (unsigned char)(here.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + here = dcode[hold & dmask]; + dodist: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state->sane) { + strm->msg = + (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (len <= op - whave) { + do { + PUP(out) = 0; + } while (--len); + continue; + } + len -= op - whave; + do { + PUP(out) = 0; + } while (--op > whave); + if (op == 0) { + from = out - dist; + do { + PUP(out) = PUP(from); + } while (--len); + continue; + } +#endif + } + from = window - OFF; + if (wnext == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + here = dcode[here.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + here = lcode[here.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and wnext == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/zlib/zlib/inffast.h b/zlib/zlib/inffast.h new file mode 100644 index 00000000..e5c1aa4c --- /dev/null +++ b/zlib/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/zlib/zlib/inffixed.h b/zlib/zlib/inffixed.h new file mode 100644 index 00000000..d6283277 --- /dev/null +++ b/zlib/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/zlib/zlib/inflate.c b/zlib/zlib/inflate.c new file mode 100644 index 00000000..870f89bb --- /dev/null +++ b/zlib/zlib/inflate.c @@ -0,0 +1,1512 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common wnext == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, + unsigned copy)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateResetKeep(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + if (state->wrap) /* to support ill-conceived Java test suite */ + strm->adler = state->wrap & 1; + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + state->sane = 1; + state->back = -1; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->wsize = 0; + state->whave = 0; + state->wnext = 0; + return inflateResetKeep(strm); +} + +int ZEXPORT inflateReset2(strm, windowBits) +z_streamp strm; +int windowBits; +{ + int wrap; + struct inflate_state FAR *state; + + /* get the state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) + windowBits &= 15; +#endif + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) + return Z_STREAM_ERROR; + if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) { + ZFREE(strm, state->window); + state->window = Z_NULL; + } + + /* update state and reset the rest of it */ + state->wrap = wrap; + state->wbits = (unsigned)windowBits; + return inflateReset(strm); +} + +int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) +z_streamp strm; +int windowBits; +const char *version; +int stream_size; +{ + int ret; + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->window = Z_NULL; + ret = inflateReset2(strm, windowBits); + if (ret != Z_OK) { + ZFREE(strm, state); + strm->state = Z_NULL; + } + return ret; +} + +int ZEXPORT inflateInit_(strm, version, stream_size) +z_streamp strm; +const char *version; +int stream_size; +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +int ZEXPORT inflatePrime(strm, bits, value) +z_streamp strm; +int bits; +int value; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits < 0) { + state->hold = 0; + state->bits = 0; + return Z_OK; + } + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow(strm, end, copy) +z_streamp strm; +const Bytef *end; +unsigned copy; +{ + struct inflate_state FAR *state; + unsigned dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->wnext = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state->wsize) { + zmemcpy(state->window, end - state->wsize, state->wsize); + state->wnext = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->wnext; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->wnext, end - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, end - copy, copy); + state->wnext = copy; + state->whave = state->wsize; + } + else { + state->wnext += dist; + if (state->wnext == state->wsize) state->wnext = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate(strm, flush) +z_streamp strm; +int flush; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (state->wbits == 0) + state->wbits = len; + else if (len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = ZSWAP32(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN_; /* decode codes */ + if (flush == Z_TREES) { + DROPBITS(2); + goto inf_leave; + } + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY_; + if (flush == Z_TREES) goto inf_leave; + case COPY_: + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (const code FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN_; + if (flush == Z_TREES) goto inf_leave; + case LEN_: + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + if (state->mode == TYPE) + state->back = -1; + break; + } + state->back = 0; + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + state->length = (unsigned)here.val; + if ((int)(here.op) == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + state->mode = LIT; + break; + } + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->back = -1; + state->mode = TYPE; + break; + } + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(here.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->was = state->length; + state->mode = DIST; + case DIST: + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + state->extra = (unsigned)(here.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->whave) { + if (state->sane) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + Trace((stderr, "inflate.c too far\n")); + copy -= state->whave; + if (copy > state->length) copy = state->length; + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = 0; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; +#endif + } + if (copy > state->wnext) { + copy -= state->wnext; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->wnext - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + ZSWAP32(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (out != strm->avail_out && state->mode < BAD && + (state->mode < CHECK || flush != Z_FINISH))) + if (updatewindow(strm, strm->next_out, out - strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0) + + (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) +z_streamp strm; +Bytef *dictionary; +uInt *dictLength; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* copy dictionary */ + if (state->whave && dictionary != Z_NULL) { + zmemcpy(dictionary, state->window + state->wnext, + state->whave - state->wnext); + zmemcpy(dictionary + state->whave - state->wnext, + state->window, state->wnext); + } + if (dictLength != Z_NULL) + *dictLength = state->whave; + return Z_OK; +} + +int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) +z_streamp strm; +const Bytef *dictionary; +uInt dictLength; +{ + struct inflate_state FAR *state; + unsigned long dictid; + int ret; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary identifier */ + if (state->mode == DICT) { + dictid = adler32(0L, Z_NULL, 0); + dictid = adler32(dictid, dictionary, dictLength); + if (dictid != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary + dictLength, dictLength); + if (ret) { + state->mode = MEM; + return Z_MEM_ERROR; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader(strm, head) +z_streamp strm; +gz_headerp head; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch(have, buf, len) +unsigned FAR *have; +const unsigned char FAR *buf; +unsigned len; +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync(strm) +z_streamp strm; +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(dest, source) +z_streamp dest; +z_streamp source; +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} + +int ZEXPORT inflateUndermine(strm, subvert) +z_streamp strm; +int subvert; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->sane = !subvert; +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + return Z_OK; +#else + state->sane = 1; + return Z_DATA_ERROR; +#endif +} + +long ZEXPORT inflateMark(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16; + state = (struct inflate_state FAR *)strm->state; + return ((long)(state->back) << 16) + + (state->mode == COPY ? state->length : + (state->mode == MATCH ? state->was - state->length : 0)); +} diff --git a/zlib/zlib/inflate.h b/zlib/zlib/inflate.h new file mode 100644 index 00000000..95f4986d --- /dev/null +++ b/zlib/zlib/inflate.h @@ -0,0 +1,122 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2009 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY_, /* i/o: same as COPY below, but only first time in */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN_, /* i: same as LEN below, but only first time in */ + LEN, /* i: waiting for length/lit/eob code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to BAD or MEM on error -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) or (raw) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT -> + HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + (raw) -> TYPEDO + Read deflate blocks: + TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK + STORED -> COPY_ -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN_ + LEN_ -> LEN + Read deflate codes in fixed or dynamic block: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 10K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ + int sane; /* if false, allow invalid distance too far */ + int back; /* bits back of last unprocessed length/lit */ + unsigned was; /* initial length of match */ +}; diff --git a/zlib/zlib/inftrees.c b/zlib/zlib/inftrees.c new file mode 100644 index 00000000..44d89cf2 --- /dev/null +++ b/zlib/zlib/inftrees.c @@ -0,0 +1,306 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.8 Copyright 1995-2013 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code here; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)1; + here.val = (unsigned short)0; + *(*table)++ = here; /* make a table to force an error */ + *(*table)++ = here; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + here.op = (unsigned char)0; + here.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + here.op = (unsigned char)(extra[work[sym]]); + here.val = base[work[sym]]; + } + else { + here.op = (unsigned char)(32 + 64); /* end of block */ + here.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = here; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff != 0) { + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)(len - drop); + here.val = (unsigned short)0; + next[huff] = here; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/zlib/zlib/inftrees.h b/zlib/zlib/inftrees.h new file mode 100644 index 00000000..baa53a0b --- /dev/null +++ b/zlib/zlib/inftrees.h @@ -0,0 +1,62 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of the dynamic table. The maximum number of code structures is + 1444, which is the sum of 852 for literal/length codes and 592 for distance + codes. These values were found by exhaustive searches using the program + examples/enough.c found in the zlib distribtution. The arguments to that + program are the number of symbols, the initial root table size, and the + maximum bit length of a code. "enough 286 9 15" for literal/length codes + returns returns 852, and "enough 30 6 15" for distance codes returns 592. + The initial root table size (9 or 6) is found in the fifth argument of the + inflate_table() calls in inflate.c and infback.c. If the root table size is + changed, then these maximum sizes would be need to be recalculated and + updated. */ +#define ENOUGH_LENS 852 +#define ENOUGH_DISTS 592 +#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS) + +/* Type of code to build for inflate_table() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); diff --git a/zlib/zlib/make_vms.com b/zlib/zlib/make_vms.com new file mode 100644 index 00000000..65e9d0cb --- /dev/null +++ b/zlib/zlib/make_vms.com @@ -0,0 +1,867 @@ +$! make libz under VMS written by +$! Martin P.J. Zinser +$! +$! In case of problems with the install you might contact me at +$! zinser@zinser.no-ip.info(preferred) or +$! martin.zinser@eurexchange.com (work) +$! +$! Make procedure history for Zlib +$! +$!------------------------------------------------------------------------------ +$! Version history +$! 0.01 20060120 First version to receive a number +$! 0.02 20061008 Adapt to new Makefile.in +$! 0.03 20091224 Add support for large file check +$! 0.04 20100110 Add new gzclose, gzlib, gzread, gzwrite +$! 0.05 20100221 Exchange zlibdefs.h by zconf.h.in +$! 0.06 20120111 Fix missing amiss_err, update zconf_h.in, fix new exmples +$! subdir path, update module search in makefile.in +$! 0.07 20120115 Triggered by work done by Alexey Chupahin completly redesigned +$! shared image creation +$! 0.08 20120219 Make it work on VAX again, pre-load missing symbols to shared +$! image +$! 0.09 20120305 SMS. P1 sets builder ("MMK", "MMS", " " (built-in)). +$! "" -> automatic, preference: MMK, MMS, built-in. +$! +$ on error then goto err_exit +$! +$ true = 1 +$ false = 0 +$ tmpnam = "temp_" + f$getjpi("","pid") +$ tt = tmpnam + ".txt" +$ tc = tmpnam + ".c" +$ th = tmpnam + ".h" +$ define/nolog tconfig 'th' +$ its_decc = false +$ its_vaxc = false +$ its_gnuc = false +$ s_case = False +$! +$! Setup variables holding "config" information +$! +$ Make = "''p1'" +$ name = "Zlib" +$ version = "?.?.?" +$ v_string = "ZLIB_VERSION" +$ v_file = "zlib.h" +$ ccopt = "/include = []" +$ lopts = "" +$ dnsrl = "" +$ aconf_in_file = "zconf.h.in#zconf.h_in#zconf_h.in" +$ conf_check_string = "" +$ linkonly = false +$ optfile = name + ".opt" +$ mapfile = name + ".map" +$ libdefs = "" +$ vax = f$getsyi("HW_MODEL").lt.1024 +$ axp = f$getsyi("HW_MODEL").ge.1024 .and. f$getsyi("HW_MODEL").lt.4096 +$ ia64 = f$getsyi("HW_MODEL").ge.4096 +$! +$! 2012-03-05 SMS. +$! Why is this needed? And if it is needed, why not simply ".not. vax"? +$! +$!!! if axp .or. ia64 then set proc/parse=extended +$! +$ whoami = f$parse(f$environment("Procedure"),,,,"NO_CONCEAL") +$ mydef = F$parse(whoami,,,"DEVICE") +$ mydir = f$parse(whoami,,,"DIRECTORY") - "][" +$ myproc = f$parse(whoami,,,"Name") + f$parse(whoami,,,"type") +$! +$! Check for MMK/MMS +$! +$ if (Make .eqs. "") +$ then +$ If F$Search ("Sys$System:MMS.EXE") .nes. "" Then Make = "MMS" +$ If F$Type (MMK) .eqs. "STRING" Then Make = "MMK" +$ else +$ Make = f$edit( Make, "trim") +$ endif +$! +$ gosub find_version +$! +$ open/write topt tmp.opt +$ open/write optf 'optfile' +$! +$ gosub check_opts +$! +$! Look for the compiler used +$! +$ gosub check_compiler +$ close topt +$ close optf +$! +$ if its_decc +$ then +$ ccopt = "/prefix=all" + ccopt +$ if f$trnlnm("SYS") .eqs. "" +$ then +$ if axp +$ then +$ define sys sys$library: +$ else +$ ccopt = "/decc" + ccopt +$ define sys decc$library_include: +$ endif +$ endif +$! +$! 2012-03-05 SMS. +$! Why /NAMES = AS_IS? Why not simply ".not. vax"? And why not on VAX? +$! +$ if axp .or. ia64 +$ then +$ ccopt = ccopt + "/name=as_is/opt=(inline=speed)" +$ s_case = true +$ endif +$ endif +$ if its_vaxc .or. its_gnuc +$ then +$ if f$trnlnm("SYS").eqs."" then define sys sys$library: +$ endif +$! +$! Build a fake configure input header +$! +$ open/write conf_hin config.hin +$ write conf_hin "#undef _LARGEFILE64_SOURCE" +$ close conf_hin +$! +$! +$ i = 0 +$FIND_ACONF: +$ fname = f$element(i,"#",aconf_in_file) +$ if fname .eqs. "#" then goto AMISS_ERR +$ if f$search(fname) .eqs. "" +$ then +$ i = i + 1 +$ goto find_aconf +$ endif +$ open/read/err=aconf_err aconf_in 'fname' +$ open/write aconf zconf.h +$ACONF_LOOP: +$ read/end_of_file=aconf_exit aconf_in line +$ work = f$edit(line, "compress,trim") +$ if f$extract(0,6,work) .nes. "#undef" +$ then +$ if f$extract(0,12,work) .nes. "#cmakedefine" +$ then +$ write aconf line +$ endif +$ else +$ cdef = f$element(1," ",work) +$ gosub check_config +$ endif +$ goto aconf_loop +$ACONF_EXIT: +$ write aconf "" +$ write aconf "/* VMS specifics added by make_vms.com: */" +$ write aconf "#define VMS 1" +$ write aconf "#include " +$ write aconf "#include " +$ write aconf "#ifdef _LARGEFILE" +$ write aconf "# define off64_t __off64_t" +$ write aconf "# define fopen64 fopen" +$ write aconf "# define fseeko64 fseeko" +$ write aconf "# define lseek64 lseek" +$ write aconf "# define ftello64 ftell" +$ write aconf "#endif" +$ write aconf "#if !defined( __VAX) && (__CRTL_VER >= 70312000)" +$ write aconf "# define HAVE_VSNPRINTF" +$ write aconf "#endif" +$ close aconf_in +$ close aconf +$ if f$search("''th'") .nes. "" then delete 'th';* +$! Build the thing plain or with mms +$! +$ write sys$output "Compiling Zlib sources ..." +$ if make.eqs."" +$ then +$ if (f$search( "example.obj;*") .nes. "") then delete example.obj;* +$ if (f$search( "minigzip.obj;*") .nes. "") then delete minigzip.obj;* +$ CALL MAKE adler32.OBJ "CC ''CCOPT' adler32" - + adler32.c zlib.h zconf.h +$ CALL MAKE compress.OBJ "CC ''CCOPT' compress" - + compress.c zlib.h zconf.h +$ CALL MAKE crc32.OBJ "CC ''CCOPT' crc32" - + crc32.c zlib.h zconf.h +$ CALL MAKE deflate.OBJ "CC ''CCOPT' deflate" - + deflate.c deflate.h zutil.h zlib.h zconf.h +$ CALL MAKE gzclose.OBJ "CC ''CCOPT' gzclose" - + gzclose.c zutil.h zlib.h zconf.h +$ CALL MAKE gzlib.OBJ "CC ''CCOPT' gzlib" - + gzlib.c zutil.h zlib.h zconf.h +$ CALL MAKE gzread.OBJ "CC ''CCOPT' gzread" - + gzread.c zutil.h zlib.h zconf.h +$ CALL MAKE gzwrite.OBJ "CC ''CCOPT' gzwrite" - + gzwrite.c zutil.h zlib.h zconf.h +$ CALL MAKE infback.OBJ "CC ''CCOPT' infback" - + infback.c zutil.h inftrees.h inflate.h inffast.h inffixed.h +$ CALL MAKE inffast.OBJ "CC ''CCOPT' inffast" - + inffast.c zutil.h zlib.h zconf.h inffast.h +$ CALL MAKE inflate.OBJ "CC ''CCOPT' inflate" - + inflate.c zutil.h zlib.h zconf.h infblock.h +$ CALL MAKE inftrees.OBJ "CC ''CCOPT' inftrees" - + inftrees.c zutil.h zlib.h zconf.h inftrees.h +$ CALL MAKE trees.OBJ "CC ''CCOPT' trees" - + trees.c deflate.h zutil.h zlib.h zconf.h +$ CALL MAKE uncompr.OBJ "CC ''CCOPT' uncompr" - + uncompr.c zlib.h zconf.h +$ CALL MAKE zutil.OBJ "CC ''CCOPT' zutil" - + zutil.c zutil.h zlib.h zconf.h +$ write sys$output "Building Zlib ..." +$ CALL MAKE libz.OLB "lib/crea libz.olb *.obj" *.OBJ +$ write sys$output "Building example..." +$ CALL MAKE example.OBJ "CC ''CCOPT' [.test]example" - + [.test]example.c zlib.h zconf.h +$ call make example.exe "LINK example,libz.olb/lib" example.obj libz.olb +$ write sys$output "Building minigzip..." +$ CALL MAKE minigzip.OBJ "CC ''CCOPT' [.test]minigzip" - + [.test]minigzip.c zlib.h zconf.h +$ call make minigzip.exe - + "LINK minigzip,libz.olb/lib" - + minigzip.obj libz.olb +$ else +$ gosub crea_mms +$ write sys$output "Make ''name' ''version' with ''Make' " +$ 'make' +$ endif +$! +$! Create shareable image +$! +$ gosub crea_olist +$ write sys$output "Creating libzshr.exe" +$ call map_2_shopt 'mapfile' 'optfile' +$ LINK_'lopts'/SHARE=libzshr.exe modules.opt/opt,'optfile'/opt +$ write sys$output "Zlib build completed" +$ delete/nolog tmp.opt;* +$ exit +$AMISS_ERR: +$ write sys$output "No source for config.hin found." +$ write sys$output "Tried any of ''aconf_in_file'" +$ goto err_exit +$CC_ERR: +$ write sys$output "C compiler required to build ''name'" +$ goto err_exit +$ERR_EXIT: +$ set message/facil/ident/sever/text +$ close/nolog optf +$ close/nolog topt +$ close/nolog aconf_in +$ close/nolog aconf +$ close/nolog out +$ close/nolog min +$ close/nolog mod +$ close/nolog h_in +$ write sys$output "Exiting..." +$ exit 2 +$! +$! +$MAKE: SUBROUTINE !SUBROUTINE TO CHECK DEPENDENCIES +$ V = 'F$Verify(0) +$! P1 = What we are trying to make +$! P2 = Command to make it +$! P3 - P8 What it depends on +$ +$ If F$Search(P1) .Eqs. "" Then Goto Makeit +$ Time = F$CvTime(F$File(P1,"RDT")) +$arg=3 +$Loop: +$ Argument = P'arg +$ If Argument .Eqs. "" Then Goto Exit +$ El=0 +$Loop2: +$ File = F$Element(El," ",Argument) +$ If File .Eqs. " " Then Goto Endl +$ AFile = "" +$Loop3: +$ OFile = AFile +$ AFile = F$Search(File) +$ If AFile .Eqs. "" .Or. AFile .Eqs. OFile Then Goto NextEl +$ If F$CvTime(F$File(AFile,"RDT")) .Ges. Time Then Goto Makeit +$ Goto Loop3 +$NextEL: +$ El = El + 1 +$ Goto Loop2 +$EndL: +$ arg=arg+1 +$ If arg .Le. 8 Then Goto Loop +$ Goto Exit +$ +$Makeit: +$ VV=F$VERIFY(0) +$ write sys$output P2 +$ 'P2 +$ VV='F$Verify(VV) +$Exit: +$ If V Then Set Verify +$ENDSUBROUTINE +$!------------------------------------------------------------------------------ +$! +$! Check command line options and set symbols accordingly +$! +$!------------------------------------------------------------------------------ +$! Version history +$! 0.01 20041206 First version to receive a number +$! 0.02 20060126 Add new "HELP" target +$ CHECK_OPTS: +$ i = 1 +$ OPT_LOOP: +$ if i .lt. 9 +$ then +$ cparm = f$edit(p'i',"upcase") +$! +$! Check if parameter actually contains something +$! +$ if f$edit(cparm,"trim") .nes. "" +$ then +$ if cparm .eqs. "DEBUG" +$ then +$ ccopt = ccopt + "/noopt/deb" +$ lopts = lopts + "/deb" +$ endif +$ if f$locate("CCOPT=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ ccopt = ccopt + f$extract(start,len,cparm) +$ if f$locate("AS_IS",f$edit(ccopt,"UPCASE")) .lt. f$length(ccopt) - + then s_case = true +$ endif +$ if cparm .eqs. "LINK" then linkonly = true +$ if f$locate("LOPTS=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ lopts = lopts + f$extract(start,len,cparm) +$ endif +$ if f$locate("CC=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ cc_com = f$extract(start,len,cparm) + if (cc_com .nes. "DECC") .and. - + (cc_com .nes. "VAXC") .and. - + (cc_com .nes. "GNUC") +$ then +$ write sys$output "Unsupported compiler choice ''cc_com' ignored" +$ write sys$output "Use DECC, VAXC, or GNUC instead" +$ else +$ if cc_com .eqs. "DECC" then its_decc = true +$ if cc_com .eqs. "VAXC" then its_vaxc = true +$ if cc_com .eqs. "GNUC" then its_gnuc = true +$ endif +$ endif +$ if f$locate("MAKE=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ mmks = f$extract(start,len,cparm) +$ if (mmks .eqs. "MMK") .or. (mmks .eqs. "MMS") +$ then +$ make = mmks +$ else +$ write sys$output "Unsupported make choice ''mmks' ignored" +$ write sys$output "Use MMK or MMS instead" +$ endif +$ endif +$ if cparm .eqs. "HELP" then gosub bhelp +$ endif +$ i = i + 1 +$ goto opt_loop +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Look for the compiler used +$! +$! Version history +$! 0.01 20040223 First version to receive a number +$! 0.02 20040229 Save/set value of decc$no_rooted_search_lists +$! 0.03 20060202 Extend handling of GNU C +$! 0.04 20090402 Compaq -> hp +$CHECK_COMPILER: +$ if (.not. (its_decc .or. its_vaxc .or. its_gnuc)) +$ then +$ its_decc = (f$search("SYS$SYSTEM:DECC$COMPILER.EXE") .nes. "") +$ its_vaxc = .not. its_decc .and. (F$Search("SYS$System:VAXC.Exe") .nes. "") +$ its_gnuc = .not. (its_decc .or. its_vaxc) .and. (f$trnlnm("gnu_cc") .nes. "") +$ endif +$! +$! Exit if no compiler available +$! +$ if (.not. (its_decc .or. its_vaxc .or. its_gnuc)) +$ then goto CC_ERR +$ else +$ if its_decc +$ then +$ write sys$output "CC compiler check ... hp C" +$ if f$trnlnm("decc$no_rooted_search_lists") .nes. "" +$ then +$ dnrsl = f$trnlnm("decc$no_rooted_search_lists") +$ endif +$ define/nolog decc$no_rooted_search_lists 1 +$ else +$ if its_vaxc then write sys$output "CC compiler check ... VAX C" +$ if its_gnuc +$ then +$ write sys$output "CC compiler check ... GNU C" +$ if f$trnlnm(topt) then write topt "gnu_cc:[000000]gcclib.olb/lib" +$ if f$trnlnm(optf) then write optf "gnu_cc:[000000]gcclib.olb/lib" +$ cc = "gcc" +$ endif +$ if f$trnlnm(topt) then write topt "sys$share:vaxcrtl.exe/share" +$ if f$trnlnm(optf) then write optf "sys$share:vaxcrtl.exe/share" +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! If MMS/MMK are available dump out the descrip.mms if required +$! +$CREA_MMS: +$ write sys$output "Creating descrip.mms..." +$ create descrip.mms +$ open/append out descrip.mms +$ copy sys$input: out +$ deck +# descrip.mms: MMS description file for building zlib on VMS +# written by Martin P.J. Zinser +# + +OBJS = adler32.obj, compress.obj, crc32.obj, gzclose.obj, gzlib.obj\ + gzread.obj, gzwrite.obj, uncompr.obj, infback.obj\ + deflate.obj, trees.obj, zutil.obj, inflate.obj, \ + inftrees.obj, inffast.obj + +$ eod +$ write out "CFLAGS=", ccopt +$ write out "LOPTS=", lopts +$ write out "all : example.exe minigzip.exe libz.olb" +$ copy sys$input: out +$ deck + @ write sys$output " Example applications available" + +libz.olb : libz.olb($(OBJS)) + @ write sys$output " libz available" + +example.exe : example.obj libz.olb + link $(LOPTS) example,libz.olb/lib + +minigzip.exe : minigzip.obj libz.olb + link $(LOPTS) minigzip,libz.olb/lib + +clean : + delete *.obj;*,libz.olb;*,*.opt;*,*.exe;* + + +# Other dependencies. +adler32.obj : adler32.c zutil.h zlib.h zconf.h +compress.obj : compress.c zlib.h zconf.h +crc32.obj : crc32.c zutil.h zlib.h zconf.h +deflate.obj : deflate.c deflate.h zutil.h zlib.h zconf.h +example.obj : [.test]example.c zlib.h zconf.h +gzclose.obj : gzclose.c zutil.h zlib.h zconf.h +gzlib.obj : gzlib.c zutil.h zlib.h zconf.h +gzread.obj : gzread.c zutil.h zlib.h zconf.h +gzwrite.obj : gzwrite.c zutil.h zlib.h zconf.h +inffast.obj : inffast.c zutil.h zlib.h zconf.h inftrees.h inffast.h +inflate.obj : inflate.c zutil.h zlib.h zconf.h +inftrees.obj : inftrees.c zutil.h zlib.h zconf.h inftrees.h +minigzip.obj : [.test]minigzip.c zlib.h zconf.h +trees.obj : trees.c deflate.h zutil.h zlib.h zconf.h +uncompr.obj : uncompr.c zlib.h zconf.h +zutil.obj : zutil.c zutil.h zlib.h zconf.h +infback.obj : infback.c zutil.h inftrees.h inflate.h inffast.h inffixed.h +$ eod +$ close out +$ return +$!------------------------------------------------------------------------------ +$! +$! Read list of core library sources from makefile.in and create options +$! needed to build shareable image +$! +$CREA_OLIST: +$ open/read min makefile.in +$ open/write mod modules.opt +$ src_check_list = "OBJZ =#OBJG =" +$MRLOOP: +$ read/end=mrdone min rec +$ i = 0 +$SRC_CHECK_LOOP: +$ src_check = f$element(i, "#", src_check_list) +$ i = i+1 +$ if src_check .eqs. "#" then goto mrloop +$ if (f$extract(0,6,rec) .nes. src_check) then goto src_check_loop +$ rec = rec - src_check +$ gosub extra_filnam +$ if (f$element(1,"\",rec) .eqs. "\") then goto mrloop +$MRSLOOP: +$ read/end=mrdone min rec +$ gosub extra_filnam +$ if (f$element(1,"\",rec) .nes. "\") then goto mrsloop +$MRDONE: +$ close min +$ close mod +$ return +$!------------------------------------------------------------------------------ +$! +$! Take record extracted in crea_olist and split it into single filenames +$! +$EXTRA_FILNAM: +$ myrec = f$edit(rec - "\", "trim,compress") +$ i = 0 +$FELOOP: +$ srcfil = f$element(i," ", myrec) +$ if (srcfil .nes. " ") +$ then +$ write mod f$parse(srcfil,,,"NAME"), ".obj" +$ i = i + 1 +$ goto feloop +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Find current Zlib version number +$! +$FIND_VERSION: +$ open/read h_in 'v_file' +$hloop: +$ read/end=hdone h_in rec +$ rec = f$edit(rec,"TRIM") +$ if (f$extract(0,1,rec) .nes. "#") then goto hloop +$ rec = f$edit(rec - "#", "TRIM") +$ if f$element(0," ",rec) .nes. "define" then goto hloop +$ if f$element(1," ",rec) .eqs. v_string +$ then +$ version = 'f$element(2," ",rec)' +$ goto hdone +$ endif +$ goto hloop +$hdone: +$ close h_in +$ return +$!------------------------------------------------------------------------------ +$! +$CHECK_CONFIG: +$! +$ in_ldef = f$locate(cdef,libdefs) +$ if (in_ldef .lt. f$length(libdefs)) +$ then +$ write aconf "#define ''cdef' 1" +$ libdefs = f$extract(0,in_ldef,libdefs) + - + f$extract(in_ldef + f$length(cdef) + 1, - + f$length(libdefs) - in_ldef - f$length(cdef) - 1, - + libdefs) +$ else +$ if (f$type('cdef') .eqs. "INTEGER") +$ then +$ write aconf "#define ''cdef' ", 'cdef' +$ else +$ if (f$type('cdef') .eqs. "STRING") +$ then +$ write aconf "#define ''cdef' ", """", '''cdef'', """" +$ else +$ gosub check_cc_def +$ endif +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Check if this is a define relating to the properties of the C/C++ +$! compiler +$! +$ CHECK_CC_DEF: +$ if (cdef .eqs. "_LARGEFILE64_SOURCE") +$ then +$ copy sys$input: 'tc' +$ deck +#include "tconfig" +#define _LARGEFILE +#include + +int main(){ +FILE *fp; + fp = fopen("temp.txt","r"); + fseeko(fp,1,SEEK_SET); + fclose(fp); +} + +$ eod +$ test_inv = false +$ comm_h = false +$ gosub cc_prop_check +$ return +$ endif +$ write aconf "/* ", line, " */" +$ return +$!------------------------------------------------------------------------------ +$! +$! Check for properties of C/C++ compiler +$! +$! Version history +$! 0.01 20031020 First version to receive a number +$! 0.02 20031022 Added logic for defines with value +$! 0.03 20040309 Make sure local config file gets not deleted +$! 0.04 20041230 Also write include for configure run +$! 0.05 20050103 Add processing of "comment defines" +$CC_PROP_CHECK: +$ cc_prop = true +$ is_need = false +$ is_need = (f$extract(0,4,cdef) .eqs. "NEED") .or. (test_inv .eq. true) +$ if f$search(th) .eqs. "" then create 'th' +$ set message/nofac/noident/nosever/notext +$ on error then continue +$ cc 'tmpnam' +$ if .not. ($status) then cc_prop = false +$ on error then continue +$! The headers might lie about the capabilities of the RTL +$ link 'tmpnam',tmp.opt/opt +$ if .not. ($status) then cc_prop = false +$ set message/fac/ident/sever/text +$ on error then goto err_exit +$ delete/nolog 'tmpnam'.*;*/exclude='th' +$ if (cc_prop .and. .not. is_need) .or. - + (.not. cc_prop .and. is_need) +$ then +$ write sys$output "Checking for ''cdef'... yes" +$ if f$type('cdef_val'_yes) .nes. "" +$ then +$ if f$type('cdef_val'_yes) .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,'cdef_val'_yes) +$ if f$type('cdef_val'_yes) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,'cdef_val'_yes) +$ else +$ call write_config f$fao("#define !AS 1",cdef) +$ endif +$ if (cdef .eqs. "HAVE_FSEEKO") .or. (cdef .eqs. "_LARGE_FILES") .or. - + (cdef .eqs. "_LARGEFILE64_SOURCE") then - + call write_config f$string("#define _LARGEFILE 1") +$ else +$ write sys$output "Checking for ''cdef'... no" +$ if (comm_h) +$ then + call write_config f$fao("/* !AS */",line) +$ else +$ if f$type('cdef_val'_no) .nes. "" +$ then +$ if f$type('cdef_val'_no) .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,'cdef_val'_no) +$ if f$type('cdef_val'_no) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,'cdef_val'_no) +$ else +$ call write_config f$fao("#undef !AS",cdef) +$ endif +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Check for properties of C/C++ compiler with multiple result values +$! +$! Version history +$! 0.01 20040127 First version +$! 0.02 20050103 Reconcile changes from cc_prop up to version 0.05 +$CC_MPROP_CHECK: +$ cc_prop = true +$ i = 1 +$ idel = 1 +$ MT_LOOP: +$ if f$type(result_'i') .eqs. "STRING" +$ then +$ set message/nofac/noident/nosever/notext +$ on error then continue +$ cc 'tmpnam'_'i' +$ if .not. ($status) then cc_prop = false +$ on error then continue +$! The headers might lie about the capabilities of the RTL +$ link 'tmpnam'_'i',tmp.opt/opt +$ if .not. ($status) then cc_prop = false +$ set message/fac/ident/sever/text +$ on error then goto err_exit +$ delete/nolog 'tmpnam'_'i'.*;* +$ if (cc_prop) +$ then +$ write sys$output "Checking for ''cdef'... ", mdef_'i' +$ if f$type(mdef_'i') .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,mdef_'i') +$ if f$type('cdef_val'_yes) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,mdef_'i') +$ goto msym_clean +$ else +$ i = i + 1 +$ goto mt_loop +$ endif +$ endif +$ write sys$output "Checking for ''cdef'... no" +$ call write_config f$fao("#undef !AS",cdef) +$ MSYM_CLEAN: +$ if (idel .le. msym_max) +$ then +$ delete/sym mdef_'idel' +$ idel = idel + 1 +$ goto msym_clean +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Write configuration to both permanent and temporary config file +$! +$! Version history +$! 0.01 20031029 First version to receive a number +$! +$WRITE_CONFIG: SUBROUTINE +$ write aconf 'p1' +$ open/append confh 'th' +$ write confh 'p1' +$ close confh +$ENDSUBROUTINE +$!------------------------------------------------------------------------------ +$! +$! Analyze the project map file and create the symbol vector for a shareable +$! image from it +$! +$! Version history +$! 0.01 20120128 First version +$! 0.02 20120226 Add pre-load logic +$! +$ MAP_2_SHOPT: Subroutine +$! +$ SAY := "WRITE_ SYS$OUTPUT" +$! +$ IF F$SEARCH("''P1'") .EQS. "" +$ THEN +$ SAY "MAP_2_SHOPT-E-NOSUCHFILE: Error, inputfile ''p1' not available" +$ goto exit_m2s +$ ENDIF +$ IF "''P2'" .EQS. "" +$ THEN +$ SAY "MAP_2_SHOPT: Error, no output file provided" +$ goto exit_m2s +$ ENDIF +$! +$ module1 = "deflate#deflateEnd#deflateInit_#deflateParams#deflateSetDictionary" +$ module2 = "gzclose#gzerror#gzgetc#gzgets#gzopen#gzprintf#gzputc#gzputs#gzread" +$ module3 = "gzseek#gztell#inflate#inflateEnd#inflateInit_#inflateSetDictionary" +$ module4 = "inflateSync#uncompress#zlibVersion#compress" +$ open/read map 'p1 +$ if axp .or. ia64 +$ then +$ open/write aopt a.opt +$ open/write bopt b.opt +$ write aopt " CASE_SENSITIVE=YES" +$ write bopt "SYMBOL_VECTOR= (-" +$ mod_sym_num = 1 +$ MOD_SYM_LOOP: +$ if f$type(module'mod_sym_num') .nes. "" +$ then +$ mod_in = 0 +$ MOD_SYM_IN: +$ shared_proc = f$element(mod_in, "#", module'mod_sym_num') +$ if shared_proc .nes. "#" +$ then +$ write aopt f$fao(" symbol_vector=(!AS/!AS=PROCEDURE)",- + f$edit(shared_proc,"upcase"),shared_proc) +$ write bopt f$fao("!AS=PROCEDURE,-",shared_proc) +$ mod_in = mod_in + 1 +$ goto mod_sym_in +$ endif +$ mod_sym_num = mod_sym_num + 1 +$ goto mod_sym_loop +$ endif +$MAP_LOOP: +$ read/end=map_end map line +$ if (f$locate("{",line).lt. f$length(line)) .or. - + (f$locate("global:", line) .lt. f$length(line)) +$ then +$ proc = true +$ goto map_loop +$ endif +$ if f$locate("}",line).lt. f$length(line) then proc = false +$ if f$locate("local:", line) .lt. f$length(line) then proc = false +$ if proc +$ then +$ shared_proc = f$edit(line,"collapse") +$ chop_semi = f$locate(";", shared_proc) +$ if chop_semi .lt. f$length(shared_proc) then - + shared_proc = f$extract(0, chop_semi, shared_proc) +$ write aopt f$fao(" symbol_vector=(!AS/!AS=PROCEDURE)",- + f$edit(shared_proc,"upcase"),shared_proc) +$ write bopt f$fao("!AS=PROCEDURE,-",shared_proc) +$ endif +$ goto map_loop +$MAP_END: +$ close/nolog aopt +$ close/nolog bopt +$ open/append libopt 'p2' +$ open/read aopt a.opt +$ open/read bopt b.opt +$ALOOP: +$ read/end=aloop_end aopt line +$ write libopt line +$ goto aloop +$ALOOP_END: +$ close/nolog aopt +$ sv = "" +$BLOOP: +$ read/end=bloop_end bopt svn +$ if (svn.nes."") +$ then +$ if (sv.nes."") then write libopt sv +$ sv = svn +$ endif +$ goto bloop +$BLOOP_END: +$ write libopt f$extract(0,f$length(sv)-2,sv), "-" +$ write libopt ")" +$ close/nolog bopt +$ delete/nolog/noconf a.opt;*,b.opt;* +$ else +$ if vax +$ then +$ open/append libopt 'p2' +$ mod_sym_num = 1 +$ VMOD_SYM_LOOP: +$ if f$type(module'mod_sym_num') .nes. "" +$ then +$ mod_in = 0 +$ VMOD_SYM_IN: +$ shared_proc = f$element(mod_in, "#", module'mod_sym_num') +$ if shared_proc .nes. "#" +$ then +$ write libopt f$fao("UNIVERSAL=!AS",- + f$edit(shared_proc,"upcase")) +$ mod_in = mod_in + 1 +$ goto vmod_sym_in +$ endif +$ mod_sym_num = mod_sym_num + 1 +$ goto vmod_sym_loop +$ endif +$VMAP_LOOP: +$ read/end=vmap_end map line +$ if (f$locate("{",line).lt. f$length(line)) .or. - + (f$locate("global:", line) .lt. f$length(line)) +$ then +$ proc = true +$ goto vmap_loop +$ endif +$ if f$locate("}",line).lt. f$length(line) then proc = false +$ if f$locate("local:", line) .lt. f$length(line) then proc = false +$ if proc +$ then +$ shared_proc = f$edit(line,"collapse") +$ chop_semi = f$locate(";", shared_proc) +$ if chop_semi .lt. f$length(shared_proc) then - + shared_proc = f$extract(0, chop_semi, shared_proc) +$ write libopt f$fao("UNIVERSAL=!AS",- + f$edit(shared_proc,"upcase")) +$ endif +$ goto vmap_loop +$VMAP_END: +$ else +$ write sys$output "Unknown Architecture (Not VAX, AXP, or IA64)" +$ write sys$output "No options file created" +$ endif +$ endif +$ EXIT_M2S: +$ close/nolog map +$ close/nolog libopt +$ endsubroutine diff --git a/zlib/zlib/msdos/Makefile.bor b/zlib/zlib/msdos/Makefile.bor new file mode 100644 index 00000000..3d12a2c2 --- /dev/null +++ b/zlib/zlib/msdos/Makefile.bor @@ -0,0 +1,115 @@ +# Makefile for zlib +# Borland C++ +# Last updated: 15-Mar-2003 + +# To use, do "make -fmakefile.bor" +# To compile in small model, set below: MODEL=s + +# WARNING: the small model is supported but only for small values of +# MAX_WBITS and MAX_MEM_LEVEL. For example: +# -DMAX_WBITS=11 -DDEF_WBITS=11 -DMAX_MEM_LEVEL=3 +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to the LOC macro below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------ Turbo C++, Borland C++ ------------ + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or added +# to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +# type for CPU required: 0: 8086, 1: 80186, 2: 80286, 3: 80386, etc. +CPU_TYP = 0 + +# memory model: one of s, m, c, l (small, medium, compact, large) +MODEL=l + +# replace bcc with tcc for Turbo C++ 1.0, with bcc32 for the 32 bit version +CC=bcc +LD=bcc +AR=tlib + +# compiler flags +# replace "-O2" by "-O -G -a -d" for Turbo C++ 1.0 +CFLAGS=-O2 -Z -m$(MODEL) $(LOC) + +LDFLAGS=-m$(MODEL) -f- + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del zlib_*.bak + -del foo.gz diff --git a/zlib/zlib/msdos/Makefile.dj2 b/zlib/zlib/msdos/Makefile.dj2 new file mode 100644 index 00000000..29b03954 --- /dev/null +++ b/zlib/zlib/msdos/Makefile.dj2 @@ -0,0 +1,104 @@ +# Makefile for zlib. Modified for djgpp v2.0 by F. J. Donahoe, 3/15/96. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.dj2; make test -fmakefile.dj2 +# +# To install libz.a, zconf.h and zlib.h in the djgpp directories, type: +# +# make install -fmakefile.dj2 +# +# after first defining LIBRARY_PATH and INCLUDE_PATH in djgpp.env as +# in the sample below if the pattern of the DJGPP distribution is to +# be followed. Remember that, while 'es around <=> are ignored in +# makefiles, they are *not* in batch files or in djgpp.env. +# - - - - - +# [make] +# INCLUDE_PATH=%\>;INCLUDE_PATH%%\DJDIR%\include +# LIBRARY_PATH=%\>;LIBRARY_PATH%%\DJDIR%\lib +# BUTT=-m486 +# - - - - - +# Alternately, these variables may be defined below, overriding the values +# in djgpp.env, as +# INCLUDE_PATH=c:\usr\include +# LIBRARY_PATH=c:\usr\lib + +CC=gcc + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DDEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lz +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=libz.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +OBJA = +# to use the asm code: make OBJA=match.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +check: test +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +libz.a: $(OBJS) $(OBJA) + $(AR) $@ $(OBJS) $(OBJA) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + +# INCLUDE_PATH and LIBRARY_PATH were set for [make] in djgpp.env . + +.PHONY : uninstall clean + +install: $(INCL) $(LIBS) + -@if not exist $(INCLUDE_PATH)\nul mkdir $(INCLUDE_PATH) + -@if not exist $(LIBRARY_PATH)\nul mkdir $(LIBRARY_PATH) + $(INSTALL) zlib.h $(INCLUDE_PATH) + $(INSTALL) zconf.h $(INCLUDE_PATH) + $(INSTALL) libz.a $(LIBRARY_PATH) + +uninstall: + $(RM) $(INCLUDE_PATH)\zlib.h + $(RM) $(INCLUDE_PATH)\zconf.h + $(RM) $(LIBRARY_PATH)\libz.a + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) libz.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif diff --git a/zlib/zlib/msdos/Makefile.emx b/zlib/zlib/msdos/Makefile.emx new file mode 100644 index 00000000..9c1b57a5 --- /dev/null +++ b/zlib/zlib/msdos/Makefile.emx @@ -0,0 +1,69 @@ +# Makefile for zlib. Modified for emx 0.9c by Chr. Spieler, 6/17/98. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.emx; make test -fmakefile.emx +# + +CC=gcc + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DDEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lzlib +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=zlib.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +zlib.a: $(OBJS) + $(AR) $@ $(OBJS) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + + +.PHONY : clean + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) zlib.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif diff --git a/zlib/zlib/msdos/Makefile.msc b/zlib/zlib/msdos/Makefile.msc new file mode 100644 index 00000000..ae837861 --- /dev/null +++ b/zlib/zlib/msdos/Makefile.msc @@ -0,0 +1,112 @@ +# Makefile for zlib +# Microsoft C 5.1 or later +# Last updated: 19-Mar-2003 + +# To use, do "make makefile.msc" +# To compile in small model, set below: MODEL=S + +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to the LOC macro below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------- Microsoft C 5.1 and later ------------- + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or added +# to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +# Type for CPU required: 0: 8086, 1: 80186, 2: 80286, 3: 80386, etc. +CPU_TYP = 0 + +# Memory model: one of S, M, C, L (small, medium, compact, large) +MODEL=L + +CC=cl +CFLAGS=-nologo -A$(MODEL) -G$(CPU_TYP) -W3 -Oait -Gs $(LOC) +#-Ox generates bad code with MSC 5.1 +LIB_CFLAGS=-Zl $(CFLAGS) + +LD=link +LDFLAGS=/noi/e/st:0x1500/noe/farcall/packcode +# "/farcall/packcode" are only useful for `large code' memory models +# but should be a "no-op" for small code models. + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(LIB_CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +minigzip.obj: test/minigzip.c zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + if exist $(ZLIB_LIB) del $(ZLIB_LIB) + lib $(ZLIB_LIB) $(OBJ1); + lib $(ZLIB_LIB) $(OBJ2); + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj,,,$(ZLIB_LIB); + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj,,,$(ZLIB_LIB); + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del *.map + -del zlib_*.bak + -del foo.gz diff --git a/zlib/zlib/msdos/Makefile.tc b/zlib/zlib/msdos/Makefile.tc new file mode 100644 index 00000000..5aec82a9 --- /dev/null +++ b/zlib/zlib/msdos/Makefile.tc @@ -0,0 +1,100 @@ +# Makefile for zlib +# Turbo C 2.01, Turbo C++ 1.01 +# Last updated: 15-Mar-2003 + +# To use, do "make -fmakefile.tc" +# To compile in small model, set below: MODEL=s + +# WARNING: the small model is supported but only for small values of +# MAX_WBITS and MAX_MEM_LEVEL. For example: +# -DMAX_WBITS=11 -DMAX_MEM_LEVEL=3 +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to CFLAGS below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------ Turbo C 2.01, Turbo C++ 1.01 ------------ +MODEL=l +CC=tcc +LD=tcc +AR=tlib +# CFLAGS=-O2 -G -Z -m$(MODEL) -DMAX_WBITS=11 -DMAX_MEM_LEVEL=3 +CFLAGS=-O2 -G -Z -m$(MODEL) +LDFLAGS=-m$(MODEL) -f- + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del zlib_*.bak + -del foo.gz diff --git a/zlib/zlib/nintendods/Makefile b/zlib/zlib/nintendods/Makefile new file mode 100644 index 00000000..21337d01 --- /dev/null +++ b/zlib/zlib/nintendods/Makefile @@ -0,0 +1,126 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(shell basename $(CURDIR)) +BUILD := build +SOURCES := ../../ +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -mthumb -mthumb-interwork + +CFLAGS := -Wall -O2\ + -march=armv5te -mtune=arm946e-s \ + -fomit-frame-pointer -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM9 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := $(ARCH) -march=armv5te -mtune=arm946e-s +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/lib/libz.a + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + @[ -d $@ ] || mkdir -p include + @cp ../../*.h include + +lib: + @[ -d $@ ] || mkdir -p $@ + +$(BUILD): lib + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/zlib/zlib/nintendods/README b/zlib/zlib/nintendods/README new file mode 100644 index 00000000..ba7a37db --- /dev/null +++ b/zlib/zlib/nintendods/README @@ -0,0 +1,5 @@ +This Makefile requires devkitARM (http://www.devkitpro.org/category/devkitarm/) and works inside "contrib/nds". It is based on a devkitARM template. + +Eduardo Costa +January 3, 2009 + diff --git a/zlib/zlib/old/Makefile.emx b/zlib/zlib/old/Makefile.emx new file mode 100644 index 00000000..4d6ab0ef --- /dev/null +++ b/zlib/zlib/old/Makefile.emx @@ -0,0 +1,69 @@ +# Makefile for zlib. Modified for emx/rsxnt by Chr. Spieler, 6/16/98. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.emx; make test -fmakefile.emx +# + +CC=gcc -Zwin32 + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DDEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lzlib +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=zlib.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o deflate.o gzclose.o gzlib.o gzread.o \ + gzwrite.o infback.o inffast.o inflate.o inftrees.o trees.o uncompr.o zutil.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +zlib.a: $(OBJS) + $(AR) $@ $(OBJS) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + + +.PHONY : clean + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) zlib.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif diff --git a/zlib/zlib/old/Makefile.riscos b/zlib/zlib/old/Makefile.riscos new file mode 100644 index 00000000..57e29d3f --- /dev/null +++ b/zlib/zlib/old/Makefile.riscos @@ -0,0 +1,151 @@ +# Project: zlib_1_03 +# Patched for zlib 1.1.2 rw@shadow.org.uk 19980430 +# test works out-of-the-box, installs `somewhere' on demand + +# Toolflags: +CCflags = -c -depend !Depend -IC: -g -throwback -DRISCOS -fah +C++flags = -c -depend !Depend -IC: -throwback +Linkflags = -aif -c++ -o $@ +ObjAsmflags = -throwback -NoCache -depend !Depend +CMHGflags = +LibFileflags = -c -l -o $@ +Squeezeflags = -o $@ + +# change the line below to where _you_ want the library installed. +libdest = lib:zlib + +# Final targets: +@.lib: @.o.adler32 @.o.compress @.o.crc32 @.o.deflate @.o.gzio \ + @.o.infblock @.o.infcodes @.o.inffast @.o.inflate @.o.inftrees @.o.infutil @.o.trees \ + @.o.uncompr @.o.zutil + LibFile $(LibFileflags) @.o.adler32 @.o.compress @.o.crc32 @.o.deflate \ + @.o.gzio @.o.infblock @.o.infcodes @.o.inffast @.o.inflate @.o.inftrees @.o.infutil \ + @.o.trees @.o.uncompr @.o.zutil +test: @.minigzip @.example @.lib + @copy @.lib @.libc A~C~DF~L~N~P~Q~RS~TV + @echo running tests: hang on. + @/@.minigzip -f -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -f -1 libc + @/@.minigzip -d libc-gz + @/@.minigzip -h -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -h -1 libc + @/@.minigzip -d libc-gz + @/@.minigzip -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -1 libc + @/@.minigzip -d libc-gz + @diff @.lib @.libc + @echo that should have reported '@.lib and @.libc identical' if you have diff. + @/@.example @.fred @.fred + @echo that will have given lots of hello!'s. + +@.minigzip: @.o.minigzip @.lib C:o.Stubs + Link $(Linkflags) @.o.minigzip @.lib C:o.Stubs +@.example: @.o.example @.lib C:o.Stubs + Link $(Linkflags) @.o.example @.lib C:o.Stubs + +install: @.lib + cdir $(libdest) + cdir $(libdest).h + @copy @.h.zlib $(libdest).h.zlib A~C~DF~L~N~P~Q~RS~TV + @copy @.h.zconf $(libdest).h.zconf A~C~DF~L~N~P~Q~RS~TV + @copy @.lib $(libdest).lib A~C~DF~L~N~P~Q~RS~TV + @echo okay, installed zlib in $(libdest) + +clean:; remove @.minigzip + remove @.example + remove @.libc + -wipe @.o.* F~r~cV + remove @.fred + +# User-editable dependencies: +.c.o: + cc $(ccflags) -o $@ $< + +# Static dependencies: + +# Dynamic dependencies: +o.example: c.example +o.example: h.zlib +o.example: h.zconf +o.minigzip: c.minigzip +o.minigzip: h.zlib +o.minigzip: h.zconf +o.adler32: c.adler32 +o.adler32: h.zlib +o.adler32: h.zconf +o.compress: c.compress +o.compress: h.zlib +o.compress: h.zconf +o.crc32: c.crc32 +o.crc32: h.zlib +o.crc32: h.zconf +o.deflate: c.deflate +o.deflate: h.deflate +o.deflate: h.zutil +o.deflate: h.zlib +o.deflate: h.zconf +o.gzio: c.gzio +o.gzio: h.zutil +o.gzio: h.zlib +o.gzio: h.zconf +o.infblock: c.infblock +o.infblock: h.zutil +o.infblock: h.zlib +o.infblock: h.zconf +o.infblock: h.infblock +o.infblock: h.inftrees +o.infblock: h.infcodes +o.infblock: h.infutil +o.infcodes: c.infcodes +o.infcodes: h.zutil +o.infcodes: h.zlib +o.infcodes: h.zconf +o.infcodes: h.inftrees +o.infcodes: h.infblock +o.infcodes: h.infcodes +o.infcodes: h.infutil +o.infcodes: h.inffast +o.inffast: c.inffast +o.inffast: h.zutil +o.inffast: h.zlib +o.inffast: h.zconf +o.inffast: h.inftrees +o.inffast: h.infblock +o.inffast: h.infcodes +o.inffast: h.infutil +o.inffast: h.inffast +o.inflate: c.inflate +o.inflate: h.zutil +o.inflate: h.zlib +o.inflate: h.zconf +o.inflate: h.infblock +o.inftrees: c.inftrees +o.inftrees: h.zutil +o.inftrees: h.zlib +o.inftrees: h.zconf +o.inftrees: h.inftrees +o.inftrees: h.inffixed +o.infutil: c.infutil +o.infutil: h.zutil +o.infutil: h.zlib +o.infutil: h.zconf +o.infutil: h.infblock +o.infutil: h.inftrees +o.infutil: h.infcodes +o.infutil: h.infutil +o.trees: c.trees +o.trees: h.deflate +o.trees: h.zutil +o.trees: h.zlib +o.trees: h.zconf +o.trees: h.trees +o.uncompr: c.uncompr +o.uncompr: h.zlib +o.uncompr: h.zconf +o.zutil: c.zutil +o.zutil: h.zutil +o.zutil: h.zlib +o.zutil: h.zconf diff --git a/zlib/zlib/old/README b/zlib/zlib/old/README new file mode 100644 index 00000000..800bf079 --- /dev/null +++ b/zlib/zlib/old/README @@ -0,0 +1,3 @@ +This directory contains files that have not been updated for zlib 1.2.x + +(Volunteers are encouraged to help clean this up. Thanks.) diff --git a/zlib/zlib/old/descrip.mms b/zlib/zlib/old/descrip.mms new file mode 100644 index 00000000..7066da5b --- /dev/null +++ b/zlib/zlib/old/descrip.mms @@ -0,0 +1,48 @@ +# descrip.mms: MMS description file for building zlib on VMS +# written by Martin P.J. Zinser + +cc_defs = +c_deb = + +.ifdef __DECC__ +pref = /prefix=all +.endif + +OBJS = adler32.obj, compress.obj, crc32.obj, gzio.obj, uncompr.obj,\ + deflate.obj, trees.obj, zutil.obj, inflate.obj, infblock.obj,\ + inftrees.obj, infcodes.obj, infutil.obj, inffast.obj + +CFLAGS= $(C_DEB) $(CC_DEFS) $(PREF) + +all : example.exe minigzip.exe + @ write sys$output " Example applications available" +libz.olb : libz.olb($(OBJS)) + @ write sys$output " libz available" + +example.exe : example.obj libz.olb + link example,libz.olb/lib + +minigzip.exe : minigzip.obj libz.olb + link minigzip,libz.olb/lib,x11vms:xvmsutils.olb/lib + +clean : + delete *.obj;*,libz.olb;* + + +# Other dependencies. +adler32.obj : zutil.h zlib.h zconf.h +compress.obj : zlib.h zconf.h +crc32.obj : zutil.h zlib.h zconf.h +deflate.obj : deflate.h zutil.h zlib.h zconf.h +example.obj : zlib.h zconf.h +gzio.obj : zutil.h zlib.h zconf.h +infblock.obj : zutil.h zlib.h zconf.h infblock.h inftrees.h infcodes.h infutil.h +infcodes.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h infcodes.h inffast.h +inffast.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h inffast.h +inflate.obj : zutil.h zlib.h zconf.h infblock.h +inftrees.obj : zutil.h zlib.h zconf.h inftrees.h +infutil.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h +minigzip.obj : zlib.h zconf.h +trees.obj : deflate.h zutil.h zlib.h zconf.h +uncompr.obj : zlib.h zconf.h +zutil.obj : zutil.h zlib.h zconf.h diff --git a/zlib/zlib/old/os2/Makefile.os2 b/zlib/zlib/old/os2/Makefile.os2 new file mode 100644 index 00000000..a105aaa5 --- /dev/null +++ b/zlib/zlib/old/os2/Makefile.os2 @@ -0,0 +1,136 @@ +# Makefile for zlib under OS/2 using GCC (PGCC) +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# cp Makefile.os2 .. +# cd .. +# make -f Makefile.os2 test + +# This makefile will build a static library z.lib, a shared library +# z.dll and a import library zdll.lib. You can use either z.lib or +# zdll.lib by specifying either -lz or -lzdll on gcc's command line + +CC=gcc -Zomf -s + +CFLAGS=-O6 -Wall +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DDEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +#################### BUG WARNING: ##################### +## infcodes.c hits a bug in pgcc-1.0, so you have to use either +## -O# where # <= 4 or one of (-fno-ommit-frame-pointer or -fno-force-mem) +## This bug is reportedly fixed in pgcc >1.0, but this was not tested +CFLAGS+=-fno-force-mem + +LDFLAGS=-s -L. -lzdll -Zcrtdll +LDSHARED=$(CC) -s -Zomf -Zdll -Zcrtdll + +VER=1.1.0 +ZLIB=z.lib +SHAREDLIB=z.dll +SHAREDLIBIMP=zdll.lib +LIBS=$(ZLIB) $(SHAREDLIB) $(SHAREDLIBIMP) + +AR=emxomfar cr +IMPLIB=emximp +RANLIB=echo +TAR=tar +SHELL=bash + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzio.o uncompr.o deflate.o trees.o \ + zutil.o inflate.o infblock.o inftrees.o infcodes.o infutil.o inffast.o + +TEST_OBJS = example.o minigzip.o + +DISTFILES = README INDEX ChangeLog configure Make*[a-z0-9] *.[ch] descrip.mms \ + algorithm.txt zlib.3 msdos/Make*[a-z0-9] msdos/zlib.def msdos/zlib.rc \ + nt/Makefile.nt nt/zlib.dnt contrib/README.contrib contrib/*.txt \ + contrib/asm386/*.asm contrib/asm386/*.c \ + contrib/asm386/*.bat contrib/asm386/zlibvc.d?? contrib/iostream/*.cpp \ + contrib/iostream/*.h contrib/iostream2/*.h contrib/iostream2/*.cpp \ + contrib/untgz/Makefile contrib/untgz/*.c contrib/untgz/*.w32 + +all: example.exe minigzip.exe + +test: all + @LD_LIBRARY_PATH=.:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + echo hello world | ./minigzip | ./minigzip -d || \ + echo ' *** minigzip test FAILED ***' ; \ + if ./example; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; \ + fi + +$(ZLIB): $(OBJS) + $(AR) $@ $(OBJS) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +$(SHAREDLIB): $(OBJS) os2/z.def + $(LDSHARED) -o $@ $^ + +$(SHAREDLIBIMP): os2/z.def + $(IMPLIB) -o $@ $^ + +example.exe: example.o $(LIBS) + $(CC) $(CFLAGS) -o $@ example.o $(LDFLAGS) + +minigzip.exe: minigzip.o $(LIBS) + $(CC) $(CFLAGS) -o $@ minigzip.o $(LDFLAGS) + +clean: + rm -f *.o *~ example minigzip libz.a libz.so* foo.gz + +distclean: clean + +zip: + mv Makefile Makefile~; cp -p Makefile.in Makefile + rm -f test.c ztest*.c + v=`sed -n -e 's/\.//g' -e '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h`;\ + zip -ul9 zlib$$v $(DISTFILES) + mv Makefile~ Makefile + +dist: + mv Makefile Makefile~; cp -p Makefile.in Makefile + rm -f test.c ztest*.c + d=zlib-`sed -n '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h`;\ + rm -f $$d.tar.gz; \ + if test ! -d ../$$d; then rm -f ../$$d; ln -s `pwd` ../$$d; fi; \ + files=""; \ + for f in $(DISTFILES); do files="$$files $$d/$$f"; done; \ + cd ..; \ + GZIP=-9 $(TAR) chofz $$d/$$d.tar.gz $$files; \ + if test ! -d $$d; then rm -f $$d; fi + mv Makefile~ Makefile + +tags: + etags *.[ch] + +depend: + makedepend -- $(CFLAGS) -- *.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzio.o: zutil.h zlib.h zconf.h +infblock.o: infblock.h inftrees.h infcodes.h infutil.h zutil.h zlib.h zconf.h +infcodes.o: zutil.h zlib.h zconf.h +infcodes.o: inftrees.h infblock.h infcodes.h infutil.h inffast.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h +inffast.o: infblock.h infcodes.h infutil.h inffast.h +inflate.o: zutil.h zlib.h zconf.h infblock.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +infutil.o: zutil.h zlib.h zconf.h infblock.h inftrees.h infcodes.h infutil.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h diff --git a/zlib/zlib/old/os2/zlib.def b/zlib/zlib/old/os2/zlib.def new file mode 100644 index 00000000..4c753f1a --- /dev/null +++ b/zlib/zlib/old/os2/zlib.def @@ -0,0 +1,51 @@ +; +; Slightly modified version of ../nt/zlib.dnt :-) +; + +LIBRARY Z +DESCRIPTION "Zlib compression library for OS/2" +CODE PRELOAD MOVEABLE DISCARDABLE +DATA PRELOAD MOVEABLE MULTIPLE + +EXPORTS + adler32 + compress + crc32 + deflate + deflateCopy + deflateEnd + deflateInit2_ + deflateInit_ + deflateParams + deflateReset + deflateSetDictionary + gzclose + gzdopen + gzerror + gzflush + gzopen + gzread + gzwrite + inflate + inflateEnd + inflateInit2_ + inflateInit_ + inflateReset + inflateSetDictionary + inflateSync + uncompress + zlibVersion + gzprintf + gzputc + gzgetc + gzseek + gzrewind + gztell + gzeof + gzsetparams + zError + inflateSyncPoint + get_crc_table + compress2 + gzputs + gzgets diff --git a/zlib/zlib/old/visual-basic.txt b/zlib/zlib/old/visual-basic.txt new file mode 100644 index 00000000..57efe581 --- /dev/null +++ b/zlib/zlib/old/visual-basic.txt @@ -0,0 +1,160 @@ +See below some functions declarations for Visual Basic. + +Frequently Asked Question: + +Q: Each time I use the compress function I get the -5 error (not enough + room in the output buffer). + +A: Make sure that the length of the compressed buffer is passed by + reference ("as any"), not by value ("as long"). Also check that + before the call of compress this length is equal to the total size of + the compressed buffer and not zero. + + +From: "Jon Caruana" +Subject: Re: How to port zlib declares to vb? +Date: Mon, 28 Oct 1996 18:33:03 -0600 + +Got the answer! (I haven't had time to check this but it's what I got, and +looks correct): + +He has the following routines working: + compress + uncompress + gzopen + gzwrite + gzread + gzclose + +Declares follow: (Quoted from Carlos Rios , in Vb4 form) + +#If Win16 Then 'Use Win16 calls. +Declare Function compress Lib "ZLIB.DLL" (ByVal compr As + String, comprLen As Any, ByVal buf As String, ByVal buflen + As Long) As Integer +Declare Function uncompress Lib "ZLIB.DLL" (ByVal uncompr + As String, uncomprLen As Any, ByVal compr As String, ByVal + lcompr As Long) As Integer +Declare Function gzopen Lib "ZLIB.DLL" (ByVal filePath As + String, ByVal mode As String) As Long +Declare Function gzread Lib "ZLIB.DLL" (ByVal file As + Long, ByVal uncompr As String, ByVal uncomprLen As Integer) + As Integer +Declare Function gzwrite Lib "ZLIB.DLL" (ByVal file As + Long, ByVal uncompr As String, ByVal uncomprLen As Integer) + As Integer +Declare Function gzclose Lib "ZLIB.DLL" (ByVal file As + Long) As Integer +#Else +Declare Function compress Lib "ZLIB32.DLL" + (ByVal compr As String, comprLen As Any, ByVal buf As + String, ByVal buflen As Long) As Integer +Declare Function uncompress Lib "ZLIB32.DLL" + (ByVal uncompr As String, uncomprLen As Any, ByVal compr As + String, ByVal lcompr As Long) As Long +Declare Function gzopen Lib "ZLIB32.DLL" + (ByVal file As String, ByVal mode As String) As Long +Declare Function gzread Lib "ZLIB32.DLL" + (ByVal file As Long, ByVal uncompr As String, ByVal + uncomprLen As Long) As Long +Declare Function gzwrite Lib "ZLIB32.DLL" + (ByVal file As Long, ByVal uncompr As String, ByVal + uncomprLen As Long) As Long +Declare Function gzclose Lib "ZLIB32.DLL" + (ByVal file As Long) As Long +#End If + +-Jon Caruana +jon-net@usa.net +Microsoft Sitebuilder Network Level 1 Member - HTML Writer's Guild Member + + +Here is another example from Michael that he +says conforms to the VB guidelines, and that solves the problem of not +knowing the uncompressed size by storing it at the end of the file: + +'Calling the functions: +'bracket meaning: [optional] {Range of possible values} +'Call subCompressFile( [, , [level of compression {1..9}]]) +'Call subUncompressFile() + +Option Explicit +Private lngpvtPcnSml As Long 'Stores value for 'lngPercentSmaller' +Private Const SUCCESS As Long = 0 +Private Const strFilExt As String = ".cpr" +Private Declare Function lngfncCpr Lib "zlib.dll" Alias "compress2" (ByRef +dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long, +ByVal level As Integer) As Long +Private Declare Function lngfncUcp Lib "zlib.dll" Alias "uncompress" (ByRef +dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long) +As Long + +Public Sub subCompressFile(ByVal strargOriFilPth As String, Optional ByVal +strargCprFilPth As String, Optional ByVal intLvl As Integer = 9) + Dim strCprPth As String + Dim lngOriSiz As Long + Dim lngCprSiz As Long + Dim bytaryOri() As Byte + Dim bytaryCpr() As Byte + lngOriSiz = FileLen(strargOriFilPth) + ReDim bytaryOri(lngOriSiz - 1) + Open strargOriFilPth For Binary Access Read As #1 + Get #1, , bytaryOri() + Close #1 + strCprPth = IIf(strargCprFilPth = "", strargOriFilPth, strargCprFilPth) +'Select file path and name + strCprPth = strCprPth & IIf(Right(strCprPth, Len(strFilExt)) = +strFilExt, "", strFilExt) 'Add file extension if not exists + lngCprSiz = (lngOriSiz * 1.01) + 12 'Compression needs temporary a bit +more space then original file size + ReDim bytaryCpr(lngCprSiz - 1) + If lngfncCpr(bytaryCpr(0), lngCprSiz, bytaryOri(0), lngOriSiz, intLvl) = +SUCCESS Then + lngpvtPcnSml = (1# - (lngCprSiz / lngOriSiz)) * 100 + ReDim Preserve bytaryCpr(lngCprSiz - 1) + Open strCprPth For Binary Access Write As #1 + Put #1, , bytaryCpr() + Put #1, , lngOriSiz 'Add the the original size value to the end +(last 4 bytes) + Close #1 + Else + MsgBox "Compression error" + End If + Erase bytaryCpr + Erase bytaryOri +End Sub + +Public Sub subUncompressFile(ByVal strargFilPth As String) + Dim bytaryCpr() As Byte + Dim bytaryOri() As Byte + Dim lngOriSiz As Long + Dim lngCprSiz As Long + Dim strOriPth As String + lngCprSiz = FileLen(strargFilPth) + ReDim bytaryCpr(lngCprSiz - 1) + Open strargFilPth For Binary Access Read As #1 + Get #1, , bytaryCpr() + Close #1 + 'Read the original file size value: + lngOriSiz = bytaryCpr(lngCprSiz - 1) * (2 ^ 24) _ + + bytaryCpr(lngCprSiz - 2) * (2 ^ 16) _ + + bytaryCpr(lngCprSiz - 3) * (2 ^ 8) _ + + bytaryCpr(lngCprSiz - 4) + ReDim Preserve bytaryCpr(lngCprSiz - 5) 'Cut of the original size value + ReDim bytaryOri(lngOriSiz - 1) + If lngfncUcp(bytaryOri(0), lngOriSiz, bytaryCpr(0), lngCprSiz) = SUCCESS +Then + strOriPth = Left(strargFilPth, Len(strargFilPth) - Len(strFilExt)) + Open strOriPth For Binary Access Write As #1 + Put #1, , bytaryOri() + Close #1 + Else + MsgBox "Uncompression error" + End If + Erase bytaryCpr + Erase bytaryOri +End Sub +Public Property Get lngPercentSmaller() As Long + lngPercentSmaller = lngpvtPcnSml +End Property diff --git a/zlib/zlib/qnx/package.qpg b/zlib/zlib/qnx/package.qpg new file mode 100644 index 00000000..aebf6e3a --- /dev/null +++ b/zlib/zlib/qnx/package.qpg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Library + + Medium + + 2.0 + + + + zlib + zlib + alain.bonnefoy@icbt.com + Public + public + www.gzip.org/zlib + + + Jean-Loup Gailly,Mark Adler + www.gzip.org/zlib + + zlib@gzip.org + + + A massively spiffy yet delicately unobtrusive compression library. + zlib is designed to be a free, general-purpose, legally unencumbered, lossless data compression library for use on virtually any computer hardware and operating system. + http://www.gzip.org/zlib + + + + + 1.2.8 + Medium + Stable + + + + + + + No License + + + + Software Development/Libraries and Extensions/C Libraries + zlib,compression + qnx6 + qnx6 + None + Developer + + + + + + + + + + + + + + Install + Post + No + Ignore + + No + Optional + + + + + + + + + + + + + InstallOver + zlib + + + + + + + + + + + + + InstallOver + zlib-dev + + + + + + + + + diff --git a/zlib/zlib/test/example.c b/zlib/zlib/test/example.c new file mode 100644 index 00000000..138a699b --- /dev/null +++ b/zlib/zlib/test/example.c @@ -0,0 +1,601 @@ +/* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2006, 2011 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zlib.h" +#include + +#ifdef STDC +# include +# include +#endif + +#if defined(VMS) || defined(RISCOS) +# define TESTFILE "foo-gz" +#else +# define TESTFILE "foo.gz" +#endif + +#define CHECK_ERR(err, msg) { \ + if (err != Z_OK) { \ + fprintf(stderr, "%s error: %d\n", msg, err); \ + exit(1); \ + } \ +} + +z_const char hello[] = "hello, hello!"; +/* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + */ + +const char dictionary[] = "hello"; +uLong dictId; /* Adler32 value of the dictionary */ + +void test_deflate OF((Byte *compr, uLong comprLen)); +void test_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_deflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_flush OF((Byte *compr, uLong *comprLen)); +void test_sync OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_dict_deflate OF((Byte *compr, uLong comprLen)); +void test_dict_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +int main OF((int argc, char *argv[])); + + +#ifdef Z_SOLO + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + q = Z_NULL; + return calloc(n, m); +} + +void myfree(void *q, void *p) +{ + q = Z_NULL; + free(p); +} + +static alloc_func zalloc = myalloc; +static free_func zfree = myfree; + +#else /* !Z_SOLO */ + +static alloc_func zalloc = (alloc_func)0; +static free_func zfree = (free_func)0; + +void test_compress OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_gzio OF((const char *fname, + Byte *uncompr, uLong uncomprLen)); + +/* =========================================================================== + * Test compress() and uncompress() + */ +void test_compress(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + uLong len = (uLong)strlen(hello)+1; + + err = compress(compr, &comprLen, (const Bytef*)hello, len); + CHECK_ERR(err, "compress"); + + strcpy((char*)uncompr, "garbage"); + + err = uncompress(uncompr, &uncomprLen, compr, comprLen); + CHECK_ERR(err, "uncompress"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad uncompress\n"); + exit(1); + } else { + printf("uncompress(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test read/write of .gz files + */ +void test_gzio(fname, uncompr, uncomprLen) + const char *fname; /* compressed file name */ + Byte *uncompr; + uLong uncomprLen; +{ +#ifdef NO_GZCOMPRESS + fprintf(stderr, "NO_GZCOMPRESS -- gz* functions cannot compress\n"); +#else + int err; + int len = (int)strlen(hello)+1; + gzFile file; + z_off_t pos; + + file = gzopen(fname, "wb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + gzputc(file, 'h'); + if (gzputs(file, "ello") != 4) { + fprintf(stderr, "gzputs err: %s\n", gzerror(file, &err)); + exit(1); + } + if (gzprintf(file, ", %s!", "hello") != 8) { + fprintf(stderr, "gzprintf err: %s\n", gzerror(file, &err)); + exit(1); + } + gzseek(file, 1L, SEEK_CUR); /* add one zero byte */ + gzclose(file); + + file = gzopen(fname, "rb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + strcpy((char*)uncompr, "garbage"); + + if (gzread(file, uncompr, (unsigned)uncomprLen) != len) { + fprintf(stderr, "gzread err: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad gzread: %s\n", (char*)uncompr); + exit(1); + } else { + printf("gzread(): %s\n", (char*)uncompr); + } + + pos = gzseek(file, -8L, SEEK_CUR); + if (pos != 6 || gztell(file) != pos) { + fprintf(stderr, "gzseek error, pos=%ld, gztell=%ld\n", + (long)pos, (long)gztell(file)); + exit(1); + } + + if (gzgetc(file) != ' ') { + fprintf(stderr, "gzgetc error\n"); + exit(1); + } + + if (gzungetc(' ', file) != ' ') { + fprintf(stderr, "gzungetc error\n"); + exit(1); + } + + gzgets(file, (char*)uncompr, (int)uncomprLen); + if (strlen((char*)uncompr) != 7) { /* " hello!" */ + fprintf(stderr, "gzgets err after gzseek: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello + 6)) { + fprintf(stderr, "bad gzgets after gzseek\n"); + exit(1); + } else { + printf("gzgets() after gzseek: %s\n", (char*)uncompr); + } + + gzclose(file); +#endif +} + +#endif /* Z_SOLO */ + +/* =========================================================================== + * Test deflate() with small buffers + */ +void test_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uLong len = (uLong)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + + while (c_stream.total_in != len && c_stream.total_out < comprLen) { + c_stream.avail_in = c_stream.avail_out = 1; /* force small buffers */ + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + } + /* Finish the stream, still forcing small buffers: */ + for (;;) { + c_stream.avail_out = 1; + err = deflate(&c_stream, Z_FINISH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "deflate"); + } + + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with small buffers + */ +void test_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 0; + d_stream.next_out = uncompr; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + while (d_stream.total_out < uncomprLen && d_stream.total_in < comprLen) { + d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */ + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate\n"); + exit(1); + } else { + printf("inflate(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test deflate() with large buffers and dynamic change of compression level + */ +void test_large_deflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_SPEED); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + /* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + */ + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + if (c_stream.avail_in != 0) { + fprintf(stderr, "deflate not greedy\n"); + exit(1); + } + + /* Feed in already compressed data and switch to no compression: */ + deflateParams(&c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in = compr; + c_stream.avail_in = (uInt)comprLen/2; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + /* Switch back to compressing mode: */ + deflateParams(&c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with large buffers + */ +void test_large_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + for (;;) { + d_stream.next_out = uncompr; /* discard the output */ + d_stream.avail_out = (uInt)uncomprLen; + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "large inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (d_stream.total_out != 2*uncomprLen + comprLen/2) { + fprintf(stderr, "bad large inflate: %ld\n", d_stream.total_out); + exit(1); + } else { + printf("large_inflate(): OK\n"); + } +} + +/* =========================================================================== + * Test deflate() with full flush + */ +void test_flush(compr, comprLen) + Byte *compr; + uLong *comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uInt len = (uInt)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + c_stream.avail_in = 3; + c_stream.avail_out = (uInt)*comprLen; + err = deflate(&c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, "deflate"); + + compr[3]++; /* force an error in first compressed block */ + c_stream.avail_in = len - 3; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + CHECK_ERR(err, "deflate"); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); + + *comprLen = c_stream.total_out; +} + +/* =========================================================================== + * Test inflateSync() + */ +void test_sync(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 2; /* just read the zlib header */ + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + inflate(&d_stream, Z_NO_FLUSH); + CHECK_ERR(err, "inflate"); + + d_stream.avail_in = (uInt)comprLen-2; /* read all compressed data */ + err = inflateSync(&d_stream); /* but skip the damaged part */ + CHECK_ERR(err, "inflateSync"); + + err = inflate(&d_stream, Z_FINISH); + if (err != Z_DATA_ERROR) { + fprintf(stderr, "inflate should report DATA_ERROR\n"); + /* Because of incorrect adler32 */ + exit(1); + } + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + printf("after inflateSync(): hel%s\n", (char *)uncompr); +} + +/* =========================================================================== + * Test deflate() with preset dictionary + */ +void test_dict_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + err = deflateSetDictionary(&c_stream, + (const Bytef*)dictionary, (int)sizeof(dictionary)); + CHECK_ERR(err, "deflateSetDictionary"); + + dictId = c_stream.adler; + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.avail_in = (uInt)strlen(hello)+1; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with a preset dictionary + */ +void test_dict_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + for (;;) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + if (err == Z_NEED_DICT) { + if (d_stream.adler != dictId) { + fprintf(stderr, "unexpected dictionary"); + exit(1); + } + err = inflateSetDictionary(&d_stream, (const Bytef*)dictionary, + (int)sizeof(dictionary)); + } + CHECK_ERR(err, "inflate with dict"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate with dict\n"); + exit(1); + } else { + printf("inflate with dictionary: %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Usage: example [output.gz [input.gz]] + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + Byte *compr, *uncompr; + uLong comprLen = 10000*sizeof(int); /* don't overflow on MSDOS */ + uLong uncomprLen = comprLen; + static const char* myVersion = ZLIB_VERSION; + + if (zlibVersion()[0] != myVersion[0]) { + fprintf(stderr, "incompatible zlib version\n"); + exit(1); + + } else if (strcmp(zlibVersion(), ZLIB_VERSION) != 0) { + fprintf(stderr, "warning: different zlib version\n"); + } + + printf("zlib version %s = 0x%04x, compile flags = 0x%lx\n", + ZLIB_VERSION, ZLIB_VERNUM, zlibCompileFlags()); + + compr = (Byte*)calloc((uInt)comprLen, 1); + uncompr = (Byte*)calloc((uInt)uncomprLen, 1); + /* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + */ + if (compr == Z_NULL || uncompr == Z_NULL) { + printf("out of memory\n"); + exit(1); + } + +#ifdef Z_SOLO + argc = strlen(argv[0]); +#else + test_compress(compr, comprLen, uncompr, uncomprLen); + + test_gzio((argc > 1 ? argv[1] : TESTFILE), + uncompr, uncomprLen); +#endif + + test_deflate(compr, comprLen); + test_inflate(compr, comprLen, uncompr, uncomprLen); + + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + + test_flush(compr, &comprLen); + test_sync(compr, comprLen, uncompr, uncomprLen); + comprLen = uncomprLen; + + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + + free(compr); + free(uncompr); + + return 0; +} diff --git a/zlib/zlib/test/infcover.c b/zlib/zlib/test/infcover.c new file mode 100644 index 00000000..fe3d9203 --- /dev/null +++ b/zlib/zlib/test/infcover.c @@ -0,0 +1,671 @@ +/* infcover.c -- test zlib's inflate routines with full code coverage + * Copyright (C) 2011 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* to use, do: ./configure --cover && make cover */ + +#include +#include +#include +#include +#include "zlib.h" + +/* get definition of internal structure so we can mess with it (see pull()), + and so we can call inflate_trees() (see cover5()) */ +#define ZLIB_INTERNAL +#include "inftrees.h" +#include "inflate.h" + +#define local static + +/* -- memory tracking routines -- */ + +/* + These memory tracking routines are provided to zlib and track all of zlib's + allocations and deallocations, check for LIFO operations, keep a current + and high water mark of total bytes requested, optionally set a limit on the + total memory that can be allocated, and when done check for memory leaks. + + They are used as follows: + + z_stream strm; + mem_setup(&strm) initializes the memory tracking and sets the + zalloc, zfree, and opaque members of strm to use + memory tracking for all zlib operations on strm + mem_limit(&strm, limit) sets a limit on the total bytes requested -- a + request that exceeds this limit will result in an + allocation failure (returns NULL) -- setting the + limit to zero means no limit, which is the default + after mem_setup() + mem_used(&strm, "msg") prints to stderr "msg" and the total bytes used + mem_high(&strm, "msg") prints to stderr "msg" and the high water mark + mem_done(&strm, "msg") ends memory tracking, releases all allocations + for the tracking as well as leaked zlib blocks, if + any. If there was anything unusual, such as leaked + blocks, non-FIFO frees, or frees of addresses not + allocated, then "msg" and information about the + problem is printed to stderr. If everything is + normal, nothing is printed. mem_done resets the + strm members to Z_NULL to use the default memory + allocation routines on the next zlib initialization + using strm. + */ + +/* these items are strung together in a linked list, one for each allocation */ +struct mem_item { + void *ptr; /* pointer to allocated memory */ + size_t size; /* requested size of allocation */ + struct mem_item *next; /* pointer to next item in list, or NULL */ +}; + +/* this structure is at the root of the linked list, and tracks statistics */ +struct mem_zone { + struct mem_item *first; /* pointer to first item in list, or NULL */ + size_t total, highwater; /* total allocations, and largest total */ + size_t limit; /* memory allocation limit, or 0 if no limit */ + int notlifo, rogue; /* counts of non-LIFO frees and rogue frees */ +}; + +/* memory allocation routine to pass to zlib */ +local void *mem_alloc(void *mem, unsigned count, unsigned size) +{ + void *ptr; + struct mem_item *item; + struct mem_zone *zone = mem; + size_t len = count * (size_t)size; + + /* induced allocation failure */ + if (zone == NULL || (zone->limit && zone->total + len > zone->limit)) + return NULL; + + /* perform allocation using the standard library, fill memory with a + non-zero value to make sure that the code isn't depending on zeros */ + ptr = malloc(len); + if (ptr == NULL) + return NULL; + memset(ptr, 0xa5, len); + + /* create a new item for the list */ + item = malloc(sizeof(struct mem_item)); + if (item == NULL) { + free(ptr); + return NULL; + } + item->ptr = ptr; + item->size = len; + + /* insert item at the beginning of the list */ + item->next = zone->first; + zone->first = item; + + /* update the statistics */ + zone->total += item->size; + if (zone->total > zone->highwater) + zone->highwater = zone->total; + + /* return the allocated memory */ + return ptr; +} + +/* memory free routine to pass to zlib */ +local void mem_free(void *mem, void *ptr) +{ + struct mem_item *item, *next; + struct mem_zone *zone = mem; + + /* if no zone, just do a free */ + if (zone == NULL) { + free(ptr); + return; + } + + /* point next to the item that matches ptr, or NULL if not found -- remove + the item from the linked list if found */ + next = zone->first; + if (next) { + if (next->ptr == ptr) + zone->first = next->next; /* first one is it, remove from list */ + else { + do { /* search the linked list */ + item = next; + next = item->next; + } while (next != NULL && next->ptr != ptr); + if (next) { /* if found, remove from linked list */ + item->next = next->next; + zone->notlifo++; /* not a LIFO free */ + } + + } + } + + /* if found, update the statistics and free the item */ + if (next) { + zone->total -= next->size; + free(next); + } + + /* if not found, update the rogue count */ + else + zone->rogue++; + + /* in any case, do the requested free with the standard library function */ + free(ptr); +} + +/* set up a controlled memory allocation space for monitoring, set the stream + parameters to the controlled routines, with opaque pointing to the space */ +local void mem_setup(z_stream *strm) +{ + struct mem_zone *zone; + + zone = malloc(sizeof(struct mem_zone)); + assert(zone != NULL); + zone->first = NULL; + zone->total = 0; + zone->highwater = 0; + zone->limit = 0; + zone->notlifo = 0; + zone->rogue = 0; + strm->opaque = zone; + strm->zalloc = mem_alloc; + strm->zfree = mem_free; +} + +/* set a limit on the total memory allocation, or 0 to remove the limit */ +local void mem_limit(z_stream *strm, size_t limit) +{ + struct mem_zone *zone = strm->opaque; + + zone->limit = limit; +} + +/* show the current total requested allocations in bytes */ +local void mem_used(z_stream *strm, char *prefix) +{ + struct mem_zone *zone = strm->opaque; + + fprintf(stderr, "%s: %lu allocated\n", prefix, zone->total); +} + +/* show the high water allocation in bytes */ +local void mem_high(z_stream *strm, char *prefix) +{ + struct mem_zone *zone = strm->opaque; + + fprintf(stderr, "%s: %lu high water mark\n", prefix, zone->highwater); +} + +/* release the memory allocation zone -- if there are any surprises, notify */ +local void mem_done(z_stream *strm, char *prefix) +{ + int count = 0; + struct mem_item *item, *next; + struct mem_zone *zone = strm->opaque; + + /* show high water mark */ + mem_high(strm, prefix); + + /* free leftover allocations and item structures, if any */ + item = zone->first; + while (item != NULL) { + free(item->ptr); + next = item->next; + free(item); + item = next; + count++; + } + + /* issue alerts about anything unexpected */ + if (count || zone->total) + fprintf(stderr, "** %s: %lu bytes in %d blocks not freed\n", + prefix, zone->total, count); + if (zone->notlifo) + fprintf(stderr, "** %s: %d frees not LIFO\n", prefix, zone->notlifo); + if (zone->rogue) + fprintf(stderr, "** %s: %d frees not recognized\n", + prefix, zone->rogue); + + /* free the zone and delete from the stream */ + free(zone); + strm->opaque = Z_NULL; + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; +} + +/* -- inflate test routines -- */ + +/* Decode a hexadecimal string, set *len to length, in[] to the bytes. This + decodes liberally, in that hex digits can be adjacent, in which case two in + a row writes a byte. Or they can delimited by any non-hex character, where + the delimiters are ignored except when a single hex digit is followed by a + delimiter in which case that single digit writes a byte. The returned + data is allocated and must eventually be freed. NULL is returned if out of + memory. If the length is not needed, then len can be NULL. */ +local unsigned char *h2b(const char *hex, unsigned *len) +{ + unsigned char *in; + unsigned next, val; + + in = malloc((strlen(hex) + 1) >> 1); + if (in == NULL) + return NULL; + next = 0; + val = 1; + do { + if (*hex >= '0' && *hex <= '9') + val = (val << 4) + *hex - '0'; + else if (*hex >= 'A' && *hex <= 'F') + val = (val << 4) + *hex - 'A' + 10; + else if (*hex >= 'a' && *hex <= 'f') + val = (val << 4) + *hex - 'a' + 10; + else if (val != 1 && val < 32) /* one digit followed by delimiter */ + val += 240; /* make it look like two digits */ + if (val > 255) { /* have two digits */ + in[next++] = val & 0xff; /* save the decoded byte */ + val = 1; /* start over */ + } + } while (*hex++); /* go through the loop with the terminating null */ + if (len != NULL) + *len = next; + in = reallocf(in, next); + return in; +} + +/* generic inflate() run, where hex is the hexadecimal input data, what is the + text to include in an error message, step is how much input data to feed + inflate() on each call, or zero to feed it all, win is the window bits + parameter to inflateInit2(), len is the size of the output buffer, and err + is the error code expected from the first inflate() call (the second + inflate() call is expected to return Z_STREAM_END). If win is 47, then + header information is collected with inflateGetHeader(). If a zlib stream + is looking for a dictionary, then an empty dictionary is provided. + inflate() is run until all of the input data is consumed. */ +local void inf(char *hex, char *what, unsigned step, int win, unsigned len, + int err) +{ + int ret; + unsigned have; + unsigned char *in, *out; + z_stream strm, copy; + gz_header head; + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, win); + if (ret != Z_OK) { + mem_done(&strm, what); + return; + } + out = malloc(len); assert(out != NULL); + if (win == 47) { + head.extra = out; + head.extra_max = len; + head.name = out; + head.name_max = len; + head.comment = out; + head.comm_max = len; + ret = inflateGetHeader(&strm, &head); assert(ret == Z_OK); + } + in = h2b(hex, &have); assert(in != NULL); + if (step == 0 || step > have) + step = have; + strm.avail_in = step; + have -= step; + strm.next_in = in; + do { + strm.avail_out = len; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); assert(err == 9 || ret == err); + if (ret != Z_OK && ret != Z_BUF_ERROR && ret != Z_NEED_DICT) + break; + if (ret == Z_NEED_DICT) { + ret = inflateSetDictionary(&strm, in, 1); + assert(ret == Z_DATA_ERROR); + mem_limit(&strm, 1); + ret = inflateSetDictionary(&strm, out, 0); + assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + ((struct inflate_state *)strm.state)->mode = DICT; + ret = inflateSetDictionary(&strm, out, 0); + assert(ret == Z_OK); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_BUF_ERROR); + } + ret = inflateCopy(©, &strm); assert(ret == Z_OK); + ret = inflateEnd(©); assert(ret == Z_OK); + err = 9; /* don't care next time around */ + have += strm.avail_in; + strm.avail_in = step > have ? have : step; + have -= strm.avail_in; + } while (strm.avail_in); + free(in); + free(out); + ret = inflateReset2(&strm, -8); assert(ret == Z_OK); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, what); +} + +/* cover all of the lines in inflate.c up to inflate() */ +local void cover_support(void) +{ + int ret; + z_stream strm; + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); assert(ret == Z_OK); + mem_used(&strm, "inflate init"); + ret = inflatePrime(&strm, 5, 31); assert(ret == Z_OK); + ret = inflatePrime(&strm, -1, 0); assert(ret == Z_OK); + ret = inflateSetDictionary(&strm, Z_NULL, 0); + assert(ret == Z_STREAM_ERROR); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "prime"); + + inf("63 0", "force window allocation", 0, -15, 1, Z_OK); + inf("63 18 5", "force window replacement", 0, -8, 259, Z_OK); + inf("63 18 68 30 d0 0 0", "force split window update", 4, -8, 259, Z_OK); + inf("3 0", "use fixed blocks", 0, -15, 1, Z_STREAM_END); + inf("", "bad window size", 0, 1, 0, Z_STREAM_ERROR); + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit_(&strm, ZLIB_VERSION - 1, (int)sizeof(z_stream)); + assert(ret == Z_VERSION_ERROR); + mem_done(&strm, "wrong version"); + + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); assert(ret == Z_OK); + ret = inflateEnd(&strm); assert(ret == Z_OK); + fputs("inflate built-in memory routines\n", stderr); +} + +/* cover all inflate() header and trailer cases and code after inflate() */ +local void cover_wrap(void) +{ + int ret; + z_stream strm, copy; + unsigned char dict[257]; + + ret = inflate(Z_NULL, 0); assert(ret == Z_STREAM_ERROR); + ret = inflateEnd(Z_NULL); assert(ret == Z_STREAM_ERROR); + ret = inflateCopy(Z_NULL, Z_NULL); assert(ret == Z_STREAM_ERROR); + fputs("inflate bad parameters\n", stderr); + + inf("1f 8b 0 0", "bad gzip method", 0, 31, 0, Z_DATA_ERROR); + inf("1f 8b 8 80", "bad gzip flags", 0, 31, 0, Z_DATA_ERROR); + inf("77 85", "bad zlib method", 0, 15, 0, Z_DATA_ERROR); + inf("8 99", "set window size from header", 0, 0, 0, Z_OK); + inf("78 9c", "bad zlib window size", 0, 8, 0, Z_DATA_ERROR); + inf("78 9c 63 0 0 0 1 0 1", "check adler32", 0, 15, 1, Z_STREAM_END); + inf("1f 8b 8 1e 0 0 0 0 0 0 1 0 0 0 0 0 0", "bad header crc", 0, 47, 1, + Z_DATA_ERROR); + inf("1f 8b 8 2 0 0 0 0 0 0 1d 26 3 0 0 0 0 0 0 0 0 0", "check gzip length", + 0, 47, 0, Z_STREAM_END); + inf("78 90", "bad zlib header check", 0, 47, 0, Z_DATA_ERROR); + inf("8 b8 0 0 0 1", "need dictionary", 0, 8, 0, Z_NEED_DICT); + inf("78 9c 63 0", "compute adler32", 0, 15, 1, Z_OK); + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -8); + strm.avail_in = 2; + strm.next_in = (void *)"\x63"; + strm.avail_out = 1; + strm.next_out = (void *)&ret; + mem_limit(&strm, 1); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_MEM_ERROR); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + memset(dict, 0, 257); + ret = inflateSetDictionary(&strm, dict, 257); + assert(ret == Z_OK); + mem_limit(&strm, (sizeof(struct inflate_state) << 1) + 256); + ret = inflatePrime(&strm, 16, 0); assert(ret == Z_OK); + strm.avail_in = 2; + strm.next_in = (void *)"\x80"; + ret = inflateSync(&strm); assert(ret == Z_DATA_ERROR); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_STREAM_ERROR); + strm.avail_in = 4; + strm.next_in = (void *)"\0\0\xff\xff"; + ret = inflateSync(&strm); assert(ret == Z_OK); + (void)inflateSyncPoint(&strm); + ret = inflateCopy(©, &strm); assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + ret = inflateUndermine(&strm, 1); assert(ret == Z_DATA_ERROR); + (void)inflateMark(&strm); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "miscellaneous, force memory errors"); +} + +/* input and output functions for inflateBack() */ +local unsigned pull(void *desc, unsigned char **buf) +{ + static unsigned int next = 0; + static unsigned char dat[] = {0x63, 0, 2, 0}; + struct inflate_state *state; + + if (desc == Z_NULL) { + next = 0; + return 0; /* no input (already provided at next_in) */ + } + state = (void *)((z_stream *)desc)->state; + if (state != Z_NULL) + state->mode = SYNC; /* force an otherwise impossible situation */ + return next < sizeof(dat) ? (*buf = dat + next++, 1) : 0; +} + +local int push(void *desc, unsigned char *buf, unsigned len) +{ + buf += len; + return desc != Z_NULL; /* force error if desc not null */ +} + +/* cover inflateBack() up to common deflate data cases and after those */ +local void cover_back(void) +{ + int ret; + z_stream strm; + unsigned char win[32768]; + + ret = inflateBackInit_(Z_NULL, 0, win, 0, 0); + assert(ret == Z_VERSION_ERROR); + ret = inflateBackInit(Z_NULL, 0, win); assert(ret == Z_STREAM_ERROR); + ret = inflateBack(Z_NULL, Z_NULL, Z_NULL, Z_NULL, Z_NULL); + assert(ret == Z_STREAM_ERROR); + ret = inflateBackEnd(Z_NULL); assert(ret == Z_STREAM_ERROR); + fputs("inflateBack bad parameters\n", stderr); + + mem_setup(&strm); + ret = inflateBackInit(&strm, 15, win); assert(ret == Z_OK); + strm.avail_in = 2; + strm.next_in = (void *)"\x03"; + ret = inflateBack(&strm, pull, Z_NULL, push, Z_NULL); + assert(ret == Z_STREAM_END); + /* force output error */ + strm.avail_in = 3; + strm.next_in = (void *)"\x63\x00"; + ret = inflateBack(&strm, pull, Z_NULL, push, &strm); + assert(ret == Z_BUF_ERROR); + /* force mode error by mucking with state */ + ret = inflateBack(&strm, pull, &strm, push, Z_NULL); + assert(ret == Z_STREAM_ERROR); + ret = inflateBackEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "inflateBack bad state"); + + ret = inflateBackInit(&strm, 15, win); assert(ret == Z_OK); + ret = inflateBackEnd(&strm); assert(ret == Z_OK); + fputs("inflateBack built-in memory routines\n", stderr); +} + +/* do a raw inflate of data in hexadecimal with both inflate and inflateBack */ +local int try(char *hex, char *id, int err) +{ + int ret; + unsigned len, size; + unsigned char *in, *out, *win; + char *prefix; + z_stream strm; + + /* convert to hex */ + in = h2b(hex, &len); + assert(in != NULL); + + /* allocate work areas */ + size = len << 3; + out = malloc(size); + assert(out != NULL); + win = malloc(32768); + assert(win != NULL); + prefix = malloc(strlen(id) + 6); + assert(prefix != NULL); + + /* first with inflate */ + strcpy(prefix, id); + strcat(prefix, "-late"); + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, err < 0 ? 47 : -15); + assert(ret == Z_OK); + strm.avail_in = len; + strm.next_in = in; + do { + strm.avail_out = size; + strm.next_out = out; + ret = inflate(&strm, Z_TREES); + assert(ret != Z_STREAM_ERROR && ret != Z_MEM_ERROR); + if (ret == Z_DATA_ERROR || ret == Z_NEED_DICT) + break; + } while (strm.avail_in || strm.avail_out == 0); + if (err) { + assert(ret == Z_DATA_ERROR); + assert(strcmp(id, strm.msg) == 0); + } + inflateEnd(&strm); + mem_done(&strm, prefix); + + /* then with inflateBack */ + if (err >= 0) { + strcpy(prefix, id); + strcat(prefix, "-back"); + mem_setup(&strm); + ret = inflateBackInit(&strm, 15, win); + assert(ret == Z_OK); + strm.avail_in = len; + strm.next_in = in; + ret = inflateBack(&strm, pull, Z_NULL, push, Z_NULL); + assert(ret != Z_STREAM_ERROR); + if (err) { + assert(ret == Z_DATA_ERROR); + assert(strcmp(id, strm.msg) == 0); + } + inflateBackEnd(&strm); + mem_done(&strm, prefix); + } + + /* clean up */ + free(prefix); + free(win); + free(out); + free(in); + return ret; +} + +/* cover deflate data cases in both inflate() and inflateBack() */ +local void cover_inflate(void) +{ + try("0 0 0 0 0", "invalid stored block lengths", 1); + try("3 0", "fixed", 0); + try("6", "invalid block type", 1); + try("1 1 0 fe ff 0", "stored", 0); + try("fc 0 0", "too many length or distance symbols", 1); + try("4 0 fe ff", "invalid code lengths set", 1); + try("4 0 24 49 0", "invalid bit length repeat", 1); + try("4 0 24 e9 ff ff", "invalid bit length repeat", 1); + try("4 0 24 e9 ff 6d", "invalid code -- missing end-of-block", 1); + try("4 80 49 92 24 49 92 24 71 ff ff 93 11 0", + "invalid literal/lengths set", 1); + try("4 80 49 92 24 49 92 24 f b4 ff ff c3 84", "invalid distances set", 1); + try("4 c0 81 8 0 0 0 0 20 7f eb b 0 0", "invalid literal/length code", 1); + try("2 7e ff ff", "invalid distance code", 1); + try("c c0 81 0 0 0 0 0 90 ff 6b 4 0", "invalid distance too far back", 1); + + /* also trailer mismatch just in inflate() */ + try("1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 1", "incorrect data check", -1); + try("1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 1", + "incorrect length check", -1); + try("5 c0 21 d 0 0 0 80 b0 fe 6d 2f 91 6c", "pull 17", 0); + try("5 e0 81 91 24 cb b2 2c 49 e2 f 2e 8b 9a 47 56 9f fb fe ec d2 ff 1f", + "long code", 0); + try("ed c0 1 1 0 0 0 40 20 ff 57 1b 42 2c 4f", "length extra", 0); + try("ed cf c1 b1 2c 47 10 c4 30 fa 6f 35 1d 1 82 59 3d fb be 2e 2a fc f c", + "long distance and extra", 0); + try("ed c0 81 0 0 0 0 80 a0 fd a9 17 a9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6", "window end", 0); + inf("2 8 20 80 0 3 0", "inflate_fast TYPE return", 0, -15, 258, + Z_STREAM_END); + inf("63 18 5 40 c 0", "window wrap", 3, -8, 300, Z_OK); +} + +/* cover remaining lines in inftrees.c */ +local void cover_trees(void) +{ + int ret; + unsigned bits; + unsigned short lens[16], work[16]; + code *next, table[ENOUGH_DISTS]; + + /* we need to call inflate_table() directly in order to manifest not- + enough errors, since zlib insures that enough is always enough */ + for (bits = 0; bits < 15; bits++) + lens[bits] = (unsigned short)(bits + 1); + lens[15] = 15; + next = table; + bits = 15; + ret = inflate_table(DISTS, lens, 16, &next, &bits, work); + assert(ret == 1); + next = table; + bits = 1; + ret = inflate_table(DISTS, lens, 16, &next, &bits, work); + assert(ret == 1); + fputs("inflate_table not enough errors\n", stderr); +} + +/* cover remaining inffast.c decoding and window copying */ +local void cover_fast(void) +{ + inf("e5 e0 81 ad 6d cb b2 2c c9 01 1e 59 63 ae 7d ee fb 4d fd b5 35 41 68" + " ff 7f 0f 0 0 0", "fast length extra bits", 0, -8, 258, Z_DATA_ERROR); + inf("25 fd 81 b5 6d 59 b6 6a 49 ea af 35 6 34 eb 8c b9 f6 b9 1e ef 67 49" + " 50 fe ff ff 3f 0 0", "fast distance extra bits", 0, -8, 258, + Z_DATA_ERROR); + inf("3 7e 0 0 0 0 0", "fast invalid distance code", 0, -8, 258, + Z_DATA_ERROR); + inf("1b 7 0 0 0 0 0", "fast invalid literal/length code", 0, -8, 258, + Z_DATA_ERROR); + inf("d c7 1 ae eb 38 c 4 41 a0 87 72 de df fb 1f b8 36 b1 38 5d ff ff 0", + "fast 2nd level codes and too far back", 0, -8, 258, Z_DATA_ERROR); + inf("63 18 5 8c 10 8 0 0 0 0", "very common case", 0, -8, 259, Z_OK); + inf("63 60 60 18 c9 0 8 18 18 18 26 c0 28 0 29 0 0 0", + "contiguous and wrap around window", 6, -8, 259, Z_OK); + inf("63 0 3 0 0 0 0 0", "copy direct from output", 0, -8, 259, + Z_STREAM_END); +} + +int main(void) +{ + fprintf(stderr, "%s\n", zlibVersion()); + cover_support(); + cover_wrap(); + cover_back(); + cover_inflate(); + cover_trees(); + cover_fast(); + return 0; +} diff --git a/zlib/zlib/test/minigzip.c b/zlib/zlib/test/minigzip.c new file mode 100644 index 00000000..b3025a48 --- /dev/null +++ b/zlib/zlib/test/minigzip.c @@ -0,0 +1,651 @@ +/* minigzip.c -- simulate gzip using the zlib compression library + * Copyright (C) 1995-2006, 2010, 2011 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * minigzip is a minimal implementation of the gzip utility. This is + * only an example of using zlib and isn't meant to replace the + * full-featured gzip. No attempt is made to deal with file systems + * limiting names to 14 or 8+3 characters, etc... Error checking is + * very limited. So use minigzip only for testing; use gzip for the + * real thing. On MSDOS, use only on file names without extension + * or in pipe mode. + */ + +/* @(#) $Id$ */ + +#include "zlib.h" +#include + +#ifdef STDC +# include +# include +#endif + +#ifdef USE_MMAP +# include +# include +# include +#endif + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# ifdef UNDER_CE +# include +# endif +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#ifdef _MSC_VER +# define snprintf _snprintf +#endif + +#ifdef VMS +# define unlink delete +# define GZ_SUFFIX "-gz" +#endif +#ifdef RISCOS +# define unlink remove +# define GZ_SUFFIX "-gz" +# define fileno(file) file->__file +#endif +#if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fileno */ +#endif + +#if !defined(Z_HAVE_UNISTD_H) && !defined(_LARGEFILE64_SOURCE) +#ifndef WIN32 /* unlink already in stdio.h for WIN32 */ + extern int unlink OF((const char *)); +#endif +#endif + +#if defined(UNDER_CE) +# include +# define perror(s) pwinerror(s) + +/* Map the Windows error number in ERROR to a locale-dependent error + message string and return a pointer to it. Typically, the values + for ERROR come from GetLastError. + + The string pointed to shall not be modified by the application, + but may be overwritten by a subsequent call to strwinerror + + The strwinerror function does not change the current setting + of GetLastError. */ + +static char *strwinerror (error) + DWORD error; +{ + static char buf[1024]; + + wchar_t *msgbuf; + DWORD lasterr = GetLastError(); + DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + error, + 0, /* Default language */ + (LPVOID)&msgbuf, + 0, + NULL); + if (chars != 0) { + /* If there is an \r\n appended, zap it. */ + if (chars >= 2 + && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') { + chars -= 2; + msgbuf[chars] = 0; + } + + if (chars > sizeof (buf) - 1) { + chars = sizeof (buf) - 1; + msgbuf[chars] = 0; + } + + wcstombs(buf, msgbuf, chars + 1); + LocalFree(msgbuf); + } + else { + sprintf(buf, "unknown win32 error (%ld)", error); + } + + SetLastError(lasterr); + return buf; +} + +static void pwinerror (s) + const char *s; +{ + if (s && *s) + fprintf(stderr, "%s: %s\n", s, strwinerror(GetLastError ())); + else + fprintf(stderr, "%s\n", strwinerror(GetLastError ())); +} + +#endif /* UNDER_CE */ + +#ifndef GZ_SUFFIX +# define GZ_SUFFIX ".gz" +#endif +#define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1) + +#define BUFLEN 16384 +#define MAX_NAME_LEN 1024 + +#ifdef MAXSEG_64K +# define local static + /* Needed for systems with limitation on stack size. */ +#else +# define local +#endif + +#ifdef Z_SOLO +/* for Z_SOLO, create simplified gz* functions using deflate and inflate */ + +#if defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE) +# include /* for unlink() */ +#endif + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + q = Z_NULL; + return calloc(n, m); +} + +void myfree(q, p) + void *q, *p; +{ + q = Z_NULL; + free(p); +} + +typedef struct gzFile_s { + FILE *file; + int write; + int err; + char *msg; + z_stream strm; +} *gzFile; + +gzFile gzopen OF((const char *, const char *)); +gzFile gzdopen OF((int, const char *)); +gzFile gz_open OF((const char *, int, const char *)); + +gzFile gzopen(path, mode) +const char *path; +const char *mode; +{ + return gz_open(path, -1, mode); +} + +gzFile gzdopen(fd, mode) +int fd; +const char *mode; +{ + return gz_open(NULL, fd, mode); +} + +gzFile gz_open(path, fd, mode) + const char *path; + int fd; + const char *mode; +{ + gzFile gz; + int ret; + + gz = malloc(sizeof(struct gzFile_s)); + if (gz == NULL) + return NULL; + gz->write = strchr(mode, 'w') != NULL; + gz->strm.zalloc = myalloc; + gz->strm.zfree = myfree; + gz->strm.opaque = Z_NULL; + if (gz->write) + ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0); + else { + gz->strm.next_in = 0; + gz->strm.avail_in = Z_NULL; + ret = inflateInit2(&(gz->strm), 15 + 16); + } + if (ret != Z_OK) { + free(gz); + return NULL; + } + gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") : + fopen(path, gz->write ? "wb" : "rb"); + if (gz->file == NULL) { + gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm)); + free(gz); + return NULL; + } + gz->err = 0; + gz->msg = ""; + return gz; +} + +int gzwrite OF((gzFile, const void *, unsigned)); + +int gzwrite(gz, buf, len) + gzFile gz; + const void *buf; + unsigned len; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL || !gz->write) + return 0; + strm = &(gz->strm); + strm->next_in = (void *)buf; + strm->avail_in = len; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_NO_FLUSH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + return len; +} + +int gzread OF((gzFile, void *, unsigned)); + +int gzread(gz, buf, len) + gzFile gz; + void *buf; + unsigned len; +{ + int ret; + unsigned got; + unsigned char in[1]; + z_stream *strm; + + if (gz == NULL || gz->write) + return 0; + if (gz->err) + return 0; + strm = &(gz->strm); + strm->next_out = (void *)buf; + strm->avail_out = len; + do { + got = fread(in, 1, 1, gz->file); + if (got == 0) + break; + strm->next_in = in; + strm->avail_in = 1; + ret = inflate(strm, Z_NO_FLUSH); + if (ret == Z_DATA_ERROR) { + gz->err = Z_DATA_ERROR; + gz->msg = strm->msg; + return 0; + } + if (ret == Z_STREAM_END) + inflateReset(strm); + } while (strm->avail_out); + return len - strm->avail_out; +} + +int gzclose OF((gzFile)); + +int gzclose(gz) + gzFile gz; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL) + return Z_STREAM_ERROR; + strm = &(gz->strm); + if (gz->write) { + strm->next_in = Z_NULL; + strm->avail_in = 0; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_FINISH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + deflateEnd(strm); + } + else + inflateEnd(strm); + fclose(gz->file); + free(gz); + return Z_OK; +} + +const char *gzerror OF((gzFile, int *)); + +const char *gzerror(gz, err) + gzFile gz; + int *err; +{ + *err = gz->err; + return gz->msg; +} + +#endif + +char *prog; + +void error OF((const char *msg)); +void gz_compress OF((FILE *in, gzFile out)); +#ifdef USE_MMAP +int gz_compress_mmap OF((FILE *in, gzFile out)); +#endif +void gz_uncompress OF((gzFile in, FILE *out)); +void file_compress OF((char *file, char *mode)); +void file_uncompress OF((char *file)); +int main OF((int argc, char *argv[])); + +/* =========================================================================== + * Display error message and exit + */ +void error(msg) + const char *msg; +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + +/* =========================================================================== + * Compress input to output then close both files. + */ + +void gz_compress(in, out) + FILE *in; + gzFile out; +{ + local char buf[BUFLEN]; + int len; + int err; + +#ifdef USE_MMAP + /* Try first compressing with mmap. If mmap fails (minigzip used in a + * pipe), use the normal fread loop. + */ + if (gz_compress_mmap(in, out) == Z_OK) return; +#endif + for (;;) { + len = (int)fread(buf, 1, sizeof(buf), in); + if (ferror(in)) { + perror("fread"); + exit(1); + } + if (len == 0) break; + + if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err)); + } + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); +} + +#ifdef USE_MMAP /* MMAP version, Miguel Albrecht */ + +/* Try compressing the input file at once using mmap. Return Z_OK if + * if success, Z_ERRNO otherwise. + */ +int gz_compress_mmap(in, out) + FILE *in; + gzFile out; +{ + int len; + int err; + int ifd = fileno(in); + caddr_t buf; /* mmap'ed buffer for the entire input file */ + off_t buf_len; /* length of the input file */ + struct stat sb; + + /* Determine the size of the file, needed for mmap: */ + if (fstat(ifd, &sb) < 0) return Z_ERRNO; + buf_len = sb.st_size; + if (buf_len <= 0) return Z_ERRNO; + + /* Now do the actual mmap: */ + buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0); + if (buf == (caddr_t)(-1)) return Z_ERRNO; + + /* Compress the whole file at once: */ + len = gzwrite(out, (char *)buf, (unsigned)buf_len); + + if (len != (int)buf_len) error(gzerror(out, &err)); + + munmap(buf, buf_len); + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); + return Z_OK; +} +#endif /* USE_MMAP */ + +/* =========================================================================== + * Uncompress input to output then close both files. + */ +void gz_uncompress(in, out) + gzFile in; + FILE *out; +{ + local char buf[BUFLEN]; + int len; + int err; + + for (;;) { + len = gzread(in, buf, sizeof(buf)); + if (len < 0) error (gzerror(in, &err)); + if (len == 0) break; + + if ((int)fwrite(buf, 1, (unsigned)len, out) != len) { + error("failed fwrite"); + } + } + if (fclose(out)) error("failed fclose"); + + if (gzclose(in) != Z_OK) error("failed gzclose"); +} + + +/* =========================================================================== + * Compress the given file: create a corresponding .gz file and remove the + * original. + */ +void file_compress(file, mode) + char *file; + char *mode; +{ + local char outfile[MAX_NAME_LEN]; + FILE *in; + gzFile out; + + if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) { + fprintf(stderr, "%s: filename too long\n", prog); + exit(1); + } + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outfile, sizeof(outfile), "%s%s", file, GZ_SUFFIX); +#else + strcpy(outfile, file); + strcat(outfile, GZ_SUFFIX); +#endif + + in = fopen(file, "rb"); + if (in == NULL) { + perror(file); + exit(1); + } + out = gzopen(outfile, mode); + if (out == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile); + exit(1); + } + gz_compress(in, out); + + unlink(file); +} + + +/* =========================================================================== + * Uncompress the given file and remove the original. + */ +void file_uncompress(file) + char *file; +{ + local char buf[MAX_NAME_LEN]; + char *infile, *outfile; + FILE *out; + gzFile in; + size_t len = strlen(file); + + if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) { + fprintf(stderr, "%s: filename too long\n", prog); + exit(1); + } + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf, sizeof(buf), "%s", file); +#else + strcpy(buf, file); +#endif + + if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) { + infile = file; + outfile = buf; + outfile[len-3] = '\0'; + } else { + outfile = file; + infile = buf; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf + len, sizeof(buf) - len, "%s", GZ_SUFFIX); +#else + strcat(infile, GZ_SUFFIX); +#endif + } + in = gzopen(infile, "rb"); + if (in == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, infile); + exit(1); + } + out = fopen(outfile, "wb"); + if (out == NULL) { + perror(file); + exit(1); + } + + gz_uncompress(in, out); + + unlink(infile); +} + + +/* =========================================================================== + * Usage: minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...] + * -c : write to standard output + * -d : decompress + * -f : compress with Z_FILTERED + * -h : compress with Z_HUFFMAN_ONLY + * -r : compress with Z_RLE + * -1 to -9 : compression level + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + int copyout = 0; + int uncompr = 0; + gzFile file; + char *bname, outmode[20]; + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outmode, sizeof(outmode), "%s", "wb6 "); +#else + strcpy(outmode, "wb6 "); +#endif + + prog = argv[0]; + bname = strrchr(argv[0], '/'); + if (bname) + bname++; + else + bname = argv[0]; + argc--, argv++; + + if (!strcmp(bname, "gunzip")) + uncompr = 1; + else if (!strcmp(bname, "zcat")) + copyout = uncompr = 1; + + while (argc > 0) { + if (strcmp(*argv, "-c") == 0) + copyout = 1; + else if (strcmp(*argv, "-d") == 0) + uncompr = 1; + else if (strcmp(*argv, "-f") == 0) + outmode[3] = 'f'; + else if (strcmp(*argv, "-h") == 0) + outmode[3] = 'h'; + else if (strcmp(*argv, "-r") == 0) + outmode[3] = 'R'; + else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' && + (*argv)[2] == 0) + outmode[2] = (*argv)[1]; + else + break; + argc--, argv++; + } + if (outmode[3] == ' ') + outmode[3] = 0; + if (argc == 0) { + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + if (uncompr) { + file = gzdopen(fileno(stdin), "rb"); + if (file == NULL) error("can't gzdopen stdin"); + gz_uncompress(file, stdout); + } else { + file = gzdopen(fileno(stdout), outmode); + if (file == NULL) error("can't gzdopen stdout"); + gz_compress(stdin, file); + } + } else { + if (copyout) { + SET_BINARY_MODE(stdout); + } + do { + if (uncompr) { + if (copyout) { + file = gzopen(*argv, "rb"); + if (file == NULL) + fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv); + else + gz_uncompress(file, stdout); + } else { + file_uncompress(*argv); + } + } else { + if (copyout) { + FILE * in = fopen(*argv, "rb"); + + if (in == NULL) { + perror(*argv); + } else { + file = gzdopen(fileno(stdout), outmode); + if (file == NULL) error("can't gzdopen stdout"); + + gz_compress(in, file); + } + + } else { + file_compress(*argv, outmode); + } + } + } while (argv++, --argc); + } + return 0; +} diff --git a/zlib/zlib/treebuild.xml b/zlib/zlib/treebuild.xml new file mode 100644 index 00000000..38d29d75 --- /dev/null +++ b/zlib/zlib/treebuild.xml @@ -0,0 +1,116 @@ + + + + zip compression library + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zlib/zlib/trees.c b/zlib/zlib/trees.c new file mode 100644 index 00000000..1fd7759e --- /dev/null +++ b/zlib/zlib/trees.c @@ -0,0 +1,1226 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-2012 Jean-loup Gailly + * detect_data_type() function provided freely by Cosmin Truta, 2006 + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id$ */ + +/* #define GEN_TREES_H */ + +#include "deflate.h" + +#ifdef DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, const ct_data *ltree, + const ct_data *dtree)); +local int detect_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); +local void copy_block OF((deflate_state *s, charf *buf, unsigned len, + int header)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits(s, value, length) + deflate_state *s; + int value; /* value to send */ + int length; /* number of bits */ +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (ush)value << s->bi_valid; + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= (ush)value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (ush)val << s->bi_valid;\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (ush)(value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* DEBUG */ + + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ +#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, + "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void ZLIB_INTERNAL _tr_init(s) + deflate_state *s; +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(s) + deflate_state *s; +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap(s, tree, k) + deflate_state *s; + ct_data *tree; /* the tree to restore */ + int k; /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen(s, desc) + deflate_state *s; + tree_desc *desc; /* the tree descriptor */ +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (bits + xbits); + if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits); + } + if (overflow == 0) return; + + Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if ((unsigned) tree[m].Len != (unsigned) bits) { + Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((long)bits - (long)tree[m].Len) + *(long)tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (tree, max_code, bl_count) + ct_data *tree; /* the tree to decorate */ + int max_code; /* largest code with non zero frequency */ + ushf *bl_count; /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree(s) + deflate_state *s; +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*(max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees(s, lcodes, dcodes, blcodes) + deflate_state *s; + int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) + deflate_state *s; + charf *buf; /* input block */ + ulg stored_len; /* length of input block */ + int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ +#ifdef DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; +#endif + copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ +} + +/* =========================================================================== + * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) + */ +void ZLIB_INTERNAL _tr_flush_bits(s) + deflate_state *s; +{ + bi_flush(s); +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +void ZLIB_INTERNAL _tr_align(s) + deflate_state *s; +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) + deflate_state *s; + charf *buf; /* input block, or NULL if too old */ + ulg stored_len; /* length of input block */ + int last; /* one if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is binary or text */ + if (s->strm->data_type == Z_UNKNOWN) + s->strm->data_type = detect_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+last, 3); + compress_block(s, (const ct_data *)static_ltree, + (const ct_data *)static_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+last, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (const ct_data *)s->dyn_ltree, + (const ct_data *)s->dyn_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); +#ifdef DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*last)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int ZLIB_INTERNAL _tr_tally (s, dist, lc) + deflate_state *s; + unsigned dist; /* distance of matched string */ + unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(s, ltree, dtree) + deflate_state *s; + const ct_data *ltree; /* literal tree */ + const ct_data *dtree; /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); +} + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local int detect_data_type(s) + deflate_state *s; +{ + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + unsigned long black_mask = 0xf3ffc07fUL; + int n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>= 1) + if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0)) + return Z_BINARY; + + /* Check for textual ("white-listed") bytes. */ + if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 + || s->dyn_ltree[13].Freq != 0) + return Z_TEXT; + for (n = 32; n < LITERALS; n++) + if (s->dyn_ltree[n].Freq != 0) + return Z_TEXT; + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(code, len) + unsigned code; /* the value to invert */ + int len; /* its bit length */ +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(s) + deflate_state *s; +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(s) + deflate_state *s; +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +local void copy_block(s, buf, len, header) + deflate_state *s; + charf *buf; /* the input data */ + unsigned len; /* its length */ + int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + + if (header) { + put_short(s, (ush)len); + put_short(s, (ush)~len); +#ifdef DEBUG + s->bits_sent += 2*16; +#endif + } +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif + while (len--) { + put_byte(s, *buf++); + } +} diff --git a/zlib/zlib/trees.h b/zlib/zlib/trees.h new file mode 100644 index 00000000..d35639d8 --- /dev/null +++ b/zlib/zlib/trees.h @@ -0,0 +1,128 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + diff --git a/zlib/zlib/uncompr.c b/zlib/zlib/uncompr.c new file mode 100644 index 00000000..242e9493 --- /dev/null +++ b/zlib/zlib/uncompr.c @@ -0,0 +1,59 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003, 2010 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ +int ZEXPORT uncompress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + z_stream stream; + int err; + + stream.next_in = (z_const Bytef *)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} diff --git a/zlib/zlib/watcom/watcom_f.mak b/zlib/zlib/watcom/watcom_f.mak new file mode 100644 index 00000000..37f4d74c --- /dev/null +++ b/zlib/zlib/watcom/watcom_f.mak @@ -0,0 +1,43 @@ +# Makefile for zlib +# OpenWatcom flat model +# Last updated: 28-Dec-2005 + +# To use, do "wmake -f watcom_f.mak" + +C_SOURCE = adler32.c compress.c crc32.c deflate.c & + gzclose.c gzlib.c gzread.c gzwrite.c & + infback.c inffast.c inflate.c inftrees.c & + trees.c uncompr.c zutil.c + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj & + gzclose.obj gzlib.obj gzread.obj gzwrite.obj & + infback.obj inffast.obj inflate.obj inftrees.obj & + trees.obj uncompr.obj zutil.obj + +CC = wcc386 +LINKER = wcl386 +CFLAGS = -zq -mf -3r -fp3 -s -bt=dos -oilrtfm -fr=nul -wx +ZLIB_LIB = zlib_f.lib + +.C.OBJ: + $(CC) $(CFLAGS) $[@ + +all: $(ZLIB_LIB) example.exe minigzip.exe + +$(ZLIB_LIB): $(OBJS) + wlib -b -c $(ZLIB_LIB) -+adler32.obj -+compress.obj -+crc32.obj + wlib -b -c $(ZLIB_LIB) -+gzclose.obj -+gzlib.obj -+gzread.obj -+gzwrite.obj + wlib -b -c $(ZLIB_LIB) -+deflate.obj -+infback.obj + wlib -b -c $(ZLIB_LIB) -+inffast.obj -+inflate.obj -+inftrees.obj + wlib -b -c $(ZLIB_LIB) -+trees.obj -+uncompr.obj -+zutil.obj + +example.exe: $(ZLIB_LIB) example.obj + $(LINKER) -ldos32a -fe=example.exe example.obj $(ZLIB_LIB) + +minigzip.exe: $(ZLIB_LIB) minigzip.obj + $(LINKER) -ldos32a -fe=minigzip.exe minigzip.obj $(ZLIB_LIB) + +clean: .SYMBOLIC + del *.obj + del $(ZLIB_LIB) + @echo Cleaning done diff --git a/zlib/zlib/watcom/watcom_l.mak b/zlib/zlib/watcom/watcom_l.mak new file mode 100644 index 00000000..193eed7b --- /dev/null +++ b/zlib/zlib/watcom/watcom_l.mak @@ -0,0 +1,43 @@ +# Makefile for zlib +# OpenWatcom large model +# Last updated: 28-Dec-2005 + +# To use, do "wmake -f watcom_l.mak" + +C_SOURCE = adler32.c compress.c crc32.c deflate.c & + gzclose.c gzlib.c gzread.c gzwrite.c & + infback.c inffast.c inflate.c inftrees.c & + trees.c uncompr.c zutil.c + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj & + gzclose.obj gzlib.obj gzread.obj gzwrite.obj & + infback.obj inffast.obj inflate.obj inftrees.obj & + trees.obj uncompr.obj zutil.obj + +CC = wcc +LINKER = wcl +CFLAGS = -zq -ml -s -bt=dos -oilrtfm -fr=nul -wx +ZLIB_LIB = zlib_l.lib + +.C.OBJ: + $(CC) $(CFLAGS) $[@ + +all: $(ZLIB_LIB) example.exe minigzip.exe + +$(ZLIB_LIB): $(OBJS) + wlib -b -c $(ZLIB_LIB) -+adler32.obj -+compress.obj -+crc32.obj + wlib -b -c $(ZLIB_LIB) -+gzclose.obj -+gzlib.obj -+gzread.obj -+gzwrite.obj + wlib -b -c $(ZLIB_LIB) -+deflate.obj -+infback.obj + wlib -b -c $(ZLIB_LIB) -+inffast.obj -+inflate.obj -+inftrees.obj + wlib -b -c $(ZLIB_LIB) -+trees.obj -+uncompr.obj -+zutil.obj + +example.exe: $(ZLIB_LIB) example.obj + $(LINKER) -fe=example.exe example.obj $(ZLIB_LIB) + +minigzip.exe: $(ZLIB_LIB) minigzip.obj + $(LINKER) -fe=minigzip.exe minigzip.obj $(ZLIB_LIB) + +clean: .SYMBOLIC + del *.obj + del $(ZLIB_LIB) + @echo Cleaning done diff --git a/zlib/zlib/win32/DLL_FAQ.txt b/zlib/zlib/win32/DLL_FAQ.txt new file mode 100644 index 00000000..12c00901 --- /dev/null +++ b/zlib/zlib/win32/DLL_FAQ.txt @@ -0,0 +1,397 @@ + + Frequently Asked Questions about ZLIB1.DLL + + +This document describes the design, the rationale, and the usage +of the official DLL build of zlib, named ZLIB1.DLL. If you have +general questions about zlib, you should see the file "FAQ" found +in the zlib distribution, or at the following location: + http://www.gzip.org/zlib/zlib_faq.html + + + 1. What is ZLIB1.DLL, and how can I get it? + + - ZLIB1.DLL is the official build of zlib as a DLL. + (Please remark the character '1' in the name.) + + Pointers to a precompiled ZLIB1.DLL can be found in the zlib + web site at: + http://www.zlib.net/ + + Applications that link to ZLIB1.DLL can rely on the following + specification: + + * The exported symbols are exclusively defined in the source + files "zlib.h" and "zlib.def", found in an official zlib + source distribution. + * The symbols are exported by name, not by ordinal. + * The exported names are undecorated. + * The calling convention of functions is "C" (CDECL). + * The ZLIB1.DLL binary is linked to MSVCRT.DLL. + + The archive in which ZLIB1.DLL is bundled contains compiled + test programs that must run with a valid build of ZLIB1.DLL. + It is recommended to download the prebuilt DLL from the zlib + web site, instead of building it yourself, to avoid potential + incompatibilities that could be introduced by your compiler + and build settings. If you do build the DLL yourself, please + make sure that it complies with all the above requirements, + and it runs with the precompiled test programs, bundled with + the original ZLIB1.DLL distribution. + + If, for any reason, you need to build an incompatible DLL, + please use a different file name. + + + 2. Why did you change the name of the DLL to ZLIB1.DLL? + What happened to the old ZLIB.DLL? + + - The old ZLIB.DLL, built from zlib-1.1.4 or earlier, required + compilation settings that were incompatible to those used by + a static build. The DLL settings were supposed to be enabled + by defining the macro ZLIB_DLL, before including "zlib.h". + Incorrect handling of this macro was silently accepted at + build time, resulting in two major problems: + + * ZLIB_DLL was missing from the old makefile. When building + the DLL, not all people added it to the build options. In + consequence, incompatible incarnations of ZLIB.DLL started + to circulate around the net. + + * When switching from using the static library to using the + DLL, applications had to define the ZLIB_DLL macro and + to recompile all the sources that contained calls to zlib + functions. Failure to do so resulted in creating binaries + that were unable to run with the official ZLIB.DLL build. + + The only possible solution that we could foresee was to make + a binary-incompatible change in the DLL interface, in order to + remove the dependency on the ZLIB_DLL macro, and to release + the new DLL under a different name. + + We chose the name ZLIB1.DLL, where '1' indicates the major + zlib version number. We hope that we will not have to break + the binary compatibility again, at least not as long as the + zlib-1.x series will last. + + There is still a ZLIB_DLL macro, that can trigger a more + efficient build and use of the DLL, but compatibility no + longer dependents on it. + + + 3. Can I build ZLIB.DLL from the new zlib sources, and replace + an old ZLIB.DLL, that was built from zlib-1.1.4 or earlier? + + - In principle, you can do it by assigning calling convention + keywords to the macros ZEXPORT and ZEXPORTVA. In practice, + it depends on what you mean by "an old ZLIB.DLL", because the + old DLL exists in several mutually-incompatible versions. + You have to find out first what kind of calling convention is + being used in your particular ZLIB.DLL build, and to use the + same one in the new build. If you don't know what this is all + about, you might be better off if you would just leave the old + DLL intact. + + + 4. Can I compile my application using the new zlib interface, and + link it to an old ZLIB.DLL, that was built from zlib-1.1.4 or + earlier? + + - The official answer is "no"; the real answer depends again on + what kind of ZLIB.DLL you have. Even if you are lucky, this + course of action is unreliable. + + If you rebuild your application and you intend to use a newer + version of zlib (post- 1.1.4), it is strongly recommended to + link it to the new ZLIB1.DLL. + + + 5. Why are the zlib symbols exported by name, and not by ordinal? + + - Although exporting symbols by ordinal is a little faster, it + is risky. Any single glitch in the maintenance or use of the + DEF file that contains the ordinals can result in incompatible + builds and frustrating crashes. Simply put, the benefits of + exporting symbols by ordinal do not justify the risks. + + Technically, it should be possible to maintain ordinals in + the DEF file, and still export the symbols by name. Ordinals + exist in every DLL, and even if the dynamic linking performed + at the DLL startup is searching for names, ordinals serve as + hints, for a faster name lookup. However, if the DEF file + contains ordinals, the Microsoft linker automatically builds + an implib that will cause the executables linked to it to use + those ordinals, and not the names. It is interesting to + notice that the GNU linker for Win32 does not suffer from this + problem. + + It is possible to avoid the DEF file if the exported symbols + are accompanied by a "__declspec(dllexport)" attribute in the + source files. You can do this in zlib by predefining the + ZLIB_DLL macro. + + + 6. I see that the ZLIB1.DLL functions use the "C" (CDECL) calling + convention. Why not use the STDCALL convention? + STDCALL is the standard convention in Win32, and I need it in + my Visual Basic project! + + (For readability, we use CDECL to refer to the convention + triggered by the "__cdecl" keyword, STDCALL to refer to + the convention triggered by "__stdcall", and FASTCALL to + refer to the convention triggered by "__fastcall".) + + - Most of the native Windows API functions (without varargs) use + indeed the WINAPI convention (which translates to STDCALL in + Win32), but the standard C functions use CDECL. If a user + application is intrinsically tied to the Windows API (e.g. + it calls native Windows API functions such as CreateFile()), + sometimes it makes sense to decorate its own functions with + WINAPI. But if ANSI C or POSIX portability is a goal (e.g. + it calls standard C functions such as fopen()), it is not a + sound decision to request the inclusion of , or to + use non-ANSI constructs, for the sole purpose to make the user + functions STDCALL-able. + + The functionality offered by zlib is not in the category of + "Windows functionality", but is more like "C functionality". + + Technically, STDCALL is not bad; in fact, it is slightly + faster than CDECL, and it works with variable-argument + functions, just like CDECL. It is unfortunate that, in spite + of using STDCALL in the Windows API, it is not the default + convention used by the C compilers that run under Windows. + The roots of the problem reside deep inside the unsafety of + the K&R-style function prototypes, where the argument types + are not specified; but that is another story for another day. + + The remaining fact is that CDECL is the default convention. + Even if an explicit convention is hard-coded into the function + prototypes inside C headers, problems may appear. The + necessity to expose the convention in users' callbacks is one + of these problems. + + The calling convention issues are also important when using + zlib in other programming languages. Some of them, like Ada + (GNAT) and Fortran (GNU G77), have C bindings implemented + initially on Unix, and relying on the C calling convention. + On the other hand, the pre- .NET versions of Microsoft Visual + Basic require STDCALL, while Borland Delphi prefers, although + it does not require, FASTCALL. + + In fairness to all possible uses of zlib outside the C + programming language, we choose the default "C" convention. + Anyone interested in different bindings or conventions is + encouraged to maintain specialized projects. The "contrib/" + directory from the zlib distribution already holds a couple + of foreign bindings, such as Ada, C++, and Delphi. + + + 7. I need a DLL for my Visual Basic project. What can I do? + + - Define the ZLIB_WINAPI macro before including "zlib.h", when + building both the DLL and the user application (except that + you don't need to define anything when using the DLL in Visual + Basic). The ZLIB_WINAPI macro will switch on the WINAPI + (STDCALL) convention. The name of this DLL must be different + than the official ZLIB1.DLL. + + Gilles Vollant has contributed a build named ZLIBWAPI.DLL, + with the ZLIB_WINAPI macro turned on, and with the minizip + functionality built in. For more information, please read + the notes inside "contrib/vstudio/readme.txt", found in the + zlib distribution. + + + 8. I need to use zlib in my Microsoft .NET project. What can I + do? + + - Henrik Ravn has contributed a .NET wrapper around zlib. Look + into contrib/dotzlib/, inside the zlib distribution. + + + 9. If my application uses ZLIB1.DLL, should I link it to + MSVCRT.DLL? Why? + + - It is not required, but it is recommended to link your + application to MSVCRT.DLL, if it uses ZLIB1.DLL. + + The executables (.EXE, .DLL, etc.) that are involved in the + same process and are using the C run-time library (i.e. they + are calling standard C functions), must link to the same + library. There are several libraries in the Win32 system: + CRTDLL.DLL, MSVCRT.DLL, the static C libraries, etc. + Since ZLIB1.DLL is linked to MSVCRT.DLL, the executables that + depend on it should also be linked to MSVCRT.DLL. + + +10. Why are you saying that ZLIB1.DLL and my application should + be linked to the same C run-time (CRT) library? I linked my + application and my DLLs to different C libraries (e.g. my + application to a static library, and my DLLs to MSVCRT.DLL), + and everything works fine. + + - If a user library invokes only pure Win32 API (accessible via + and the related headers), its DLL build will work + in any context. But if this library invokes standard C API, + things get more complicated. + + There is a single Win32 library in a Win32 system. Every + function in this library resides in a single DLL module, that + is safe to call from anywhere. On the other hand, there are + multiple versions of the C library, and each of them has its + own separate internal state. Standalone executables and user + DLLs that call standard C functions must link to a C run-time + (CRT) library, be it static or shared (DLL). Intermixing + occurs when an executable (not necessarily standalone) and a + DLL are linked to different CRTs, and both are running in the + same process. + + Intermixing multiple CRTs is possible, as long as their + internal states are kept intact. The Microsoft Knowledge Base + articles KB94248 "HOWTO: Use the C Run-Time" and KB140584 + "HOWTO: Link with the Correct C Run-Time (CRT) Library" + mention the potential problems raised by intermixing. + + If intermixing works for you, it's because your application + and DLLs are avoiding the corruption of each of the CRTs' + internal states, maybe by careful design, or maybe by fortune. + + Also note that linking ZLIB1.DLL to non-Microsoft CRTs, such + as those provided by Borland, raises similar problems. + + +11. Why are you linking ZLIB1.DLL to MSVCRT.DLL? + + - MSVCRT.DLL exists on every Windows 95 with a new service pack + installed, or with Microsoft Internet Explorer 4 or later, and + on all other Windows 4.x or later (Windows 98, Windows NT 4, + or later). It is freely distributable; if not present in the + system, it can be downloaded from Microsoft or from other + software provider for free. + + The fact that MSVCRT.DLL does not exist on a virgin Windows 95 + is not so problematic. Windows 95 is scarcely found nowadays, + Microsoft ended its support a long time ago, and many recent + applications from various vendors, including Microsoft, do not + even run on it. Furthermore, no serious user should run + Windows 95 without a proper update installed. + + +12. Why are you not linking ZLIB1.DLL to + <> ? + + - We considered and abandoned the following alternatives: + + * Linking ZLIB1.DLL to a static C library (LIBC.LIB, or + LIBCMT.LIB) is not a good option. People are using the DLL + mainly to save disk space. If you are linking your program + to a static C library, you may as well consider linking zlib + in statically, too. + + * Linking ZLIB1.DLL to CRTDLL.DLL looks appealing, because + CRTDLL.DLL is present on every Win32 installation. + Unfortunately, it has a series of problems: it does not + work properly with Microsoft's C++ libraries, it does not + provide support for 64-bit file offsets, (and so on...), + and Microsoft discontinued its support a long time ago. + + * Linking ZLIB1.DLL to MSVCR70.DLL or MSVCR71.DLL, supplied + with the Microsoft .NET platform, and Visual C++ 7.0/7.1, + raises problems related to the status of ZLIB1.DLL as a + system component. According to the Microsoft Knowledge Base + article KB326922 "INFO: Redistribution of the Shared C + Runtime Component in Visual C++ .NET", MSVCR70.DLL and + MSVCR71.DLL are not supposed to function as system DLLs, + because they may clash with MSVCRT.DLL. Instead, the + application's installer is supposed to put these DLLs + (if needed) in the application's private directory. + If ZLIB1.DLL depends on a non-system runtime, it cannot + function as a redistributable system component. + + * Linking ZLIB1.DLL to non-Microsoft runtimes, such as + Borland's, or Cygwin's, raises problems related to the + reliable presence of these runtimes on Win32 systems. + It's easier to let the DLL build of zlib up to the people + who distribute these runtimes, and who may proceed as + explained in the answer to Question 14. + + +13. If ZLIB1.DLL cannot be linked to MSVCR70.DLL or MSVCR71.DLL, + how can I build/use ZLIB1.DLL in Microsoft Visual C++ 7.0 + (Visual Studio .NET) or newer? + + - Due to the problems explained in the Microsoft Knowledge Base + article KB326922 (see the previous answer), the C runtime that + comes with the VC7 environment is no longer considered a + system component. That is, it should not be assumed that this + runtime exists, or may be installed in a system directory. + Since ZLIB1.DLL is supposed to be a system component, it may + not depend on a non-system component. + + In order to link ZLIB1.DLL and your application to MSVCRT.DLL + in VC7, you need the library of Visual C++ 6.0 or older. If + you don't have this library at hand, it's probably best not to + use ZLIB1.DLL. + + We are hoping that, in the future, Microsoft will provide a + way to build applications linked to a proper system runtime, + from the Visual C++ environment. Until then, you have a + couple of alternatives, such as linking zlib in statically. + If your application requires dynamic linking, you may proceed + as explained in the answer to Question 14. + + +14. I need to link my own DLL build to a CRT different than + MSVCRT.DLL. What can I do? + + - Feel free to rebuild the DLL from the zlib sources, and link + it the way you want. You should, however, clearly state that + your build is unofficial. You should give it a different file + name, and/or install it in a private directory that can be + accessed by your application only, and is not visible to the + others (i.e. it's neither in the PATH, nor in the SYSTEM or + SYSTEM32 directories). Otherwise, your build may clash with + applications that link to the official build. + + For example, in Cygwin, zlib is linked to the Cygwin runtime + CYGWIN1.DLL, and it is distributed under the name CYGZ.DLL. + + +15. May I include additional pieces of code that I find useful, + link them in ZLIB1.DLL, and export them? + + - No. A legitimate build of ZLIB1.DLL must not include code + that does not originate from the official zlib source code. + But you can make your own private DLL build, under a different + file name, as suggested in the previous answer. + + For example, zlib is a part of the VCL library, distributed + with Borland Delphi and C++ Builder. The DLL build of VCL + is a redistributable file, named VCLxx.DLL. + + +16. May I remove some functionality out of ZLIB1.DLL, by enabling + macros like NO_GZCOMPRESS or NO_GZIP at compile time? + + - No. A legitimate build of ZLIB1.DLL must provide the complete + zlib functionality, as implemented in the official zlib source + code. But you can make your own private DLL build, under a + different file name, as suggested in the previous answer. + + +17. I made my own ZLIB1.DLL build. Can I test it for compliance? + + - We prefer that you download the official DLL from the zlib + web site. If you need something peculiar from this DLL, you + can send your suggestion to the zlib mailing list. + + However, in case you do rebuild the DLL yourself, you can run + it with the test programs found in the DLL distribution. + Running these test programs is not a guarantee of compliance, + but a failure can imply a detected problem. + +** + +This document is written and maintained by +Cosmin Truta diff --git a/zlib/zlib/win32/Makefile.bor b/zlib/zlib/win32/Makefile.bor new file mode 100644 index 00000000..d152bbb7 --- /dev/null +++ b/zlib/zlib/win32/Makefile.bor @@ -0,0 +1,110 @@ +# Makefile for zlib +# Borland C++ for Win32 +# +# Usage: +# make -f win32/Makefile.bor +# make -f win32/Makefile.bor LOCAL_ZLIB=-DASMV OBJA=match.obj OBJPA=+match.obj + +# ------------ Borland C++ ------------ + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or +# added to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +CC = bcc32 +AS = bcc32 +LD = bcc32 +AR = tlib +CFLAGS = -a -d -k- -O2 $(LOC) +ASFLAGS = $(LOC) +LDFLAGS = $(LOC) + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +#OBJA = +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj +#OBJPA= + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $< + +.asm.obj: + $(AS) -c $(ASFLAGS) $< + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) $(OBJA) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + $(AR) $(ZLIB_LIB) $(OBJPA) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del $(ZLIB_LIB) + -del *.obj + -del *.exe + -del *.tds + -del zlib.bak + -del foo.gz diff --git a/zlib/zlib/win32/Makefile.gcc b/zlib/zlib/win32/Makefile.gcc new file mode 100644 index 00000000..6d1ded62 --- /dev/null +++ b/zlib/zlib/win32/Makefile.gcc @@ -0,0 +1,182 @@ +# Makefile for zlib, derived from Makefile.dj2. +# Modified for mingw32 by C. Spieler, 6/16/98. +# Updated for zlib 1.2.x by Christian Spieler and Cosmin Truta, Mar-2003. +# Last updated: Mar 2012. +# Tested under Cygwin and MinGW. + +# Copyright (C) 1995-2003 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type from the top level zlib directory: +# +# make -fwin32/Makefile.gcc; make test testdll -fwin32/Makefile.gcc +# +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o -fwin32/Makefile.gcc +# +# To install libz.a, zconf.h and zlib.h in the system directories, type: +# +# make install -fwin32/Makefile.gcc +# +# BINARY_PATH, INCLUDE_PATH and LIBRARY_PATH must be set. +# +# To install the shared lib, append SHARED_MODE=1 to the make command : +# +# make install -fwin32/Makefile.gcc SHARED_MODE=1 + +# Note: +# If the platform is *not* MinGW (e.g. it is Cygwin or UWIN), +# the DLL name should be changed from "zlib1.dll". + +STATICLIB = libz.a +SHAREDLIB = zlib1.dll +IMPLIB = libz.dll.a + +# +# Set to 1 if shared object needs to be installed +# +SHARED_MODE=0 + +#LOC = -DASMV +#LOC = -DDEBUG -g + +PREFIX = +CC = $(PREFIX)gcc +CFLAGS = $(LOC) -O3 -Wall + +AS = $(CC) +ASFLAGS = $(LOC) -Wall + +LD = $(CC) +LDFLAGS = $(LOC) + +AR = $(PREFIX)ar +ARFLAGS = rcs + +RC = $(PREFIX)windres +RCFLAGS = --define GCC_WINDRES + +STRIP = $(PREFIX)strip + +CP = cp -fp +# If GNU install is available, replace $(CP) with install. +INSTALL = $(CP) +RM = rm -f + +prefix ?= /usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o deflate.o gzclose.o gzlib.o gzread.o \ + gzwrite.o infback.o inffast.o inflate.o inftrees.o trees.o uncompr.o zutil.o +OBJA = + +all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) example.exe minigzip.exe example_d.exe minigzip_d.exe + +test: example.exe minigzip.exe + ./example + echo hello world | ./minigzip | ./minigzip -d + +testdll: example_d.exe minigzip_d.exe + ./example_d + echo hello world | ./minigzip_d | ./minigzip_d -d + +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +.S.o: + $(AS) $(ASFLAGS) -c -o $@ $< + +$(STATICLIB): $(OBJS) $(OBJA) + $(AR) $(ARFLAGS) $@ $(OBJS) $(OBJA) + +$(IMPLIB): $(SHAREDLIB) + +$(SHAREDLIB): win32/zlib.def $(OBJS) $(OBJA) zlibrc.o + $(CC) -shared -Wl,--out-implib,$(IMPLIB) $(LDFLAGS) \ + -o $@ win32/zlib.def $(OBJS) $(OBJA) zlibrc.o + $(STRIP) $@ + +example.exe: example.o $(STATICLIB) + $(LD) $(LDFLAGS) -o $@ example.o $(STATICLIB) + $(STRIP) $@ + +minigzip.exe: minigzip.o $(STATICLIB) + $(LD) $(LDFLAGS) -o $@ minigzip.o $(STATICLIB) + $(STRIP) $@ + +example_d.exe: example.o $(IMPLIB) + $(LD) $(LDFLAGS) -o $@ example.o $(IMPLIB) + $(STRIP) $@ + +minigzip_d.exe: minigzip.o $(IMPLIB) + $(LD) $(LDFLAGS) -o $@ minigzip.o $(IMPLIB) + $(STRIP) $@ + +example.o: test/example.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/example.c + +minigzip.o: test/minigzip.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/minigzip.c + +zlibrc.o: win32/zlib1.rc + $(RC) $(RCFLAGS) -o $@ win32/zlib1.rc + +.PHONY: install uninstall clean + +install: zlib.h zconf.h $(STATICLIB) $(IMPLIB) + @if test -z "$(DESTDIR)$(INCLUDE_PATH)" -o -z "$(DESTDIR)$(LIBRARY_PATH)" -o -z "$(DESTDIR)$(BINARY_PATH)"; then \ + echo INCLUDE_PATH, LIBRARY_PATH, and BINARY_PATH must be specified; \ + exit 1; \ + fi + -@mkdir -p '$(DESTDIR)$(INCLUDE_PATH)' + -@mkdir -p '$(DESTDIR)$(LIBRARY_PATH)' '$(DESTDIR)$(LIBRARY_PATH)'/pkgconfig + -if [ "$(SHARED_MODE)" = "1" ]; then \ + mkdir -p '$(DESTDIR)$(BINARY_PATH)'; \ + $(INSTALL) $(SHAREDLIB) '$(DESTDIR)$(BINARY_PATH)'; \ + $(INSTALL) $(IMPLIB) '$(DESTDIR)$(LIBRARY_PATH)'; \ + fi + -$(INSTALL) zlib.h '$(DESTDIR)$(INCLUDE_PATH)' + -$(INSTALL) zconf.h '$(DESTDIR)$(INCLUDE_PATH)' + -$(INSTALL) $(STATICLIB) '$(DESTDIR)$(LIBRARY_PATH)' + sed \ + -e 's|@prefix@|${prefix}|g' \ + -e 's|@exec_prefix@|${exec_prefix}|g' \ + -e 's|@libdir@|$(LIBRARY_PATH)|g' \ + -e 's|@sharedlibdir@|$(LIBRARY_PATH)|g' \ + -e 's|@includedir@|$(INCLUDE_PATH)|g' \ + -e 's|@VERSION@|'`sed -n -e '/VERSION "/s/.*"\(.*\)".*/\1/p' zlib.h`'|g' \ + zlib.pc.in > '$(DESTDIR)$(LIBRARY_PATH)'/pkgconfig/zlib.pc + +uninstall: + -if [ "$(SHARED_MODE)" = "1" ]; then \ + $(RM) '$(DESTDIR)$(BINARY_PATH)'/$(SHAREDLIB); \ + $(RM) '$(DESTDIR)$(LIBRARY_PATH)'/$(IMPLIB); \ + fi + -$(RM) '$(DESTDIR)$(INCLUDE_PATH)'/zlib.h + -$(RM) '$(DESTDIR)$(INCLUDE_PATH)'/zconf.h + -$(RM) '$(DESTDIR)$(LIBRARY_PATH)'/$(STATICLIB) + +clean: + -$(RM) $(STATICLIB) + -$(RM) $(SHAREDLIB) + -$(RM) $(IMPLIB) + -$(RM) *.o + -$(RM) *.exe + -$(RM) foo.gz + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +gzclose.o: zlib.h zconf.h gzguts.h +gzlib.o: zlib.h zconf.h gzguts.h +gzread.o: zlib.h zconf.h gzguts.h +gzwrite.o: zlib.h zconf.h gzguts.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h diff --git a/zlib/zlib/win32/Makefile.msc b/zlib/zlib/win32/Makefile.msc new file mode 100644 index 00000000..67b77317 --- /dev/null +++ b/zlib/zlib/win32/Makefile.msc @@ -0,0 +1,163 @@ +# Makefile for zlib using Microsoft (Visual) C +# zlib is copyright (C) 1995-2006 Jean-loup Gailly and Mark Adler +# +# Usage: +# nmake -f win32/Makefile.msc (standard build) +# nmake -f win32/Makefile.msc LOC=-DFOO (nonstandard build) +# nmake -f win32/Makefile.msc LOC="-DASMV -DASMINF" \ +# OBJA="inffas32.obj match686.obj" (use ASM code, x86) +# nmake -f win32/Makefile.msc AS=ml64 LOC="-DASMV -DASMINF -I." \ +# OBJA="inffasx64.obj gvmat64.obj inffas8664.obj" (use ASM code, x64) + +# The toplevel directory of the source tree. +# +TOP = . + +# optional build flags +LOC = + +# variables +STATICLIB = zlib.lib +SHAREDLIB = zlib1.dll +IMPLIB = zdll.lib + +CC = cl +AS = ml +LD = link +AR = lib +RC = rc +CFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi -Fd"zlib" $(LOC) +WFLAGS = -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE +ASFLAGS = -coff -Zi $(LOC) +LDFLAGS = -nologo -debug -incremental:no -opt:ref +ARFLAGS = -nologo +RCFLAGS = /dWIN32 /r + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj \ + gzwrite.obj infback.obj inflate.obj inftrees.obj inffast.obj trees.obj uncompr.obj zutil.obj +OBJA = + + +# targets +all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) \ + example.exe minigzip.exe example_d.exe minigzip_d.exe + +$(STATICLIB): $(OBJS) $(OBJA) + $(AR) $(ARFLAGS) -out:$@ $(OBJS) $(OBJA) + +$(IMPLIB): $(SHAREDLIB) + +$(SHAREDLIB): $(TOP)/win32/zlib.def $(OBJS) $(OBJA) zlib1.res + $(LD) $(LDFLAGS) -def:$(TOP)/win32/zlib.def -dll -implib:$(IMPLIB) \ + -out:$@ -base:0x5A4C0000 $(OBJS) $(OBJA) zlib1.res + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;2 + +example.exe: example.obj $(STATICLIB) + $(LD) $(LDFLAGS) example.obj $(STATICLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +minigzip.exe: minigzip.obj $(STATICLIB) + $(LD) $(LDFLAGS) minigzip.obj $(STATICLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +example_d.exe: example.obj $(IMPLIB) + $(LD) $(LDFLAGS) -out:$@ example.obj $(IMPLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +minigzip_d.exe: minigzip.obj $(IMPLIB) + $(LD) $(LDFLAGS) -out:$@ minigzip.obj $(IMPLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +{$(TOP)}.c.obj: + $(CC) -c $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/test}.c.obj: + $(CC) -c -I$(TOP) $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/contrib/masmx64}.c.obj: + $(CC) -c $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/contrib/masmx64}.asm.obj: + $(AS) -c $(ASFLAGS) $< + +{$(TOP)/contrib/masmx86}.asm.obj: + $(AS) -c $(ASFLAGS) $< + +adler32.obj: $(TOP)/adler32.c $(TOP)/zlib.h $(TOP)/zconf.h + +compress.obj: $(TOP)/compress.c $(TOP)/zlib.h $(TOP)/zconf.h + +crc32.obj: $(TOP)/crc32.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/crc32.h + +deflate.obj: $(TOP)/deflate.c $(TOP)/deflate.h $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h + +gzclose.obj: $(TOP)/gzclose.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzlib.obj: $(TOP)/gzlib.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzread.obj: $(TOP)/gzread.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzwrite.obj: $(TOP)/gzwrite.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +infback.obj: $(TOP)/infback.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h $(TOP)/inffixed.h + +inffast.obj: $(TOP)/inffast.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h + +inflate.obj: $(TOP)/inflate.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h $(TOP)/inffixed.h + +inftrees.obj: $(TOP)/inftrees.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h + +trees.obj: $(TOP)/trees.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/deflate.h $(TOP)/trees.h + +uncompr.obj: $(TOP)/uncompr.c $(TOP)/zlib.h $(TOP)/zconf.h + +zutil.obj: $(TOP)/zutil.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h + +gvmat64.obj: $(TOP)/contrib\masmx64\gvmat64.asm + +inffasx64.obj: $(TOP)/contrib\masmx64\inffasx64.asm + +inffas8664.obj: $(TOP)/contrib\masmx64\inffas8664.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h \ + $(TOP)/inftrees.h $(TOP)/inflate.h $(TOP)/inffast.h + +inffas32.obj: $(TOP)/contrib\masmx86\inffas32.asm + +match686.obj: $(TOP)/contrib\masmx86\match686.asm + +example.obj: $(TOP)/test/example.c $(TOP)/zlib.h $(TOP)/zconf.h + +minigzip.obj: $(TOP)/test/minigzip.c $(TOP)/zlib.h $(TOP)/zconf.h + +zlib1.res: $(TOP)/win32/zlib1.rc + $(RC) $(RCFLAGS) /fo$@ $(TOP)/win32/zlib1.rc + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +testdll: example_d.exe minigzip_d.exe + example_d + echo hello world | minigzip_d | minigzip_d -d + + +# cleanup +clean: + -del $(STATICLIB) + -del $(SHAREDLIB) + -del $(IMPLIB) + -del *.obj + -del *.res + -del *.exp + -del *.exe + -del *.pdb + -del *.manifest + -del foo.gz diff --git a/zlib/zlib/win32/README-WIN32.txt b/zlib/zlib/win32/README-WIN32.txt new file mode 100644 index 00000000..3d77d521 --- /dev/null +++ b/zlib/zlib/win32/README-WIN32.txt @@ -0,0 +1,103 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.8 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). Two compiled +examples are distributed in this package, example and minigzip. The example_d +and minigzip_d flavors validate that the zlib1.dll file is working correctly. + +Questions about zlib should be sent to . The zlib home page +is http://zlib.net/ . Before reporting a problem, please check this site to +verify that you have the latest version of zlib; otherwise get the latest +version and check whether the problem still exists or not. + +PLEASE read DLL_FAQ.txt, and the the zlib FAQ http://zlib.net/zlib_faq.html +before asking for help. + + +Manifest: + +The package zlib-1.2.8-win32-x86.zip will contain the following files: + + README-WIN32.txt This document + ChangeLog Changes since previous zlib packages + DLL_FAQ.txt Frequently asked questions about zlib1.dll + zlib.3.pdf Documentation of this library in Adobe Acrobat format + + example.exe A statically-bound example (using zlib.lib, not the dll) + example.pdb Symbolic information for debugging example.exe + + example_d.exe A zlib1.dll bound example (using zdll.lib) + example_d.pdb Symbolic information for debugging example_d.exe + + minigzip.exe A statically-bound test program (using zlib.lib, not the dll) + minigzip.pdb Symbolic information for debugging minigzip.exe + + minigzip_d.exe A zlib1.dll bound test program (using zdll.lib) + minigzip_d.pdb Symbolic information for debugging minigzip_d.exe + + zlib.h Install these files into the compilers' INCLUDE path to + zconf.h compile programs which use zlib.lib or zdll.lib + + zdll.lib Install these files into the compilers' LIB path if linking + zdll.exp a compiled program to the zlib1.dll binary + + zlib.lib Install these files into the compilers' LIB path to link zlib + zlib.pdb into compiled programs, without zlib1.dll runtime dependency + (zlib.pdb provides debugging info to the compile time linker) + + zlib1.dll Install this binary shared library into the system PATH, or + the program's runtime directory (where the .exe resides) + zlib1.pdb Install in the same directory as zlib1.dll, in order to debug + an application crash using WinDbg or similar tools. + +All .pdb files above are entirely optional, but are very useful to a developer +attempting to diagnose program misbehavior or a crash. Many additional +important files for developers can be found in the zlib127.zip source package +available from http://zlib.net/ - review that package's README file for details. + + +Acknowledgments: + +The deflate format used by zlib was defined by Phil Katz. The deflate and +zlib specifications were written by L. Peter Deutsch. Thanks to all the +people who reported problems and suggested various improvements in zlib; they +are too numerous to cite here. + + +Copyright notice: + + (C) 1995-2012 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. diff --git a/zlib/zlib/win32/VisualC.txt b/zlib/zlib/win32/VisualC.txt new file mode 100644 index 00000000..579a5fc9 --- /dev/null +++ b/zlib/zlib/win32/VisualC.txt @@ -0,0 +1,3 @@ + +To build zlib using the Microsoft Visual C++ environment, +use the appropriate project from the projects/ directory. diff --git a/zlib/zlib/win32/zlib.def b/zlib/zlib/win32/zlib.def new file mode 100644 index 00000000..face6551 --- /dev/null +++ b/zlib/zlib/win32/zlib.def @@ -0,0 +1,86 @@ +; zlib data compression library +EXPORTS +; basic functions + zlibVersion + deflate + deflateEnd + inflate + inflateEnd +; advanced functions + deflateSetDictionary + deflateCopy + deflateReset + deflateParams + deflateTune + deflateBound + deflatePending + deflatePrime + deflateSetHeader + inflateSetDictionary + inflateGetDictionary + inflateSync + inflateCopy + inflateReset + inflateReset2 + inflatePrime + inflateMark + inflateGetHeader + inflateBack + inflateBackEnd + zlibCompileFlags +; utility functions + compress + compress2 + compressBound + uncompress + gzopen + gzdopen + gzbuffer + gzsetparams + gzread + gzwrite + gzprintf + gzvprintf + gzputs + gzgets + gzputc + gzgetc + gzungetc + gzflush + gzseek + gzrewind + gztell + gzoffset + gzeof + gzdirect + gzclose + gzclose_r + gzclose_w + gzerror + gzclearerr +; large file functions + gzopen64 + gzseek64 + gztell64 + gzoffset64 + adler32_combine64 + crc32_combine64 +; checksum functions + adler32 + crc32 + adler32_combine + crc32_combine +; various hacks, don't look :) + deflateInit_ + deflateInit2_ + inflateInit_ + inflateInit2_ + inflateBackInit_ + gzgetc_ + zError + inflateSyncPoint + get_crc_table + inflateUndermine + inflateResetKeep + deflateResetKeep + gzopen_w diff --git a/zlib/zlib/win32/zlib1.rc b/zlib/zlib/win32/zlib1.rc new file mode 100644 index 00000000..5c0feed1 --- /dev/null +++ b/zlib/zlib/win32/zlib1.rc @@ -0,0 +1,40 @@ +#include +#include "../zlib.h" + +#ifdef GCC_WINDRES +VS_VERSION_INFO VERSIONINFO +#else +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE +#endif + FILEVERSION ZLIB_VER_MAJOR,ZLIB_VER_MINOR,ZLIB_VER_REVISION,0 + PRODUCTVERSION ZLIB_VER_MAJOR,ZLIB_VER_MINOR,ZLIB_VER_REVISION,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS 1 +#else + FILEFLAGS 0 +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "zlib data compression library\0" + VALUE "FileVersion", ZLIB_VERSION "\0" + VALUE "InternalName", "zlib1.dll\0" + VALUE "LegalCopyright", "(C) 1995-2013 Jean-loup Gailly & Mark Adler\0" + VALUE "OriginalFilename", "zlib1.dll\0" + VALUE "ProductName", "zlib\0" + VALUE "ProductVersion", ZLIB_VERSION "\0" + VALUE "Comments", "For more information visit http://www.zlib.net/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/zlib/zlib/zconf.h b/zlib/zlib/zconf.h new file mode 100644 index 00000000..9987a775 --- /dev/null +++ b/zlib/zlib/zconf.h @@ -0,0 +1,511 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetHeader z_inflateGetHeader +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/zlib/zlib/zconf.h.cmakein b/zlib/zlib/zconf.h.cmakein new file mode 100644 index 00000000..043019cd --- /dev/null +++ b/zlib/zlib/zconf.h.cmakein @@ -0,0 +1,513 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H +#cmakedefine Z_PREFIX +#cmakedefine Z_HAVE_UNISTD_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetHeader z_inflateGetHeader +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/zlib/zlib/zconf.h.in b/zlib/zlib/zconf.h.in new file mode 100644 index 00000000..9987a775 --- /dev/null +++ b/zlib/zlib/zconf.h.in @@ -0,0 +1,511 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetHeader z_inflateGetHeader +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/zlib/zlib/zlib.3 b/zlib/zlib/zlib.3 new file mode 100644 index 00000000..0160e62b --- /dev/null +++ b/zlib/zlib/zlib.3 @@ -0,0 +1,151 @@ +.TH ZLIB 3 "28 Apr 2013" +.SH NAME +zlib \- compression/decompression library +.SH SYNOPSIS +[see +.I zlib.h +for full description] +.SH DESCRIPTION +The +.I zlib +library is a general purpose data compression library. +The code is thread safe, assuming that the standard library functions +used are thread safe, such as memory allocation routines. +It provides in-memory compression and decompression functions, +including integrity checks of the uncompressed data. +This version of the library supports only one compression method (deflation) +but other algorithms may be added later +with the same stream interface. +.LP +Compression can be done in a single step if the buffers are large enough +or can be done by repeated calls of the compression function. +In the latter case, +the application must provide more input and/or consume the output +(providing more output space) before each call. +.LP +The library also supports reading and writing files in +.IR gzip (1) +(.gz) format +with an interface similar to that of stdio. +.LP +The library does not install any signal handler. +The decoder checks the consistency of the compressed data, +so the library should never crash even in the case of corrupted input. +.LP +All functions of the compression library are documented in the file +.IR zlib.h . +The distribution source includes examples of use of the library +in the files +.I test/example.c +and +.IR test/minigzip.c, +as well as other examples in the +.IR examples/ +directory. +.LP +Changes to this version are documented in the file +.I ChangeLog +that accompanies the source. +.LP +.I zlib +is available in Java using the java.util.zip package: +.IP +http://java.sun.com/developer/technicalArticles/Programming/compression/ +.LP +A Perl interface to +.IR zlib , +written by Paul Marquess (pmqs@cpan.org), +is available at CPAN (Comprehensive Perl Archive Network) sites, +including: +.IP +http://search.cpan.org/~pmqs/IO-Compress-Zlib/ +.LP +A Python interface to +.IR zlib , +written by A.M. Kuchling (amk@magnet.com), +is available in Python 1.5 and later versions: +.IP +http://docs.python.org/library/zlib.html +.LP +.I zlib +is built into +.IR tcl: +.IP +http://wiki.tcl.tk/4610 +.LP +An experimental package to read and write files in .zip format, +written on top of +.I zlib +by Gilles Vollant (info@winimage.com), +is available at: +.IP +http://www.winimage.com/zLibDll/minizip.html +and also in the +.I contrib/minizip +directory of the main +.I zlib +source distribution. +.SH "SEE ALSO" +The +.I zlib +web site can be found at: +.IP +http://zlib.net/ +.LP +The data format used by the zlib library is described by RFC +(Request for Comments) 1950 to 1952 in the files: +.IP +http://tools.ietf.org/html/rfc1950 (for the zlib header and trailer format) +.br +http://tools.ietf.org/html/rfc1951 (for the deflate compressed data format) +.br +http://tools.ietf.org/html/rfc1952 (for the gzip header and trailer format) +.LP +Mark Nelson wrote an article about +.I zlib +for the Jan. 1997 issue of Dr. Dobb's Journal; +a copy of the article is available at: +.IP +http://marknelson.us/1997/01/01/zlib-engine/ +.SH "REPORTING PROBLEMS" +Before reporting a problem, +please check the +.I zlib +web site to verify that you have the latest version of +.IR zlib ; +otherwise, +obtain the latest version and see if the problem still exists. +Please read the +.I zlib +FAQ at: +.IP +http://zlib.net/zlib_faq.html +.LP +before asking for help. +Send questions and/or comments to zlib@gzip.org, +or (for the Windows DLL version) to Gilles Vollant (info@winimage.com). +.SH AUTHORS +Version 1.2.8 +Copyright (C) 1995-2013 Jean-loup Gailly (jloup@gzip.org) +and Mark Adler (madler@alumni.caltech.edu). +.LP +This software is provided "as-is," +without any express or implied warranty. +In no event will the authors be held liable for any damages +arising from the use of this software. +See the distribution directory with respect to requirements +governing redistribution. +The deflate format used by +.I zlib +was defined by Phil Katz. +The deflate and +.I zlib +specifications were written by L. Peter Deutsch. +Thanks to all the people who reported problems and suggested various +improvements in +.IR zlib ; +who are too numerous to cite here. +.LP +UNIX manual page by R. P. C. Rodgers, +U.S. National Library of Medicine (rodgers@nlm.nih.gov). +.\" end of man page diff --git a/zlib/zlib/zlib.3.pdf b/zlib/zlib/zlib.3.pdf new file mode 100644 index 00000000..a346b5d7 Binary files /dev/null and b/zlib/zlib/zlib.3.pdf differ diff --git a/zlib/zlib/zlib.h b/zlib/zlib/zlib.h new file mode 100644 index 00000000..3e0c7672 --- /dev/null +++ b/zlib/zlib/zlib.h @@ -0,0 +1,1768 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.8, April 28th, 2013 + + Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.8" +#define ZLIB_VERNUM 0x1280 +#define ZLIB_VER_MAJOR 1 +#define ZLIB_VER_MINOR 2 +#define ZLIB_VER_REVISION 8 +#define ZLIB_VER_SUBREVISION 0 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed data. + This version of the library supports only one compression method (deflation) + but other algorithms will be added later and will have the same stream + interface. + + Compression can be done in a single step if the buffers are large enough, + or can be done by repeated calls of the compression function. In the latter + case, the application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never crash + even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + z_const Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total number of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total number of bytes output so far */ + + z_const char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has dropped + to zero. It must update next_out and avail_out when avail_out has dropped + to zero. The application must initialize zalloc, zfree and opaque before + calling the init function. All other fields are set by the compression + library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this if + the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers + returned by zalloc for objects of exactly 65536 bytes *must* have their + offset normalized to zero. The default allocation function provided by this + library ensures this (see zutil.c). To reduce memory requirements and avoid + any allocation of 64K objects, at the expense of compression ratio, compile + the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or progress + reports. After compression, total_in holds the total size of the + uncompressed data and may be saved for use in the decompressor (particularly + if the decompressor wants to decompress everything in a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +#define Z_TREES 6 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check + is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If + zalloc and zfree are set to Z_NULL, deflateInit updates them to use default + allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at all + (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION + requests a default compromise between speed and compression (currently + equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if level is not a valid compression level, or + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). msg is set to null + if there is no error message. deflateInit does not perform any compression: + this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). Some + output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating avail_in or avail_out accordingly; avail_out should + never be zero before the call. The application can consume the compressed + output when it wants, for example when the output buffer is full (avail_out + == 0), or after each call of deflate(). If deflate returns Z_OK and with + zero avail_out, it must be called again after making room in the output + buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumulate before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In + particular avail_in is zero after the call if enough output space has been + provided before the call.) Flushing may degrade compression for some + compression algorithms and so it should be used only when necessary. This + completes the current deflate block and follows it with an empty stored block + that is three bits plus filler bits to the next byte, followed by four bytes + (00 00 ff ff). + + If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the + output buffer, but the output is not aligned to a byte boundary. All of the + input data so far will be available to the decompressor, as for Z_SYNC_FLUSH. + This completes the current deflate block and follows it with an empty fixed + codes block that is 10 bits long. This assures that enough bytes are output + in order for the decompressor to finish the block before the empty fixed code + block. + + If flush is set to Z_BLOCK, a deflate block is completed and emitted, as + for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to + seven bits of the current block are held to be written as the next byte after + the next deflate block is completed. In this case, the decompressor may not + be provided enough bits at this point in order to complete decompression of + the data provided so far to the compressor. It may need to wait for the next + block to be emitted. This is for advanced applications that need to control + the emission of deflate blocks. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there was + enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the stream + are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least the + value returned by deflateBound (see below). Then deflate is guaranteed to + return Z_STREAM_END. If not enough output space is provided, deflate will + not return Z_STREAM_END, and it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect the + compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, msg + may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the + exact value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit() does not process any header information -- that is deferred + until inflate() is called. +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing will + resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there is + no more input data or no more space in the output buffer (see below about + the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating the next_* and avail_* values accordingly. The + application can consume the uncompressed output when it wants, for example + when the output buffer is full (avail_out == 0), or after each call of + inflate(). If inflate returns Z_OK and with zero avail_out, it must be + called again after making room in the output buffer because there might be + more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH, + Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() + stop if and when it gets to the next deflate block boundary. When decoding + the zlib or gzip format, this will cause inflate() to return immediately + after the header and before the first block. When doing a raw inflate, + inflate() will go ahead and process the first block, and will return when it + gets to the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 if + inflate() is currently decoding the last block in the deflate stream, plus + 128 if inflate() returned immediately after decoding an end-of-block code or + decoding the complete header up to just before the first byte of the deflate + stream. The end-of-block will not be indicated until all of the uncompressed + data from that block has been written to strm->next_out. The number of + unused bits may in general be greater than seven, except when bit 7 of + data_type is set, in which case the number of unused bits will be less than + eight. data_type is set as noted here every time inflate() returns for all + flush options, and so can be used to determine the amount of currently + consumed input in bits. + + The Z_TREES option behaves as Z_BLOCK does, but it also returns when the + end of each deflate block header is reached, before any actual data in that + block is decoded. This allows the caller to determine the length of the + deflate block header for later use in random access within a deflate block. + 256 is added to the value of strm->data_type when inflate() returns + immediately after reaching the end of the deflate block header. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step (a + single call of inflate), the parameter flush should be set to Z_FINISH. In + this case all pending input is processed and all pending output is flushed; + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the Adler-32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() can decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically, if requested when + initializing with inflateInit2(). Any information contained in the gzip + header is not retained, so applications that need that information should + instead use raw inflate, see inflateInit2() below, or inflateBack() and + perform their own processing of the gzip header and trailer. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + producted so far. The CRC-32 is checked against the gzip trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may + then call inflateSync() to look for a good compression block if a partial + recovery of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the + caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), no + header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but is + slow and reduces compression ratio; memLevel=9 uses maximum memory for + optimal speed. The default value is 8. See zconf.h for total memory usage + as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as + fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The + strategy parameter only affects the compression ratio but not the + correctness of the compressed output even if it is not set appropriately. + Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler + decoder for special applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid + method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is + incompatible with the version assumed by the caller (ZLIB_VERSION). msg is + set to null if there is no error message. deflateInit2 does not perform any + compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size + provided in deflateInit or deflateInit2. Thus the strings most likely to be + useful should be put at the end of the dictionary, not at the front. In + addition, the current implementation of deflate will use at most the window + size minus 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and can + consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. The + stream will keep the same compression level and any other attributes that + may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different strategy. + If the compression level is changed, the input available so far is + compressed with the old level (and may be flushed); the new level will take + effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to be + compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if + strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or + deflateInit2(), and after deflateSetHeader(), if used. This would be used + to allocate an output buffer for deflation in a single pass, and so would be + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, + int *bits)); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits + leftover from a previous deflate stream when appending to it. As such, this + function can only be used for raw deflate, and must be used before the first + deflate() call after a deflateInit2() or deflateReset(). bits must be less + than or equal to 16, and that many of the least significant bits of value + will be inserted in the output. + + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be zero to request that inflate use the window size in + the zlib header of the compressed stream. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a + crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit2 does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit2() does not process any header information -- that is + deferred until inflate() is called. +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. The + stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, + int windowBits)); +/* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted + the same as it is for inflateInit2. + + inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL), or if + the windowBits parameter is invalid. +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + If bits is negative, then the input stream bit buffer is emptied. Then + inflatePrime() can be called again to put bits in the buffer. This is used + to clear out bits leftover after feeding inflate a block description prior + to feeding inflate codes. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); +/* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the + return value down 16 bits. If the upper value is -1 and the lower value is + zero, then inflate() is currently decoding information outside of a block. + If the upper value is -1 and the lower value is non-zero, then inflate is in + the middle of a stored block, with the lower value equaling the number of + bytes from the input remaining to copy. If the upper value is not -1, then + it is the number of bits back from the current bit position in the input of + the code (literal or length/distance pair) currently being processed. In + that case the lower value is the number of bytes already emitted for that + code. + + A code is being processed if inflate is waiting for more input to complete + decoding of the code, or if it has completed decoding but is waiting for + more output space to write the literal or match data. + + inflateMark() is used to mark locations in the input data for random + access, which may be at bit positions, and to note those cases where the + output of a code may span boundaries of random access blocks. The current + location in the input stream can be determined from avail_in and data_type + as noted in the description for the Z_BLOCK flush parameter for inflate. + + inflateMark returns the value noted above or -1 << 16 if the provided + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be + used to force inflate() to return immediately after header processing is + complete and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When any + of extra, name, or comment are not Z_NULL and the respective field is not + present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the parameters are invalid, Z_MEM_ERROR if the internal state could not be + allocated, or Z_VERSION_ERROR if the version of the library does not match + the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, + z_const unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free the + allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects only + the raw deflate stream to decompress. This is different from the normal + behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format error + in the deflate stream (in which case strm->msg is set to indicate the nature + of the error), or Z_STREAM_ERROR if the stream was not properly initialized. + In the case of Z_BUF_ERROR, an input or output error can be distinguished + using strm->next_in which will be Z_NULL only if in() returned an error. If + strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning + non-zero. (in() will always be called before out(), so strm->next_in is + assured to be defined if out() returns non-zero.) Note that inflateBack() + cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + +#ifndef Z_SOLO + + /* utility functions */ + +/* + The following utility functions are implemented on top of the basic + stream-oriented functions. To simplify the interface, some default options + are assumed (compression level and memory usage, standard memory allocation + functions). The source code of these utility functions can be modified if + you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, destLen + is the actual size of the uncompressed buffer. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. +*/ + + /* gzip file access functions */ + +/* + This library supports reading and writing files in gzip (.gz) format with + an interface similar to that of stdio, using the functions that start with + "gz". The gzip format is different from the zlib format. gzip is a gzip + wrapper, documented in RFC 1952, wrapped around a deflate stream. +*/ + +typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ + +/* +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); + + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or + a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only + compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' + for fixed code compression as in "wb9F". (See the description of + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. + + gzopen returns NULL if the file could not be opened, if there was + insufficient memory to allocate the gzFile state, or if an invalid mode was + specified (an 'r', 'w', or 'a' was not provided, or '+' was provided). + errno can be checked to determine if the reason gzopen failed was that the + file could not be opened. +*/ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file + has been previously opened with fopen). The mode parameter is as in gzopen. + + The next call of gzclose on the returned gzFile will also close the file + descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor + fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, + mode);. The duplicated descriptor should be saved to avoid a leak, since + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. + + gzdopen returns NULL if there was insufficient memory to allocate the + gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not + provided, or '+' was provided), or if fd is -1. The file descriptor is not + used until the next gz* read, write, seek, or close operation, so gzdopen + will not detect if fd is invalid (unless fd is -1). +*/ + +ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); +/* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after + gzopen() or gzdopen(), and before any other calls that read or write the + file. The buffer memory allocation is always deferred to the first read or + write. Two buffers are allocated, either both of the specified size when + writing, or one of the specified size and the other twice that size when + reading. A larger buffer size of, for example, 64K or 128K bytes will + noticeably increase the speed of decompression (reading). + + The new buffer size also affects the maximum length for gzprintf(). + + gzbuffer() returns 0 on success, or -1 on failure, such as being called + too late. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. + + After reaching the end of a gzip stream in the input, gzread will continue + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. + + gzread returns the number of uncompressed bytes actually read, less than + len for end of file, or -1 for error. +*/ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. +*/ + +ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the arguments to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written, or 0 in case of error. The number of + uncompressed bytes written is limited to 8191, or one less than the buffer + size given to gzbuffer(). The caller should assure that this limit is not + exceeded. If it is exceeded, then gzprintf() will return an error (0) with + nothing written. In this case, there may also be a buffer overflow with + unpredictable consequences, which is possible only if zlib was compiled with + the insecure functions sprintf() or vsprintf() because the secure snprintf() + or vsnprintf() functions were not available. This can be determined using + zlibCompileFlags(). +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file + condition is encountered. If any characters are read or if len == 1, the + string is terminated with a null character. If no characters are read due + to an end-of-file or len < 1, then the buffer is left untouched. + + gzgets returns buf which is a null-terminated string, or it returns NULL + for end-of-file or in case of error. If there was an error, the contents at + buf are indeterminate. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. + gzungetc() returns the character pushed, or -1 on failure. gzungetc() will + fail if c is -1, and may fail if a character has been pushed but not read + yet. If gzungetc is used immediately after gzopen or gzdopen, at least the + output buffer size of pushed characters is allowed. (See gzbuffer above.) + The pushed character will be discarded if the stream is repositioned with + gzseek() or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number + (see function gzerror below). gzflush is only permitted when writing. + + If the flush parameter is Z_FINISH, the remaining data is written and the + gzip stream is completed in the output. If gzwrite() is called again, a new + gzip stream will be started in the output. gzread() is able to read such + concatented gzip streams. + + gzflush should be called only when strictly necessary because it will + degrade compression if called too often. +*/ + +/* +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); + + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); + + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the + uncompressed data stream, and is zero when starting, even if appending or + reading a gzip stream from the middle of a file using gzdopen(). + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); + + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when + appending or when using gzdopen() for reading. When reading, the offset + does not include as yet unused buffered input. This information can be used + for a progress indicator. On error, gzoffset() returns -1. +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the + read tried to go past the end of the input, but came up short. Therefore, + just like feof(), gzeof() may return false even if there is no more data to + read, in the event that the last read request was for the exact number of + bytes remaining in the input file. This will happen if the input file size + is an exact multiple of the buffer size. + + If gzeof() returns true, then the read functions will return no more data, + unless the end-of-file indicator is reset by gzclearerr() and the input file + has grown since the previous end of file was detected. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. + + If the input file is empty, gzdirect() will return true, since the input + does not contain a gzip stream. + + If gzdirect() is used immediately after gzopen() or gzdopen() it will + cause buffers to be allocated to allow reading the file to determine if it + is a gzip file. Therefore if gzbuffer() is used, it should be called before + gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you + cannot call gzerror with file, since its structures have been deallocated. + gzclose must not be called more than once on the same file, just as free + must not be called more than once on the same allocation. + + gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. +*/ + +ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); +/* + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to + using these instead of gzclose() is that they avoid linking in zlib + compression or decompression code that is not used when only reading or only + writing respectively. If gzclose() is used, then both compression and + decompression code will be included the application when linking to a static + zlib library. +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred + in the file system and not in the compression library, errnum is set to + Z_ERRNO and the application may consult errno to get the exact error code. + + The application must not modify the returned string. Future calls to + this function may invalidate the previously returned string. If file is + closed, then the string previously returned by gzerror will no longer be + available. + + gzerror() should be used to distinguish errors from end-of-file for those + functions above that do not distinguish those cases in their return values. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + +#endif /* !Z_SOLO */ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the compression + library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is Z_NULL, this function returns the + required initial value for the checksum. + + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. + + Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +/* +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); + + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +/* +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, (int)sizeof(z_stream)) + +#ifndef Z_SOLO + +/* gzgetc() macro and its supporting function and exposed data structure. Note + * that the real internal state is much larger than the exposed structure. + * This abbreviated structure exposes just enough for the gzgetc() macro. The + * user should not mess with these exposed elements, since their names or + * behavior could change in the future, perhaps even capriciously. They can + * only be used by the gzgetc() macro. You have been warned. + */ +struct gzFile_s { + unsigned have; + unsigned char *next; + z_off64_t pos; +}; +ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +# define z_gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#else +# define gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#endif + +/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or + * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if + * both are true, the application gets the *64 functions, and the regular + * functions are changed to 64 bits) -- in case these are set on systems + * without large file support, _LFS64_LARGEFILE must also be true + */ +#ifdef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); + ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); +#endif + +#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +# ifdef Z_PREFIX_SET +# define z_gzopen z_gzopen64 +# define z_gzseek z_gzseek64 +# define z_gztell z_gztell64 +# define z_gzoffset z_gzoffset64 +# define z_adler32_combine z_adler32_combine64 +# define z_crc32_combine z_crc32_combine64 +# else +# define gzopen gzopen64 +# define gzseek gzseek64 +# define gztell gztell64 +# define gzoffset gzoffset64 +# define adler32_combine adler32_combine64 +# define crc32_combine crc32_combine64 +# endif +# ifndef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +# endif +#else + ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); +#endif + +#else /* Z_SOLO */ + + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + +#endif /* !Z_SOLO */ + +/* hack for buggy compilers */ +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; +#endif + +/* undocumented functions */ +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); +ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +#if defined(_WIN32) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, + const char *mode)); +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, + const char *format, + va_list va)); +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ diff --git a/zlib/zlib/zlib.map b/zlib/zlib/zlib.map new file mode 100644 index 00000000..55c6647e --- /dev/null +++ b/zlib/zlib/zlib.map @@ -0,0 +1,83 @@ +ZLIB_1.2.0 { + global: + compressBound; + deflateBound; + inflateBack; + inflateBackEnd; + inflateBackInit_; + inflateCopy; + local: + deflate_copyright; + inflate_copyright; + inflate_fast; + inflate_table; + zcalloc; + zcfree; + z_errmsg; + gz_error; + gz_intmax; + _*; +}; + +ZLIB_1.2.0.2 { + gzclearerr; + gzungetc; + zlibCompileFlags; +} ZLIB_1.2.0; + +ZLIB_1.2.0.8 { + deflatePrime; +} ZLIB_1.2.0.2; + +ZLIB_1.2.2 { + adler32_combine; + crc32_combine; + deflateSetHeader; + inflateGetHeader; +} ZLIB_1.2.0.8; + +ZLIB_1.2.2.3 { + deflateTune; + gzdirect; +} ZLIB_1.2.2; + +ZLIB_1.2.2.4 { + inflatePrime; +} ZLIB_1.2.2.3; + +ZLIB_1.2.3.3 { + adler32_combine64; + crc32_combine64; + gzopen64; + gzseek64; + gztell64; + inflateUndermine; +} ZLIB_1.2.2.4; + +ZLIB_1.2.3.4 { + inflateReset2; + inflateMark; +} ZLIB_1.2.3.3; + +ZLIB_1.2.3.5 { + gzbuffer; + gzoffset; + gzoffset64; + gzclose_r; + gzclose_w; +} ZLIB_1.2.3.4; + +ZLIB_1.2.5.1 { + deflatePending; +} ZLIB_1.2.3.5; + +ZLIB_1.2.5.2 { + deflateResetKeep; + gzgetc_; + inflateResetKeep; +} ZLIB_1.2.5.1; + +ZLIB_1.2.7.1 { + inflateGetDictionary; + gzvprintf; +} ZLIB_1.2.5.2; diff --git a/zlib/zlib/zlib.pc.cmakein b/zlib/zlib/zlib.pc.cmakein new file mode 100644 index 00000000..a5e64293 --- /dev/null +++ b/zlib/zlib/zlib.pc.cmakein @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@INSTALL_LIB_DIR@ +sharedlibdir=@INSTALL_LIB_DIR@ +includedir=@INSTALL_INC_DIR@ + +Name: zlib +Description: zlib compression library +Version: @VERSION@ + +Requires: +Libs: -L${libdir} -L${sharedlibdir} -lz +Cflags: -I${includedir} diff --git a/zlib/zlib/zlib.pc.in b/zlib/zlib/zlib.pc.in new file mode 100644 index 00000000..7e5acf9c --- /dev/null +++ b/zlib/zlib/zlib.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +sharedlibdir=@sharedlibdir@ +includedir=@includedir@ + +Name: zlib +Description: zlib compression library +Version: @VERSION@ + +Requires: +Libs: -L${libdir} -L${sharedlibdir} -lz +Cflags: -I${includedir} diff --git a/zlib/zlib/zlib2ansi b/zlib/zlib/zlib2ansi new file mode 100755 index 00000000..15e3e165 --- /dev/null +++ b/zlib/zlib/zlib2ansi @@ -0,0 +1,152 @@ +#!/usr/bin/perl + +# Transform K&R C function definitions into ANSI equivalent. +# +# Author: Paul Marquess +# Version: 1.0 +# Date: 3 October 2006 + +# TODO +# +# Asumes no function pointer parameters. unless they are typedefed. +# Assumes no literal strings that look like function definitions +# Assumes functions start at the beginning of a line + +use strict; +use warnings; + +local $/; +$_ = <>; + +my $sp = qr{ \s* (?: /\* .*? \*/ )? \s* }x; # assume no nested comments + +my $d1 = qr{ $sp (?: [\w\*\s]+ $sp)* $sp \w+ $sp [\[\]\s]* $sp }x ; +my $decl = qr{ $sp (?: \w+ $sp )+ $d1 }xo ; +my $dList = qr{ $sp $decl (?: $sp , $d1 )* $sp ; $sp }xo ; + + +while (s/^ + ( # Start $1 + ( # Start $2 + .*? # Minimal eat content + ( ^ \w [\w\s\*]+ ) # $3 -- function name + \s* # optional whitespace + ) # $2 - Matched up to before parameter list + + \( \s* # Literal "(" + optional whitespace + ( [^\)]+ ) # $4 - one or more anythings except ")" + \s* \) # optional whitespace surrounding a Literal ")" + + ( (?: $dList )+ ) # $5 + + $sp ^ { # literal "{" at start of line + ) # Remember to $1 + //xsom + ) +{ + my $all = $1 ; + my $prefix = $2; + my $param_list = $4 ; + my $params = $5; + + StripComments($params); + StripComments($param_list); + $param_list =~ s/^\s+//; + $param_list =~ s/\s+$//; + + my $i = 0 ; + my %pList = map { $_ => $i++ } + split /\s*,\s*/, $param_list; + my $pMatch = '(\b' . join('|', keys %pList) . '\b)\W*$' ; + + my @params = split /\s*;\s*/, $params; + my @outParams = (); + foreach my $p (@params) + { + if ($p =~ /,/) + { + my @bits = split /\s*,\s*/, $p; + my $first = shift @bits; + $first =~ s/^\s*//; + push @outParams, $first; + $first =~ /^(\w+\s*)/; + my $type = $1 ; + push @outParams, map { $type . $_ } @bits; + } + else + { + $p =~ s/^\s+//; + push @outParams, $p; + } + } + + + my %tmp = map { /$pMatch/; $_ => $pList{$1} } + @outParams ; + + @outParams = map { " $_" } + sort { $tmp{$a} <=> $tmp{$b} } + @outParams ; + + print $prefix ; + print "(\n" . join(",\n", @outParams) . ")\n"; + print "{" ; + +} + +# Output any trailing code. +print ; +exit 0; + + +sub StripComments +{ + + no warnings; + + # Strip C & C++ coments + # From the perlfaq + $_[0] =~ + + s{ + /\* ## Start of /* ... */ comment + [^*]*\*+ ## Non-* followed by 1-or-more *'s + ( + [^/*][^*]*\*+ + )* ## 0-or-more things which don't start with / + ## but do end with '*' + / ## End of /* ... */ comment + + | ## OR C++ Comment + // ## Start of C++ comment // + [^\n]* ## followed by 0-or-more non end of line characters + + | ## OR various things which aren't comments: + + ( + " ## Start of " ... " string + ( + \\. ## Escaped char + | ## OR + [^"\\] ## Non "\ + )* + " ## End of " ... " string + + | ## OR + + ' ## Start of ' ... ' string + ( + \\. ## Escaped char + | ## OR + [^'\\] ## Non '\ + )* + ' ## End of ' ... ' string + + | ## OR + + . ## Anything other char + [^/"'\\]* ## Chars which doesn't start a comment, string or escape + ) + }{$2}gxs; + +} diff --git a/zlib/zlib/zutil.c b/zlib/zlib/zutil.c new file mode 100644 index 00000000..23d2ebef --- /dev/null +++ b/zlib/zlib/zutil.c @@ -0,0 +1,324 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005, 2010, 2011, 2012 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" +#ifndef Z_SOLO +# include "gzguts.h" +#endif + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +z_const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch ((int)(sizeof(uInt))) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch ((int)(sizeof(uLong))) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch ((int)(sizeof(voidpf))) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch ((int)(sizeof(z_off_t))) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +} + +#ifdef DEBUG + +# ifndef verbose +# define verbose 0 +# endif +int ZLIB_INTERNAL z_verbose = verbose; + +void ZLIB_INTERNAL z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void ZLIB_INTERNAL zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int ZLIB_INTERNAL zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void ZLIB_INTERNAL zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + +#ifndef Z_SOLO + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void ZLIB_INTERNAL zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ + +#endif /* !Z_SOLO */ diff --git a/zlib/zlib/zutil.h b/zlib/zlib/zutil.h new file mode 100644 index 00000000..24ab06b1 --- /dev/null +++ b/zlib/zlib/zutil.h @@ -0,0 +1,253 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#ifdef HAVE_HIDDEN +# define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) +#else +# define ZLIB_INTERNAL +#endif + +#include "zlib.h" + +#if defined(STDC) && !defined(Z_SOLO) +# if !(defined(_WIN32_WCE) && defined(_MSC_VER)) +# include +# endif +# include +# include +#endif + +#ifdef Z_SOLO + typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# ifndef Z_SOLO +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# if defined(M_I86) && !defined(Z_SOLO) +# include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# ifndef Z_SOLO +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + +#if defined(__BORLANDC__) && !defined(MSDOS) + #pragma warn -8004 + #pragma warn -8008 + #pragma warn -8066 +#endif + +/* provide prototypes for these when building zlib without LFS */ +#if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(pyr) || defined(Z_SOLO) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include + extern int ZLIB_INTERNAL z_verbose; + extern void ZLIB_INTERNAL z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + +#ifndef Z_SOLO + voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, + unsigned size)); + void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#endif + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +/* Reverse the bytes in a 32-bit value */ +#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +#endif /* ZUTIL_H */